Merge "Use path outlines to define shadow shapes"
diff --git a/api/current.txt b/api/current.txt
index 2889e62..fd901f2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12845,6 +12845,7 @@
method public final void queueSecureInputBuffer(int, int, android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException;
method public final void release();
method public final void releaseOutputBuffer(int, boolean);
+ method public void setNotificationCallback(android.media.MediaCodec.NotificationCallback);
method public final void setParameters(android.os.Bundle);
method public final void setVideoScalingMode(int);
method public final void signalEndOfInputStream();
@@ -12894,6 +12895,10 @@
field public int numSubSamples;
}
+ public static abstract interface MediaCodec.NotificationCallback {
+ method public abstract void onCodecNotify(android.media.MediaCodec);
+ }
+
public final class MediaCodecInfo {
method public final android.media.MediaCodecInfo.CodecCapabilities getCapabilitiesForType(java.lang.String);
method public final java.lang.String getName();
@@ -52412,7 +52417,7 @@
method public java.lang.String getString(java.lang.String) throws org.json.JSONException;
method public boolean has(java.lang.String);
method public boolean isNull(java.lang.String);
- method public java.util.Iterator keys();
+ method public java.util.Iterator<java.lang.String> keys();
method public int length();
method public org.json.JSONArray names();
method public static java.lang.String numberToString(java.lang.Number) throws org.json.JSONException;
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 22322e3..f532f7c 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -457,8 +457,10 @@
mSocket.close();
mSocket = null;
}
- if(mPfd != null)
- mPfd.detachFd();
+ if (mPfd != null) {
+ mPfd.close();
+ mPfd = null;
+ }
}
}
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 9c46d96..8434c5d 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -338,7 +338,7 @@
* the normal application lifecycle.
*
* <p>Comes from the
- * {@link android.R.styleable#AndroidManifestApplication_cantSaveState android:cantSaveState}
+ * android.R.styleable#AndroidManifestApplication_cantSaveState
* attribute of the <application> tag.
*
* {@hide}
@@ -456,7 +456,13 @@
* behavior was introduced.
*/
public int targetSdkVersion;
-
+
+ /**
+ * The app's declared version code.
+ * @hide
+ */
+ public int versionCode;
+
/**
* When false, indicates that all components within this application are
* considered disabled, regardless of their individually set enabled status.
@@ -508,7 +514,8 @@
if (sharedLibraryFiles != null) {
pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
}
- pw.println(prefix + "enabled=" + enabled + " targetSdkVersion=" + targetSdkVersion);
+ pw.println(prefix + "enabled=" + enabled + " targetSdkVersion=" + targetSdkVersion
+ + " versionCode=" + versionCode);
if (manageSpaceActivityName != null) {
pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName);
}
@@ -576,6 +583,7 @@
dataDir = orig.dataDir;
uid = orig.uid;
targetSdkVersion = orig.targetSdkVersion;
+ versionCode = orig.versionCode;
enabled = orig.enabled;
enabledSetting = orig.enabledSetting;
installLocation = orig.installLocation;
@@ -616,6 +624,7 @@
dest.writeString(dataDir);
dest.writeInt(uid);
dest.writeInt(targetSdkVersion);
+ dest.writeInt(versionCode);
dest.writeInt(enabled ? 1 : 0);
dest.writeInt(enabledSetting);
dest.writeInt(installLocation);
@@ -655,6 +664,7 @@
dataDir = source.readString();
uid = source.readInt();
targetSdkVersion = source.readInt();
+ versionCode = source.readInt();
enabled = source.readInt() != 0;
enabledSetting = source.readInt();
installLocation = source.readInt();
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 4607902..8a1fcd3 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -980,7 +980,7 @@
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifest);
- pkg.mVersionCode = sa.getInteger(
+ pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger(
com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
pkg.mVersionName = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifest_versionName, 0);
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index afe5804e..eabe1a3 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -975,8 +975,10 @@
private CharSequence mPrompt;
public void dismiss() {
- mPopup.dismiss();
- mPopup = null;
+ if (mPopup != null) {
+ mPopup.dismiss();
+ mPopup = null;
+ }
}
public boolean isShowing() {
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 0cad33c..eda1db2 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -171,7 +171,7 @@
static final String CSV_SEP = "\t";
// Current version of the parcel format.
- private static final int PARCEL_VERSION = 13;
+ private static final int PARCEL_VERSION = 14;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0x50535453;
@@ -189,7 +189,8 @@
public String mTimePeriodStartClockStr;
public int mFlags;
- public final ProcessMap<PackageState> mPackages = new ProcessMap<PackageState>();
+ public final ProcessMap<SparseArray<PackageState>> mPackages
+ = new ProcessMap<SparseArray<PackageState>>();
public final ProcessMap<ProcessState> mProcesses = new ProcessMap<ProcessState>();
public final long[] mMemFactorDurations = new long[ADJ_COUNT];
@@ -227,40 +228,45 @@
}
public void add(ProcessStats other) {
- ArrayMap<String, SparseArray<PackageState>> pkgMap = other.mPackages.getMap();
+ ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = other.mPackages.getMap();
for (int ip=0; ip<pkgMap.size(); ip++) {
- String pkgName = pkgMap.keyAt(ip);
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final String pkgName = pkgMap.keyAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
for (int iu=0; iu<uids.size(); iu++) {
- int uid = uids.keyAt(iu);
- PackageState otherState = uids.valueAt(iu);
- final int NPROCS = otherState.mProcesses.size();
- final int NSRVS = otherState.mServices.size();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState otherProc = otherState.mProcesses.valueAt(iproc);
- if (otherProc.mCommonProcess != otherProc) {
- if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
- + " proc " + otherProc.mName);
- ProcessState thisProc = getProcessStateLocked(pkgName, uid,
- otherProc.mName);
- if (thisProc.mCommonProcess == thisProc) {
- if (DEBUG) Slog.d(TAG, "Existing process is single-package, splitting");
- thisProc.mMultiPackage = true;
- long now = SystemClock.uptimeMillis();
- final PackageState pkgState = getPackageStateLocked(pkgName, uid);
- thisProc = thisProc.clone(thisProc.mPackage, now);
- pkgState.mProcesses.put(thisProc.mName, thisProc);
+ final int uid = uids.keyAt(iu);
+ final SparseArray<PackageState> versions = uids.valueAt(iu);
+ for (int iv=0; iv<versions.size(); iv++) {
+ final int vers = versions.keyAt(iv);
+ final PackageState otherState = versions.valueAt(iv);
+ final int NPROCS = otherState.mProcesses.size();
+ final int NSRVS = otherState.mServices.size();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState otherProc = otherState.mProcesses.valueAt(iproc);
+ if (otherProc.mCommonProcess != otherProc) {
+ if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
+ + " vers " + vers + " proc " + otherProc.mName);
+ ProcessState thisProc = getProcessStateLocked(pkgName, uid, vers,
+ otherProc.mName);
+ if (thisProc.mCommonProcess == thisProc) {
+ if (DEBUG) Slog.d(TAG, "Existing process is single-package, splitting");
+ thisProc.mMultiPackage = true;
+ long now = SystemClock.uptimeMillis();
+ final PackageState pkgState = getPackageStateLocked(pkgName, uid,
+ vers);
+ thisProc = thisProc.clone(thisProc.mPackage, now);
+ pkgState.mProcesses.put(thisProc.mName, thisProc);
+ }
+ thisProc.add(otherProc);
}
- thisProc.add(otherProc);
}
- }
- for (int isvc=0; isvc<NSRVS; isvc++) {
- ServiceState otherSvc = otherState.mServices.valueAt(isvc);
- if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
- + " service " + otherSvc.mName);
- ServiceState thisSvc = getServiceStateLocked(pkgName, uid,
- otherSvc.mProcessName, otherSvc.mName);
- thisSvc.add(otherSvc);
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ ServiceState otherSvc = otherState.mServices.valueAt(isvc);
+ if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
+ + " service " + otherSvc.mName);
+ ServiceState thisSvc = getServiceStateLocked(pkgName, uid, vers,
+ otherSvc.mProcessName, otherSvc.mName);
+ thisSvc.add(otherSvc);
+ }
}
}
}
@@ -275,9 +281,11 @@
if (DEBUG) Slog.d(TAG, "Adding uid " + uid + " proc " + otherProc.mName);
if (thisProc == null) {
if (DEBUG) Slog.d(TAG, "Creating new process!");
- thisProc = new ProcessState(this, otherProc.mPackage, uid, otherProc.mName);
+ thisProc = new ProcessState(this, otherProc.mPackage, uid, otherProc.mVersion,
+ otherProc.mName);
mProcesses.put(otherProc.mName, uid, thisProc);
- PackageState thisState = getPackageStateLocked(otherProc.mPackage, uid);
+ PackageState thisState = getPackageStateLocked(otherProc.mPackage, uid,
+ otherProc.mVersion);
if (!thisState.mProcesses.containsKey(otherProc.mName)) {
thisState.mProcesses.put(otherProc.mName, thisProc);
}
@@ -440,7 +448,7 @@
}
static void dumpServiceTimeCheckin(PrintWriter pw, String label, String packageName,
- int uid, String serviceName, ServiceState svc, int serviceType, int opCount,
+ int uid, int vers, String serviceName, ServiceState svc, int serviceType, int opCount,
int curState, long curStartTime, long now) {
if (opCount <= 0) {
return;
@@ -451,6 +459,8 @@
pw.print(",");
pw.print(uid);
pw.print(",");
+ pw.print(vers);
+ pw.print(",");
pw.print(serviceName);
pw.print(",");
pw.print(opCount);
@@ -775,7 +785,7 @@
static void dumpProcessSummaryLocked(PrintWriter pw, String prefix,
ArrayList<ProcessState> procs, int[] screenStates, int[] memStates, int[] procStates,
- long now, long totalTime) {
+ boolean inclUidVers, long now, long totalTime) {
for (int i=procs.size()-1; i>=0; i--) {
ProcessState proc = procs.get(i);
pw.print(prefix);
@@ -783,6 +793,8 @@
pw.print(proc.mName);
pw.print(" / ");
UserHandle.formatUid(pw, proc.mUid);
+ pw.print(" / v");
+ pw.print(proc.mVersion);
pw.println(":");
dumpProcessSummaryDetails(pw, proc, prefix, " TOTAL: ", screenStates, memStates,
procStates, now, totalTime, true);
@@ -869,6 +881,8 @@
pw.print("process");
pw.print(CSV_SEP);
pw.print("uid");
+ pw.print(CSV_SEP);
+ pw.print("vers");
dumpStateHeadersCsv(pw, CSV_SEP, sepScreenStates ? screenStates : null,
sepMemStates ? memStates : null,
sepProcStates ? procStates : null);
@@ -878,6 +892,8 @@
pw.print(proc.mName);
pw.print(CSV_SEP);
UserHandle.formatUid(pw, proc.mUid);
+ pw.print(CSV_SEP);
+ pw.print(proc.mVersion);
dumpProcessStateCsv(pw, proc, sepScreenStates, screenStates,
sepMemStates, memStates, sepProcStates, procStates, now);
pw.println();
@@ -979,46 +995,50 @@
public void resetSafely() {
if (DEBUG) Slog.d(TAG, "Safely resetting state of " + mTimePeriodStartClockStr);
resetCommon();
- long now = SystemClock.uptimeMillis();
- ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+
+ // First initialize use count of all common processes.
+ final long now = SystemClock.uptimeMillis();
+ final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
for (int ip=procMap.size()-1; ip>=0; ip--) {
- SparseArray<ProcessState> uids = procMap.valueAt(ip);
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
for (int iu=uids.size()-1; iu>=0; iu--) {
- ProcessState ps = uids.valueAt(iu);
- if (ps.isInUse()) {
- uids.valueAt(iu).resetSafely(now);
- } else {
- uids.valueAt(iu).makeDead();
- uids.removeAt(iu);
- }
- }
- if (uids.size() <= 0) {
- procMap.removeAt(ip);
- }
+ uids.valueAt(iu).mTmpNumInUse = 0;
+ }
}
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+
+ // Next reset or prune all per-package processes, and for the ones that are reset
+ // track this back to the common processes.
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
for (int ip=pkgMap.size()-1; ip>=0; ip--) {
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
for (int iu=uids.size()-1; iu>=0; iu--) {
- PackageState pkgState = uids.valueAt(iu);
- for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) {
- ProcessState ps = pkgState.mProcesses.valueAt(iproc);
- if (ps.isInUse() || ps.mCommonProcess.isInUse()) {
- pkgState.mProcesses.valueAt(iproc).resetSafely(now);
- } else {
- pkgState.mProcesses.valueAt(iproc).makeDead();
- pkgState.mProcesses.removeAt(iproc);
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ for (int iv=vpkgs.size()-1; iv>=0; iv--) {
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) {
+ final ProcessState ps = pkgState.mProcesses.valueAt(iproc);
+ if (ps.isInUse()) {
+ pkgState.mProcesses.valueAt(iproc).resetSafely(now);
+ ps.mCommonProcess.mTmpNumInUse++;
+ ps.mCommonProcess.mTmpFoundSubProc = ps;
+ } else {
+ pkgState.mProcesses.valueAt(iproc).makeDead();
+ pkgState.mProcesses.removeAt(iproc);
+ }
+ }
+ for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) {
+ final ServiceState ss = pkgState.mServices.valueAt(isvc);
+ if (ss.isInUse()) {
+ pkgState.mServices.valueAt(isvc).resetSafely(now);
+ } else {
+ pkgState.mServices.removeAt(isvc);
+ }
+ }
+ if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) {
+ vpkgs.removeAt(iv);
}
}
- for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) {
- ServiceState ss = pkgState.mServices.valueAt(isvc);
- if (ss.isInUse()) {
- pkgState.mServices.valueAt(isvc).resetSafely(now);
- } else {
- pkgState.mServices.removeAt(isvc);
- }
- }
- if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) {
+ if (vpkgs.size() <= 0) {
uids.removeAt(iu);
}
}
@@ -1026,6 +1046,37 @@
pkgMap.removeAt(ip);
}
}
+
+ // Finally prune out any common processes that are no longer in use.
+ for (int ip=procMap.size()-1; ip>=0; ip--) {
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
+ for (int iu=uids.size()-1; iu>=0; iu--) {
+ ProcessState ps = uids.valueAt(iu);
+ if (ps.isInUse() || ps.mTmpNumInUse > 0) {
+ // If this is a process for multiple packages, we could at this point
+ // be back down to one package. In that case, we want to revert back
+ // to a single shared ProcessState. We can do this by converting the
+ // current package-specific ProcessState up to the shared ProcessState,
+ // throwing away the current one we have here (because nobody else is
+ // using it).
+ if (!ps.mActive && ps.mMultiPackage && ps.mTmpNumInUse == 1) {
+ // Here we go...
+ ps = ps.mTmpFoundSubProc;
+ ps.mCommonProcess = ps;
+ uids.setValueAt(iu, ps);
+ } else {
+ ps.resetSafely(now);
+ }
+ } else {
+ ps.makeDead();
+ uids.removeAt(iu);
+ }
+ }
+ if (uids.size() <= 0) {
+ procMap.removeAt(ip);
+ }
+ }
+
mStartTime = now;
if (DEBUG) Slog.d(TAG, "State reset; now " + mTimePeriodStartClockStr);
}
@@ -1193,23 +1244,27 @@
uids.valueAt(iu).commitStateTime(now);
}
}
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
final int NPKG = pkgMap.size();
for (int ip=0; ip<NPKG; ip++) {
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
final int NUID = uids.size();
for (int iu=0; iu<NUID; iu++) {
- PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (proc.mCommonProcess != proc) {
- proc.commitStateTime(now);
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ final int NVERS = vpkgs.size();
+ for (int iv=0; iv<NVERS; iv++) {
+ PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (proc.mCommonProcess != proc) {
+ proc.commitStateTime(now);
+ }
}
- }
- final int NSRVS = pkgState.mServices.size();
- for (int isvc=0; isvc<NSRVS; isvc++) {
- pkgState.mServices.valueAt(isvc).commitStateTime(now);
+ final int NSRVS = pkgState.mServices.size();
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ pkgState.mServices.valueAt(isvc).commitStateTime(now);
+ }
}
}
}
@@ -1239,46 +1294,53 @@
out.writeInt(NPROC);
for (int ip=0; ip<NPROC; ip++) {
writeCommonString(out, procMap.keyAt(ip));
- SparseArray<ProcessState> uids = procMap.valueAt(ip);
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
final int NUID = uids.size();
out.writeInt(NUID);
for (int iu=0; iu<NUID; iu++) {
out.writeInt(uids.keyAt(iu));
- ProcessState proc = uids.valueAt(iu);
+ final ProcessState proc = uids.valueAt(iu);
writeCommonString(out, proc.mPackage);
+ out.writeInt(proc.mVersion);
proc.writeToParcel(out, now);
}
}
out.writeInt(NPKG);
for (int ip=0; ip<NPKG; ip++) {
writeCommonString(out, pkgMap.keyAt(ip));
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
final int NUID = uids.size();
out.writeInt(NUID);
for (int iu=0; iu<NUID; iu++) {
out.writeInt(uids.keyAt(iu));
- PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- out.writeInt(NPROCS);
- for (int iproc=0; iproc<NPROCS; iproc++) {
- writeCommonString(out, pkgState.mProcesses.keyAt(iproc));
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (proc.mCommonProcess == proc) {
- // This is the same as the common process we wrote above.
- out.writeInt(0);
- } else {
- // There is separate data for this package's process.
- out.writeInt(1);
- proc.writeToParcel(out, now);
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ final int NVERS = vpkgs.size();
+ out.writeInt(NVERS);
+ for (int iv=0; iv<NVERS; iv++) {
+ out.writeInt(vpkgs.keyAt(iv));
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ out.writeInt(NPROCS);
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ writeCommonString(out, pkgState.mProcesses.keyAt(iproc));
+ final ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (proc.mCommonProcess == proc) {
+ // This is the same as the common process we wrote above.
+ out.writeInt(0);
+ } else {
+ // There is separate data for this package's process.
+ out.writeInt(1);
+ proc.writeToParcel(out, now);
+ }
}
- }
- final int NSRVS = pkgState.mServices.size();
- out.writeInt(NSRVS);
- for (int isvc=0; isvc<NSRVS; isvc++) {
- out.writeString(pkgState.mServices.keyAt(isvc));
- ServiceState svc = pkgState.mServices.valueAt(isvc);
- writeCommonString(out, svc.mProcessName);
- svc.writeToParcel(out, now);
+ final int NSRVS = pkgState.mServices.size();
+ out.writeInt(NSRVS);
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ out.writeString(pkgState.mServices.keyAt(isvc));
+ final ServiceState svc = pkgState.mServices.valueAt(isvc);
+ writeCommonString(out, svc.mProcessName);
+ svc.writeToParcel(out, now);
+ }
}
}
}
@@ -1396,7 +1458,7 @@
}
while (NPROC > 0) {
NPROC--;
- String procName = readCommonString(in, version);
+ final String procName = readCommonString(in, version);
if (procName == null) {
mReadError = "bad process name";
return;
@@ -1408,23 +1470,24 @@
}
while (NUID > 0) {
NUID--;
- int uid = in.readInt();
+ final int uid = in.readInt();
if (uid < 0) {
mReadError = "bad uid: " + uid;
return;
}
- String pkgName = readCommonString(in, version);
+ final String pkgName = readCommonString(in, version);
if (pkgName == null) {
mReadError = "bad process package name";
return;
}
+ final int vers = in.readInt();
ProcessState proc = hadData ? mProcesses.get(procName, uid) : null;
if (proc != null) {
if (!proc.readFromParcel(in, false)) {
return;
}
} else {
- proc = new ProcessState(this, pkgName, uid, procName);
+ proc = new ProcessState(this, pkgName, uid, vers, procName);
if (!proc.readFromParcel(in, true)) {
return;
}
@@ -1444,7 +1507,7 @@
}
while (NPKG > 0) {
NPKG--;
- String pkgName = readCommonString(in, version);
+ final String pkgName = readCommonString(in, version);
if (pkgName == null) {
mReadError = "bad package name";
return;
@@ -1456,83 +1519,98 @@
}
while (NUID > 0) {
NUID--;
- int uid = in.readInt();
+ final int uid = in.readInt();
if (uid < 0) {
mReadError = "bad uid: " + uid;
return;
}
- PackageState pkgState = new PackageState(pkgName, uid);
- mPackages.put(pkgName, uid, pkgState);
- int NPROCS = in.readInt();
- if (NPROCS < 0) {
- mReadError = "bad package process count: " + NPROCS;
+ int NVERS = in.readInt();
+ if (NVERS < 0) {
+ mReadError = "bad versions count: " + NVERS;
return;
}
- while (NPROCS > 0) {
- NPROCS--;
- String procName = readCommonString(in, version);
- if (procName == null) {
- mReadError = "bad package process name";
+ while (NVERS > 0) {
+ NVERS--;
+ final int vers = in.readInt();
+ PackageState pkgState = new PackageState(pkgName, uid);
+ SparseArray<PackageState> vpkg = mPackages.get(pkgName, uid);
+ if (vpkg == null) {
+ vpkg = new SparseArray<PackageState>();
+ mPackages.put(pkgName, uid, vpkg);
+ }
+ vpkg.put(vers, pkgState);
+ int NPROCS = in.readInt();
+ if (NPROCS < 0) {
+ mReadError = "bad package process count: " + NPROCS;
return;
}
- int hasProc = in.readInt();
- if (DEBUG_PARCEL) Slog.d(TAG, "Reading package " + pkgName + " " + uid
- + " process " + procName + " hasProc=" + hasProc);
- ProcessState commonProc = mProcesses.get(procName, uid);
- if (DEBUG_PARCEL) Slog.d(TAG, "Got common proc " + procName + " " + uid
- + ": " + commonProc);
- if (commonProc == null) {
- mReadError = "no common proc: " + procName;
- return;
- }
- if (hasProc != 0) {
- // The process for this package is unique to the package; we
- // need to load it. We don't need to do anything about it if
- // it is not unique because if someone later looks for it
- // they will find and use it from the global procs.
- ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null;
- if (proc != null) {
- if (!proc.readFromParcel(in, false)) {
- return;
- }
- } else {
- proc = new ProcessState(commonProc, pkgName, uid, procName, 0);
- if (!proc.readFromParcel(in, true)) {
- return;
- }
+ while (NPROCS > 0) {
+ NPROCS--;
+ String procName = readCommonString(in, version);
+ if (procName == null) {
+ mReadError = "bad package process name";
+ return;
}
- if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
- + procName + " " + uid + " " + proc);
- pkgState.mProcesses.put(procName, proc);
- } else {
- if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
- + procName + " " + uid + " " + commonProc);
- pkgState.mProcesses.put(procName, commonProc);
+ int hasProc = in.readInt();
+ if (DEBUG_PARCEL) Slog.d(TAG, "Reading package " + pkgName + " " + uid
+ + " process " + procName + " hasProc=" + hasProc);
+ ProcessState commonProc = mProcesses.get(procName, uid);
+ if (DEBUG_PARCEL) Slog.d(TAG, "Got common proc " + procName + " " + uid
+ + ": " + commonProc);
+ if (commonProc == null) {
+ mReadError = "no common proc: " + procName;
+ return;
+ }
+ if (hasProc != 0) {
+ // The process for this package is unique to the package; we
+ // need to load it. We don't need to do anything about it if
+ // it is not unique because if someone later looks for it
+ // they will find and use it from the global procs.
+ ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null;
+ if (proc != null) {
+ if (!proc.readFromParcel(in, false)) {
+ return;
+ }
+ } else {
+ proc = new ProcessState(commonProc, pkgName, uid, vers, procName,
+ 0);
+ if (!proc.readFromParcel(in, true)) {
+ return;
+ }
+ }
+ if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
+ + procName + " " + uid + " " + proc);
+ pkgState.mProcesses.put(procName, proc);
+ } else {
+ if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
+ + procName + " " + uid + " " + commonProc);
+ pkgState.mProcesses.put(procName, commonProc);
+ }
}
- }
- int NSRVS = in.readInt();
- if (NSRVS < 0) {
- mReadError = "bad package service count: " + NSRVS;
- return;
- }
- while (NSRVS > 0) {
- NSRVS--;
- String serviceName = in.readString();
- if (serviceName == null) {
- mReadError = "bad package service name";
+ int NSRVS = in.readInt();
+ if (NSRVS < 0) {
+ mReadError = "bad package service count: " + NSRVS;
return;
}
- String processName = version > 9 ? readCommonString(in, version) : null;
- ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null;
- if (serv == null) {
- serv = new ServiceState(this, pkgName, serviceName, processName, null);
+ while (NSRVS > 0) {
+ NSRVS--;
+ String serviceName = in.readString();
+ if (serviceName == null) {
+ mReadError = "bad package service name";
+ return;
+ }
+ String processName = version > 9 ? readCommonString(in, version) : null;
+ ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null;
+ if (serv == null) {
+ serv = new ServiceState(this, pkgName, serviceName, processName, null);
+ }
+ if (!serv.readFromParcel(in)) {
+ return;
+ }
+ if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " service: "
+ + serviceName + " " + uid + " " + serv);
+ pkgState.mServices.put(serviceName, serv);
}
- if (!serv.readFromParcel(in)) {
- return;
- }
- if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " service: "
- + serviceName + " " + uid + " " + serv);
- pkgState.mServices.put(serviceName, serv);
}
}
}
@@ -1627,30 +1705,36 @@
return ~lo; // value not present
}
- public PackageState getPackageStateLocked(String packageName, int uid) {
- PackageState as = mPackages.get(packageName, uid);
+ public PackageState getPackageStateLocked(String packageName, int uid, int vers) {
+ SparseArray<PackageState> vpkg = mPackages.get(packageName, uid);
+ if (vpkg == null) {
+ vpkg = new SparseArray<PackageState>();
+ mPackages.put(packageName, uid, vpkg);
+ }
+ PackageState as = vpkg.get(vers);
if (as != null) {
return as;
}
as = new PackageState(packageName, uid);
- mPackages.put(packageName, uid, as);
+ vpkg.put(vers, as);
return as;
}
- public ProcessState getProcessStateLocked(String packageName, int uid, String processName) {
- final PackageState pkgState = getPackageStateLocked(packageName, uid);
+ public ProcessState getProcessStateLocked(String packageName, int uid, int vers,
+ String processName) {
+ final PackageState pkgState = getPackageStateLocked(packageName, uid, vers);
ProcessState ps = pkgState.mProcesses.get(processName);
if (ps != null) {
return ps;
}
ProcessState commonProc = mProcesses.get(processName, uid);
if (commonProc == null) {
- commonProc = new ProcessState(this, packageName, uid, processName);
+ commonProc = new ProcessState(this, packageName, uid, vers, processName);
mProcesses.put(processName, uid, commonProc);
if (DEBUG) Slog.d(TAG, "GETPROC created new common " + commonProc);
}
if (!commonProc.mMultiPackage) {
- if (packageName.equals(commonProc.mPackage)) {
+ if (packageName.equals(commonProc.mPackage) && vers == commonProc.mVersion) {
// This common process is not in use by multiple packages, and
// is for the calling package, so we can just use it directly.
ps = commonProc;
@@ -1668,7 +1752,8 @@
long now = SystemClock.uptimeMillis();
// First let's make a copy of the current process state and put
// that under the now unique state for its original package name.
- final PackageState commonPkgState = getPackageStateLocked(commonProc.mPackage, uid);
+ final PackageState commonPkgState = getPackageStateLocked(commonProc.mPackage,
+ uid, commonProc.mVersion);
if (commonPkgState != null) {
ProcessState cloned = commonProc.clone(commonProc.mPackage, now);
if (DEBUG) Slog.d(TAG, "GETPROC setting clone to pkg " + commonProc.mPackage
@@ -1691,13 +1776,13 @@
+ "/" + uid + " for proc " + commonProc.mName);
}
// And now make a fresh new process state for the new package name.
- ps = new ProcessState(commonProc, packageName, uid, processName, now);
+ ps = new ProcessState(commonProc, packageName, uid, vers, processName, now);
if (DEBUG) Slog.d(TAG, "GETPROC created new pkg " + ps);
}
} else {
// The common process is for multiple packages, we need to create a
// separate object for the per-package data.
- ps = new ProcessState(commonProc, packageName, uid, processName,
+ ps = new ProcessState(commonProc, packageName, uid, vers, processName,
SystemClock.uptimeMillis());
if (DEBUG) Slog.d(TAG, "GETPROC created new pkg " + ps);
}
@@ -1706,16 +1791,16 @@
return ps;
}
- public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid,
+ public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid, int vers,
String processName, String className) {
- final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid);
+ final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid, vers);
ProcessStats.ServiceState ss = as.mServices.get(className);
if (ss != null) {
if (DEBUG) Slog.d(TAG, "GETSVC: returning existing " + ss);
return ss;
}
final ProcessStats.ProcessState ps = processName != null
- ? getProcessStateLocked(packageName, uid, processName) : null;
+ ? getProcessStateLocked(packageName, uid, vers, processName) : null;
ss = new ProcessStats.ServiceState(this, packageName, className, processName, ps);
as.mServices.put(className, ss);
if (DEBUG) Slog.d(TAG, "GETSVC: creating " + ss + " in " + ps);
@@ -1756,119 +1841,124 @@
boolean dumpAll, boolean activeOnly) {
long totalTime = dumpSingleTime(null, null, mMemFactorDurations, mMemFactor,
mStartTime, now);
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+ ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
boolean printedHeader = false;
boolean sepNeeded = false;
for (int ip=0; ip<pkgMap.size(); ip++) {
final String pkgName = pkgMap.keyAt(ip);
- final SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
for (int iu=0; iu<uids.size(); iu++) {
final int uid = uids.keyAt(iu);
- final PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- final int NSRVS = pkgState.mServices.size();
- final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
- if (!pkgMatch) {
- boolean procMatch = false;
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (reqPackage.equals(proc.mName)) {
- procMatch = true;
- break;
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ for (int iv=0; iv<vpkgs.size(); iv++) {
+ final int vers = vpkgs.keyAt(iv);
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ final int NSRVS = pkgState.mServices.size();
+ final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
+ if (!pkgMatch) {
+ boolean procMatch = false;
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (reqPackage.equals(proc.mName)) {
+ procMatch = true;
+ break;
+ }
}
- }
- if (!procMatch) {
- continue;
- }
- }
- if (NPROCS > 0 || NSRVS > 0) {
- if (!printedHeader) {
- pw.println("Per-Package Stats:");
- printedHeader = true;
- sepNeeded = true;
- }
- pw.print(" * "); pw.print(pkgName); pw.print(" / ");
- UserHandle.formatUid(pw, uid); pw.println(":");
- }
- if (!dumpSummary || dumpAll) {
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ if (!procMatch) {
continue;
}
- if (activeOnly && !proc.isInUse()) {
- pw.print(" (Not active: ");
- pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")");
- continue;
- }
- pw.print(" Process ");
- pw.print(pkgState.mProcesses.keyAt(iproc));
- if (proc.mCommonProcess.mMultiPackage) {
- pw.print(" (multi, ");
- } else {
- pw.print(" (unique, ");
- }
- pw.print(proc.mDurationsTableSize);
- pw.print(" entries)");
- pw.println(":");
- dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
- ALL_PROC_STATES, now);
- dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
- ALL_PROC_STATES);
- dumpProcessInternalLocked(pw, " ", proc, dumpAll);
}
- } else {
- ArrayList<ProcessState> procs = new ArrayList<ProcessState>();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (!pkgMatch && !reqPackage.equals(proc.mName)) {
- continue;
+ if (NPROCS > 0 || NSRVS > 0) {
+ if (!printedHeader) {
+ pw.println("Per-Package Stats:");
+ printedHeader = true;
+ sepNeeded = true;
}
- if (activeOnly && !proc.isInUse()) {
- continue;
+ pw.print(" * "); pw.print(pkgName); pw.print(" / ");
+ UserHandle.formatUid(pw, uid); pw.print(" / v");
+ pw.print(vers); pw.println(":");
+ }
+ if (!dumpSummary || dumpAll) {
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ continue;
+ }
+ if (activeOnly && !proc.isInUse()) {
+ pw.print(" (Not active: ");
+ pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")");
+ continue;
+ }
+ pw.print(" Process ");
+ pw.print(pkgState.mProcesses.keyAt(iproc));
+ if (proc.mCommonProcess.mMultiPackage) {
+ pw.print(" (multi, ");
+ } else {
+ pw.print(" (unique, ");
+ }
+ pw.print(proc.mDurationsTableSize);
+ pw.print(" entries)");
+ pw.println(":");
+ dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+ ALL_PROC_STATES, now);
+ dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+ ALL_PROC_STATES);
+ dumpProcessInternalLocked(pw, " ", proc, dumpAll);
}
- procs.add(proc);
- }
- dumpProcessSummaryLocked(pw, " ", procs, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
- NON_CACHED_PROC_STATES, now, totalTime);
- }
- for (int isvc=0; isvc<NSRVS; isvc++) {
- ServiceState svc = pkgState.mServices.valueAt(isvc);
- if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) {
- continue;
- }
- if (activeOnly && !svc.isInUse()) {
- pw.print(" (Not active: ");
- pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")");
- continue;
- }
- if (dumpAll) {
- pw.print(" Service ");
} else {
- pw.print(" * ");
- }
- pw.print(pkgState.mServices.keyAt(isvc));
- pw.println(":");
- pw.print(" Process: "); pw.println(svc.mProcessName);
- dumpServiceStats(pw, " ", " ", " ", "Running", svc,
- svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState,
- svc.mRunStartTime, now, totalTime, !dumpSummary || dumpAll);
- dumpServiceStats(pw, " ", " ", " ", "Started", svc,
- svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState,
- svc.mStartedStartTime, now, totalTime, !dumpSummary || dumpAll);
- dumpServiceStats(pw, " ", " ", " ", "Bound", svc,
- svc.mBoundCount, ServiceState.SERVICE_BOUND, svc.mBoundState,
- svc.mBoundStartTime, now, totalTime, !dumpSummary || dumpAll);
- dumpServiceStats(pw, " ", " ", " ", "Executing", svc,
- svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState,
- svc.mExecStartTime, now, totalTime, !dumpSummary || dumpAll);
- if (dumpAll) {
- if (svc.mOwner != null) {
- pw.print(" mOwner="); pw.println(svc.mOwner);
+ ArrayList<ProcessState> procs = new ArrayList<ProcessState>();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ continue;
+ }
+ if (activeOnly && !proc.isInUse()) {
+ continue;
+ }
+ procs.add(proc);
}
- if (svc.mStarted || svc.mRestarting) {
- pw.print(" mStarted="); pw.print(svc.mStarted);
- pw.print(" mRestarting="); pw.println(svc.mRestarting);
+ dumpProcessSummaryLocked(pw, " ", procs, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+ NON_CACHED_PROC_STATES, false, now, totalTime);
+ }
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ ServiceState svc = pkgState.mServices.valueAt(isvc);
+ if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) {
+ continue;
+ }
+ if (activeOnly && !svc.isInUse()) {
+ pw.print(" (Not active: ");
+ pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")");
+ continue;
+ }
+ if (dumpAll) {
+ pw.print(" Service ");
+ } else {
+ pw.print(" * ");
+ }
+ pw.print(pkgState.mServices.keyAt(isvc));
+ pw.println(":");
+ pw.print(" Process: "); pw.println(svc.mProcessName);
+ dumpServiceStats(pw, " ", " ", " ", "Running", svc,
+ svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState,
+ svc.mRunStartTime, now, totalTime, !dumpSummary || dumpAll);
+ dumpServiceStats(pw, " ", " ", " ", "Started", svc,
+ svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState,
+ svc.mStartedStartTime, now, totalTime, !dumpSummary || dumpAll);
+ dumpServiceStats(pw, " ", " ", " ", "Bound", svc,
+ svc.mBoundCount, ServiceState.SERVICE_BOUND, svc.mBoundState,
+ svc.mBoundStartTime, now, totalTime, !dumpSummary || dumpAll);
+ dumpServiceStats(pw, " ", " ", " ", "Executing", svc,
+ svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState,
+ svc.mExecStartTime, now, totalTime, !dumpSummary || dumpAll);
+ if (dumpAll) {
+ if (svc.mOwner != null) {
+ pw.print(" mOwner="); pw.println(svc.mOwner);
+ }
+ if (svc.mStarted || svc.mRestarting) {
+ pw.print(" mStarted="); pw.print(svc.mStarted);
+ pw.print(" mRestarting="); pw.println(svc.mRestarting);
+ }
}
}
}
@@ -2059,7 +2149,7 @@
pw.println(header);
}
dumpProcessSummaryLocked(pw, prefix, procs, screenStates, memStates,
- sortProcStates, now, totalTime);
+ sortProcStates, true, now, totalTime);
}
}
@@ -2067,23 +2157,27 @@
int[] procStates, int sortProcStates[], long now, String reqPackage,
boolean activeOnly) {
final ArraySet<ProcessState> foundProcs = new ArraySet<ProcessState>();
- final ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
for (int ip=0; ip<pkgMap.size(); ip++) {
final String pkgName = pkgMap.keyAt(ip);
- final SparseArray<PackageState> procs = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> procs = pkgMap.valueAt(ip);
for (int iu=0; iu<procs.size(); iu++) {
- final PackageState state = procs.valueAt(iu);
- final int NPROCS = state.mProcesses.size();
- final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
- for (int iproc=0; iproc<NPROCS; iproc++) {
- final ProcessState proc = state.mProcesses.valueAt(iproc);
- if (!pkgMatch && !reqPackage.equals(proc.mName)) {
- continue;
+ final SparseArray<PackageState> vpkgs = procs.valueAt(iu);
+ final int NVERS = vpkgs.size();
+ for (int iv=0; iv<NVERS; iv++) {
+ final PackageState state = vpkgs.valueAt(iv);
+ final int NPROCS = state.mProcesses.size();
+ final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ final ProcessState proc = state.mProcesses.valueAt(iproc);
+ if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ continue;
+ }
+ if (activeOnly && !proc.isInUse()) {
+ continue;
+ }
+ foundProcs.add(proc.mCommonProcess);
}
- if (activeOnly && !proc.isInUse()) {
- continue;
- }
- foundProcs.add(proc.mCommonProcess);
}
}
}
@@ -2128,8 +2222,8 @@
public void dumpCheckinLocked(PrintWriter pw, String reqPackage) {
final long now = SystemClock.uptimeMillis();
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
- pw.println("vers,3");
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
+ pw.println("vers,4");
pw.print("period,"); pw.print(mTimePeriodStartClockStr);
pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(",");
pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
@@ -2152,75 +2246,85 @@
pw.println();
pw.print("config,"); pw.print(mRuntime); pw.print(','); pw.println(mWebView);
for (int ip=0; ip<pkgMap.size(); ip++) {
- String pkgName = pkgMap.keyAt(ip);
+ final String pkgName = pkgMap.keyAt(ip);
if (reqPackage != null && !reqPackage.equals(pkgName)) {
continue;
}
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
for (int iu=0; iu<uids.size(); iu++) {
- int uid = uids.keyAt(iu);
- PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- final int NSRVS = pkgState.mServices.size();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- pw.print("pkgproc,");
- pw.print(pkgName);
- pw.print(",");
- pw.print(uid);
- pw.print(",");
- pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
- dumpAllProcessStateCheckin(pw, proc, now);
- pw.println();
- if (proc.mPssTableSize > 0) {
- pw.print("pkgpss,");
+ final int uid = uids.keyAt(iu);
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ for (int iv=0; iv<vpkgs.size(); iv++) {
+ final int vers = vpkgs.keyAt(iv);
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ final int NSRVS = pkgState.mServices.size();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ pw.print("pkgproc,");
pw.print(pkgName);
pw.print(",");
pw.print(uid);
pw.print(",");
- pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
- dumpAllProcessPssCheckin(pw, proc);
- pw.println();
- }
- if (proc.mNumExcessiveWake > 0 || proc.mNumExcessiveCpu > 0
- || proc.mNumCachedKill > 0) {
- pw.print("pkgkills,");
- pw.print(pkgName);
- pw.print(",");
- pw.print(uid);
+ pw.print(vers);
pw.print(",");
pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
- pw.print(",");
- pw.print(proc.mNumExcessiveWake);
- pw.print(",");
- pw.print(proc.mNumExcessiveCpu);
- pw.print(",");
- pw.print(proc.mNumCachedKill);
- pw.print(",");
- pw.print(proc.mMinCachedKillPss);
- pw.print(":");
- pw.print(proc.mAvgCachedKillPss);
- pw.print(":");
- pw.print(proc.mMaxCachedKillPss);
+ dumpAllProcessStateCheckin(pw, proc, now);
pw.println();
+ if (proc.mPssTableSize > 0) {
+ pw.print("pkgpss,");
+ pw.print(pkgName);
+ pw.print(",");
+ pw.print(uid);
+ pw.print(",");
+ pw.print(vers);
+ pw.print(",");
+ pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
+ dumpAllProcessPssCheckin(pw, proc);
+ pw.println();
+ }
+ if (proc.mNumExcessiveWake > 0 || proc.mNumExcessiveCpu > 0
+ || proc.mNumCachedKill > 0) {
+ pw.print("pkgkills,");
+ pw.print(pkgName);
+ pw.print(",");
+ pw.print(uid);
+ pw.print(",");
+ pw.print(vers);
+ pw.print(",");
+ pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
+ pw.print(",");
+ pw.print(proc.mNumExcessiveWake);
+ pw.print(",");
+ pw.print(proc.mNumExcessiveCpu);
+ pw.print(",");
+ pw.print(proc.mNumCachedKill);
+ pw.print(",");
+ pw.print(proc.mMinCachedKillPss);
+ pw.print(":");
+ pw.print(proc.mAvgCachedKillPss);
+ pw.print(":");
+ pw.print(proc.mMaxCachedKillPss);
+ pw.println();
+ }
}
- }
- for (int isvc=0; isvc<NSRVS; isvc++) {
- String serviceName = collapseString(pkgName,
- pkgState.mServices.keyAt(isvc));
- ServiceState svc = pkgState.mServices.valueAt(isvc);
- dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_RUN, svc.mRunCount,
- svc.mRunState, svc.mRunStartTime, now);
- dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_STARTED, svc.mStartedCount,
- svc.mStartedState, svc.mStartedStartTime, now);
- dumpServiceTimeCheckin(pw, "pkgsvc-bound", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_BOUND, svc.mBoundCount,
- svc.mBoundState, svc.mBoundStartTime, now);
- dumpServiceTimeCheckin(pw, "pkgsvc-exec", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_EXEC, svc.mExecCount,
- svc.mExecState, svc.mExecStartTime, now);
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ String serviceName = collapseString(pkgName,
+ pkgState.mServices.keyAt(isvc));
+ ServiceState svc = pkgState.mServices.valueAt(isvc);
+ dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_RUN, svc.mRunCount,
+ svc.mRunState, svc.mRunStartTime, now);
+ dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_STARTED, svc.mStartedCount,
+ svc.mStartedState, svc.mStartedStartTime, now);
+ dumpServiceTimeCheckin(pw, "pkgsvc-bound", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_BOUND, svc.mBoundCount,
+ svc.mBoundState, svc.mBoundStartTime, now);
+ dumpServiceTimeCheckin(pw, "pkgsvc-exec", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_EXEC, svc.mExecCount,
+ svc.mExecState, svc.mExecStartTime, now);
+ }
}
}
}
@@ -2364,9 +2468,10 @@
}
public static final class ProcessState extends DurationsTable {
- public final ProcessState mCommonProcess;
+ public ProcessState mCommonProcess;
public final String mPackage;
public final int mUid;
+ public final int mVersion;
//final long[] mDurations = new long[STATE_COUNT*ADJ_COUNT];
int mCurState = STATE_NOTHING;
@@ -2393,16 +2498,19 @@
boolean mDead;
public long mTmpTotalTime;
+ int mTmpNumInUse;
+ ProcessState mTmpFoundSubProc;
/**
* Create a new top-level process state, for the initial case where there is only
* a single package running in a process. The initial state is not running.
*/
- public ProcessState(ProcessStats processStats, String pkg, int uid, String name) {
+ public ProcessState(ProcessStats processStats, String pkg, int uid, int vers, String name) {
super(processStats, name);
mCommonProcess = this;
mPackage = pkg;
mUid = uid;
+ mVersion = vers;
}
/**
@@ -2410,18 +2518,19 @@
* state. The current running state of the top-level process is also copied,
* marked as started running at 'now'.
*/
- public ProcessState(ProcessState commonProcess, String pkg, int uid, String name,
+ public ProcessState(ProcessState commonProcess, String pkg, int uid, int vers, String name,
long now) {
super(commonProcess.mStats, name);
mCommonProcess = commonProcess;
mPackage = pkg;
mUid = uid;
+ mVersion = vers;
mCurState = commonProcess.mCurState;
mStartTime = now;
}
ProcessState clone(String pkg, long now) {
- ProcessState pnew = new ProcessState(this, pkg, mUid, mName, now);
+ ProcessState pnew = new ProcessState(this, pkg, mUid, mVersion, mName, now);
copyDurationsTo(pnew);
if (mPssTable != null) {
mStats.mAddLongTable = new int[mPssTable.length];
@@ -2811,9 +2920,20 @@
// The array map is still pointing to a common process state
// that is now shared across packages. Update it to point to
// the new per-package state.
- ProcessState proc = mStats.mPackages.get(pkgName, mUid).mProcesses.get(mName);
+ SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgName, mUid);
+ if (vpkg == null) {
+ throw new IllegalStateException("Didn't find package " + pkgName
+ + " / " + mUid);
+ }
+ PackageState pkg = vpkg.get(mVersion);
+ if (pkg == null) {
+ throw new IllegalStateException("Didn't find package " + pkgName
+ + " / " + mUid + " vers " + mVersion);
+ }
+ ProcessState proc = pkg.mProcesses.get(mName);
if (proc == null) {
- throw new IllegalStateException("Didn't create per-package process");
+ throw new IllegalStateException("Didn't create per-package process "
+ + mName + " in pkg " + pkgName + " / " + mUid + " vers " + mVersion);
}
return proc;
}
@@ -2829,18 +2949,26 @@
// are losing whatever data we had in the old process state.
Log.wtf(TAG, "Pulling dead proc: name=" + mName + " pkg=" + mPackage
+ " uid=" + mUid + " common.name=" + mCommonProcess.mName);
- proc = mStats.getProcessStateLocked(proc.mPackage, proc.mUid, proc.mName);
+ proc = mStats.getProcessStateLocked(proc.mPackage, proc.mUid, proc.mVersion,
+ proc.mName);
}
if (proc.mMultiPackage) {
// The array map is still pointing to a common process state
// that is now shared across packages. Update it to point to
// the new per-package state.
- PackageState pkg = mStats.mPackages.get(pkgList.keyAt(index), proc.mUid);
- if (pkg == null) {
+ SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgList.keyAt(index),
+ proc.mUid);
+ if (vpkg == null) {
throw new IllegalStateException("No existing package "
+ pkgList.keyAt(index) + "/" + proc.mUid
+ " for multi-proc " + proc.mName);
}
+ PackageState pkg = vpkg.get(proc.mVersion);
+ if (pkg == null) {
+ throw new IllegalStateException("No existing package "
+ + pkgList.keyAt(index) + "/" + proc.mUid
+ + " for multi-proc " + proc.mName + " version " + proc.mVersion);
+ }
proc = pkg.mProcesses.get(proc.mName);
if (proc == null) {
throw new IllegalStateException("Didn't create per-package process "
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 3798d62..31ca3de 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -371,23 +371,25 @@
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
mAppearingPackages = pkgList;
- mChangeType = PACKAGE_TEMPORARY_CHANGE;
+ mChangeType = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
+ ? PACKAGE_UPDATING : PACKAGE_TEMPORARY_CHANGE;
mSomePackagesChanged = true;
if (pkgList != null) {
onPackagesAvailable(pkgList);
for (int i=0; i<pkgList.length; i++) {
- onPackageAppeared(pkgList[i], PACKAGE_TEMPORARY_CHANGE);
+ onPackageAppeared(pkgList[i], mChangeType);
}
}
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
mDisappearingPackages = pkgList;
- mChangeType = PACKAGE_TEMPORARY_CHANGE;
+ mChangeType = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
+ ? PACKAGE_UPDATING : PACKAGE_TEMPORARY_CHANGE;
mSomePackagesChanged = true;
if (pkgList != null) {
onPackagesUnavailable(pkgList);
for (int i=0; i<pkgList.length; i++) {
- onPackageDisappeared(pkgList[i], PACKAGE_TEMPORARY_CHANGE);
+ onPackageDisappeared(pkgList[i], mChangeType);
}
}
}
diff --git a/core/jni/android/graphics/Matrix.cpp b/core/jni/android/graphics/Matrix.cpp
index d0871ac5..4bd59e7 100644
--- a/core/jni/android/graphics/Matrix.cpp
+++ b/core/jni/android/graphics/Matrix.cpp
@@ -239,8 +239,8 @@
SkASSERT(dstIndex >= 0);
SkASSERT((unsigned)ptCount <= 4);
- AutoJavaFloatArray autoSrc(env, jsrc, srcIndex + (ptCount << 1));
- AutoJavaFloatArray autoDst(env, jdst, dstIndex + (ptCount << 1));
+ AutoJavaFloatArray autoSrc(env, jsrc, srcIndex + (ptCount << 1), kRO_JNIAccess);
+ AutoJavaFloatArray autoDst(env, jdst, dstIndex + (ptCount << 1), kRW_JNIAccess);
float* src = autoSrc.ptr() + srcIndex;
float* dst = autoDst.ptr() + dstIndex;
@@ -268,8 +268,8 @@
jfloatArray src, int srcIndex,
int ptCount, bool isPts) {
SkASSERT(ptCount >= 0);
- AutoJavaFloatArray autoSrc(env, src, srcIndex + (ptCount << 1));
- AutoJavaFloatArray autoDst(env, dst, dstIndex + (ptCount << 1));
+ AutoJavaFloatArray autoSrc(env, src, srcIndex + (ptCount << 1), kRO_JNIAccess);
+ AutoJavaFloatArray autoDst(env, dst, dstIndex + (ptCount << 1), kRW_JNIAccess);
float* srcArray = autoSrc.ptr() + srcIndex;
float* dstArray = autoDst.ptr() + dstIndex;
@@ -318,7 +318,7 @@
}
static void getValues(JNIEnv* env, jobject clazz, SkMatrix* matrix, jfloatArray values) {
- AutoJavaFloatArray autoValues(env, values, 9);
+ AutoJavaFloatArray autoValues(env, values, 9, kRW_JNIAccess);
float* dst = autoValues.ptr();
#ifdef SK_SCALAR_IS_FIXED
@@ -336,7 +336,7 @@
}
static void setValues(JNIEnv* env, jobject clazz, SkMatrix* matrix, jfloatArray values) {
- AutoJavaFloatArray autoValues(env, values, 9);
+ AutoJavaFloatArray autoValues(env, values, 9, kRO_JNIAccess);
const float* src = autoValues.ptr();
#ifdef SK_SCALAR_IS_FIXED
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 2c23f9d..8836918 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -229,7 +229,8 @@
}
Asset* a = cookie
- ? am->openNonAsset((void*)cookie, fileName8.c_str(), (Asset::AccessMode)mode)
+ ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(),
+ (Asset::AccessMode)mode)
: am->openNonAsset(fileName8.c_str(), (Asset::AccessMode)mode);
if (a == NULL) {
@@ -260,7 +261,7 @@
}
Asset* a = cookie
- ? am->openNonAsset((void*)cookie, fileName8.c_str(), Asset::ACCESS_RANDOM)
+ ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(), Asset::ACCESS_RANDOM)
: am->openNonAsset(fileName8.c_str(), Asset::ACCESS_RANDOM);
if (a == NULL) {
@@ -435,10 +436,10 @@
return 0;
}
- void* cookie;
+ int32_t cookie;
bool res = am->addAssetPath(String8(path8.c_str()), &cookie);
- return (res) ? (jint)cookie : 0;
+ return (res) ? static_cast<jint>(cookie) : 0;
}
static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject clazz)
@@ -800,7 +801,7 @@
if (am == NULL) {
return NULL;
}
- String8 name(am->getAssetPath((void*)cookie));
+ String8 name(am->getAssetPath(static_cast<int32_t>(cookie)));
if (name.length() == 0) {
jniThrowException(env, "java/lang/IndexOutOfBoundsException", "Empty cookie name");
return NULL;
@@ -1386,7 +1387,7 @@
}
Asset* a = cookie
- ? am->openNonAsset((void*)cookie, fileName8.c_str(), Asset::ACCESS_BUFFER)
+ ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(), Asset::ACCESS_BUFFER)
: am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER);
if (a == NULL) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 13f5e1c..2151d4c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1303,7 +1303,7 @@
<!-- @hide Allows an application to create/manage/remove stacks -->
<permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"
android:permissionGroup="android.permission-group.APP_INFO"
- android:protectionLevel="signature"
+ android:protectionLevel="signature|system"
android:label="@string/permlab_manageActivityStacks"
android:description="@string/permdesc_manageActivityStacks" />
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index e902218..25c6f99 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Ontsluit tans SIM-kaart…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Verkeerde PIN-kode."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Tik \'n PIN in wat 4 tot 8 syfers lank is."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK-kode moet 8 of meer syfers wees."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Voer weer die korrekte PUK-kode in. Herhaalde pogings sal die SIM permanent deaktiveer."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN-kodes stem nie ooreen nie"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Te veel patroonpogings"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 404a299..be8f713 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"ሲም ካርዱን በመክፈት ላይ…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"ትክክል ያልሆነ ፒን ኮድ።"</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"ከ4 እስከ 8 ቁጥሮች የያዘ ፒን ይተይቡ።"</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"የPUK ኮድ 8 ወይም ከዚያ በላይ ቁጥሮች ሊኖረው ይገባል።"</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"ትክክለኛውን የPUK ኮድ እንደገና ያስገቡ። ተደጋጋሚ ሙከራዎች ሲም ካርዱን እስከመጨረሻው ያሰናክሉታል።"</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"ፒን ኮዶች አይገጣጠሙም"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"በጣም ብዙ የስርዓተ ጥለት ሙከራዎች"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index bc91f44..a58d8b7 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"جارٍ إلغاء تأمين بطاقة SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"رمز PIN غير صحيح."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"اكتب رمز PIN المكون من 4 إلى 8 أرقام."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"يجب أن يتضمن رمز PUK 8 أرقام أو أكثر."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"أعد إدخال رمز PUK الصحيح. وستؤدي المحاولات المتكررة إلى تعطيل بطاقة SIM نهائيًا."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"لا يتطابق رمزا رمز PIN"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"محاولات النقش كثيرة جدًا"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 0d4977d..c28550c6 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM картата се отключва…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Неправилен ПИН код."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Въведете ПИН код с четири до осем цифри."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK кодът трябва да е с 8 или повече цифри."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Въведете отново правилния PUK код. Многократните опити ще деактивират за постоянно SIM картата."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"ПИН кодовете не съвпадат"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Опитите за фигурата са твърде много"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 1a68a5f..a1b4aed 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"S\'està desbloquejant la targeta SIM..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Codi PIN incorrecte."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Escriu un PIN que tingui de 4 a 8 números."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"El codi PUK ha de tenir 8 números o més."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Torna a introduir el codi PUK correcte. Els intents repetits faran que es desactivi la SIM de manera permanent."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Els codis PIN no coincideixen"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Massa intents incorrectes"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 850141b..906d352 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Odblokování SIM karty..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Nesprávný kód PIN."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Zadejte kód PIN o délce 4–8 číslic."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Minimální délka kódu PUK je 8 číslic."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Znovu zadejte správný kód PUK. Opakovanými pokusy SIM kartu trvale deaktivujete."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Kódy PIN se neshodují."</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Příliš mnoho pokusů o nakreslení gesta"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index f520165..4ab3d71 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM-kortet låses op…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Forkert pinkode."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Indtast en pinkode på mellem 4 og 8 tal."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK-koden skal være på 8 tal eller mere."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Indtast den korrekte PUK-kode. Gentagne forsøg vil permanent deaktivere SIM-kortet."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Pinkoderne stemmer ikke overens"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"For mange forsøg på at tegne mønstret korrekt"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 479e112..8e681f1 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM-Karte wird entsperrt…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Falscher PIN-Code"</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Geben Sie eine 4- bis 8-stellige PIN ein."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Der PUK-Code muss mindestens 8 Ziffern betragen."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Geben Sie den richtigen PUK-Code ein. Bei wiederholten Versuchen wird die SIM-Karte dauerhaft deaktiviert."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN-Codes stimmen nicht überein"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Zu viele Musterversuche"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index efc3c07..64d7233 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Ξεκλείδωμα κάρτας SIM..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Εσφαλμένος κωδικός PIN."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Πληκτρολογήστε έναν αριθμό PIN που να αποτελείται από 4 έως 8 αριθμούς."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Ο κωδικός PUK θα πρέπει να περιέχει τουλάχιστον 8 αριθμούς."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Εισαγάγετε ξανά τον κωδικό PUK. Οι επαναλαμβανόμενες προσπάθειες θα απενεργοποιήσουν οριστικά την κάρτα SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Δεν υπάρχει αντιστοιχία των κωδικών PIN"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Πάρα πολλές προσπάθειες μοτίβου"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 4cb4cdb..b75e169 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Unlocking SIM card…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Incorrect PIN code."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Type a PIN that is 4 to 8 numbers."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK code should be 8 numbers or more."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Re-enter the correct PUK code. Repeated attempts will permanently disable the SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN codes do not match"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Too many pattern attempts"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 4cb4cdb..b75e169 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Unlocking SIM card…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Incorrect PIN code."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Type a PIN that is 4 to 8 numbers."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK code should be 8 numbers or more."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Re-enter the correct PUK code. Repeated attempts will permanently disable the SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN codes do not match"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Too many pattern attempts"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index ed66f38..bb03dea 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Desbloqueando tarjeta SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Código PIN incorrecto"</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Escribe un PIN que tenga de cuatro a ocho números."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"El código PUK debe tener ocho números como mínimo."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Vuelve a ingresar el código PUK correcto. Si ingresas un código incorrecto varias veces, se inhabilitará la tarjeta SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Los códigos PIN no coinciden."</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Demasiados intentos incorrectos de ingresar el patrón"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 2f71718..16768ce 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Desbloqueando tarjeta SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Código PIN incorrecto"</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Introduce un código PIN con una longitud comprendida entre cuatro y ocho dígitos."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"El código PUK debe tener ocho números como mínimo."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Vuelve a introducir el código PUK correcto. Si introduces un código incorrecto varias veces, se inhabilitará la tarjeta SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Los códigos PIN no coinciden."</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Demasiados intentos incorrectos de crear el patrón"</string>
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index 53832ce..f79c905 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM-kaardi avamine ..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Vale PIN-kood."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Sisestage 4–8-numbriline PIN-kood."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK-koodi pikkus peab olema vähemalt 8 numbrit."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Sisestage uuesti õige PUK-kood. Korduvkatsete korral keelatakse SIM jäädavalt."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN-koodid ei ole vastavuses"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Liiga palju mustrikatseid"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 00bd48b..9c85499 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -704,7 +704,7 @@
<string name="policylab_encryptedStorage" msgid="8901326199909132915">"تنظیم رمزگذاری حافظه"</string>
<string name="policydesc_encryptedStorage" msgid="2637732115325316992">"باید اطلاعات ذخیره شده برنامه رمزگذاری شود."</string>
<string name="policylab_disableCamera" msgid="6395301023152297826">"غیر فعال کردن دوربین ها"</string>
- <string name="policydesc_disableCamera" msgid="2306349042834754597">"از استفاده از تمام دوربینهای دستگاه جلوگیری کنید."</string>
+ <string name="policydesc_disableCamera" msgid="2306349042834754597">"جلوگیری از استفاده از همه دوربینهای دستگاه."</string>
<string name="policylab_disableKeyguardFeatures" msgid="266329104542638802">"غیرفعال کردن ویژگیها در محافظ کلید"</string>
<string name="policydesc_disableKeyguardFeatures" msgid="3467082272186534614">"از استفاده از برخی ویژگیها در محافظ کلید جلوگیری شود."</string>
<string-array name="phoneTypes">
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"بازگشایی قفل سیم کارت..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"پین کد اشتباه است."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"یک پین ۴ تا ۸ رقمی را تایپ کنید."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"پین کد باید ۸ عدد یا بیشتر باشد."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"پین کد صحیح را دوباره وارد کنید. تلاشهای مکرر بهطور دائم سیم کارت را غیرفعال خواهد کرد."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"پین کدها منطبق نیستند"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"تلاشهای زیادی برای کشیدن الگو صورت گرفته است"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index eefe2f4..3509198 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM-kortin lukitusta poistetaan…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Virheellinen PIN-koodi."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Anna 4–8-numeroinen PIN-koodi."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK-koodissa tulee olla vähintään 8 numeroa."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Anna uudelleen oikea PUK-koodi. Jos teet liian monta yritystä, SIM-kortti poistetaan käytöstä pysyvästi."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN-koodit eivät täsmää"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Liikaa kuvionpiirtoyrityksiä"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 659ceca..46faca5 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Déblocage de la carte SIM en cours…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"NIP erroné."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Saisissez un NIP comprenant entre quatre et huit chiffres"</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Le code PUK doit contenir au moins 8 chiffres."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Veuillez saisir de nouveau le code PUK correct. Des tentatives répétées désactivent définitivement la carte SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Les codes PIN ne correspondent pas."</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Trop de tentatives."</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 0fa3334..349d4b8 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Déblocage de la carte SIM en cours…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Le code PIN est erroné."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Veuillez saisir un code PIN comprenant entre quatre et huit chiffres."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Le code PUK doit contenir au moins 8 chiffres."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Veuillez saisir de nouveau le code PUK correct. Des tentatives répétées désactivent définitivement la carte SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Les codes PIN ne correspondent pas."</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Trop de tentatives."</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index bc68c82..feeac91 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM कार्ड अनलॉक कर रहा है…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"गलत PIN कोड."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"ऐसा PIN लिखें, जो 4 से 8 अंकों का हो."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK कोड 8 या अधिक संख्या वाला होना चाहिए."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"सही PUK कोड पुन: डालें. बार-बार प्रयास करने से सिम स्थायी रूप से अक्षम हो जाएगी."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"पिन कोड का मिलान नहीं होता"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"बहुत अधिक आकार प्रयास"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 84b27bf..383d0e1 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Otključavanje SIM kartice…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Netočan PIN kôd."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Unesite PIN koji ima od 4 do 8 brojeva."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK kôd treba imati 8 brojeva ili više."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Ponovo unesite ispravan PUK kôd. Ponovljeni pokušaji trajno će onemogućiti SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN kodovi nisu jednaki"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Previše pokušaja iscrtavanja obrasca"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index a338c4d..712ffb1 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM kártya feloldása..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Helytelen PIN kód."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"4–8 számjegyű PIN kódot írjon be."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"A PUK kód legalább 8 számjegyből kell, hogy álljon."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Adja meg újra a helyes PUK kódot. Az ismételt próbálkozással véglegesen letiltja a SIM kártyát."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"A PIN kódok nem egyeznek."</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Túl sok mintarajzolási próbálkozás"</string>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index 78bdb2c..85efbf6 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Ապակողպում է SIM քարտը ..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Սխալ PIN ծածկագիր:"</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Մուտքագրեք PIN, որը 4-ից 8 թիվ է:"</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK կոդը պետք է լինի 8 կամ ավելի թիվ:"</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Վերամուտքագրեք ճիշտ PUK ծածկագիրը: Կրկնվող փորձերը ընդմիշտ կկասեցնեն SIM քարտը:"</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN ծածկագրերը չեն համընկնում"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Չափից շատ սխեմայի փորձեր"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 9c41712..0c42580 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Membuka kunci kartu SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Kode PIN salah."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Ketik PIN yang terdiri dari 4 sampai 8 angka."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Kode PUK harus terdiri dari 8 angka atau lebih."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Masukkan kembali kode PUK yang benar. Jika berulang kali gagal, SIM akan dinonaktifkan secara permanen."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Kode PIN tidak cocok"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Terlalu banyak upaya pola"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 47ba24b..d48c88a 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Sblocco scheda SIM..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Codice PIN errato."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Il PIN deve essere di 4-8 numeri."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Il codice PUK dovrebbe avere almeno otto numeri."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Inserisci di nuovo il codice PUK corretto. Ripetuti tentativi comportano la disattivazione definitiva della scheda SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"I codici PIN non corrispondono"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Troppi tentativi di inserimento della sequenza"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 6ffb7ed..a4ed51c 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"מבטל נעילה של כרטיס SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"קוד PIN שגוי."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"הקלד מספר PIN שאורכו 4 עד 8 ספרות."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"קוד PUK צריך להיות בן 8 ספרות או יותר."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"הזן מחדש את קוד PUK הנכון. ניסיונות חוזרים ישביתו לצמיתות את כרטיס ה-SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"קודי ה-PIN אינם תואמים"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"ניסיונות רבים מדי לשרטוט קו ביטול נעילה."</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 31ef156..b0c1583 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIMカードのロック解除中…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"PINコードが正しくありません。"</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"PINは4~8桁の数字で入力してください。"</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUKコードは8桁以上の番号です。"</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"正しいPUKコードを再入力してください。誤入力を繰り返すと、SIMが永久に無効になるおそれがあります。"</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PINコードが一致しません"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"パターンの入力を所定の回数以上間違えました。"</string>
diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index c629eb5..49f5cde 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM ბარათის განბლოკვა…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"არასწორი PIN კოდი."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"აკრიფეთ PIN, რომელიც შედგება 4-დან 8 ციფრამდე."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK კოდი უნდა იყოს რვა ან მეტი ციფრისგან შემდგარი."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"ხელახლა შეიყვანეთ სწორი PUK კოდი. რამდენიმე წარუმატებელი მცდელობა გამოიწვევს SIM ბარათის დაბლოკვას."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN კოდები არ ემთხვევა"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"ნახატი ნიმუშის ძალიან ბევრი მცდელობა"</string>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index 59aaafd..cb032a2 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"កំពុងដោះសោស៊ីមកាត..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"កូដ PIN មិនត្រឹមត្រូវ។"</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"បញ្ចូលកូដ PIN ដែលមានពី ៤ ដល់ ៨ លេខ។"</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"កូដ PUK គួរតែមាន ៨ លេខ ឬច្រើនជាងនេះ។"</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"បញ្ចូលកូដ PUK ម្ដងទៀត។ ការព្យាយាមដដែលច្រើនដឹងនឹងបិទស៊ីមកាតជាអចិន្ត្រៃយ៍។"</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"កូដ PIN មិនដូចគ្នា"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"ព្យាយាមលំនាំច្រើនពេក"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 948f57c..7d1b7be 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM 카드 잠금해제 중..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"PIN 코드가 잘못되었습니다."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"4~8자리 숫자로 된 PIN을 입력하세요."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK 코드는 8자리 이상의 숫자여야 합니다."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"올바른 PUK 코드를 다시 입력하세요. 입력을 반복해서 시도하면 SIM을 영구적으로 사용할 수 없게 됩니다."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN 코드가 일치하지 않음"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"패턴 시도 횟수가 너무 많음"</string>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index db700b9..db595e8 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"ປົດລັອກ SIM card..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"ລະຫັດ PIN ບໍ່ຖືກຕ້ອງ."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"ພິມລະຫັດ PIN ຄວາມຍາວ 4 ເຖິງ 8 ໂຕເລກ."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"ລະຫັດ PUK ຄວນມີຢ່າງໜ້ອຍ 8 ໂຕເລກ."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"ປ້ອນລະຫັດ PUK ທີ່ຖືກຕ້ອງຄືນໃໝ່. ການພະຍາຍາມໃສ່ຫຼາຍເທື່ອຈະເຮັດໃຫ້ຊິມກາດໃຊ້ບໍ່ໄດ້ຖາວອນ."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"ລະຫັດ PIN ບໍ່ກົງກັນ"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"ແຕ້ມຮູບແບບປົດລັອກຫຼາຍເກີນໄປ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 412513e..2d92c17 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Atrakinama SIM kortelė…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Netinkamas PIN kodas."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Įveskite PIN kodą, sudarytą iš 4–8 skaičių."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK kodas turėtų būti mažiausiai 8 skaitmenų."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Pakartotinai įveskite tinkamą PUK kodą. Pakartotinai bandant SIM bus neleidžiama visam laikui."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN kodai neatitinka"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Per daug atrakinimo piešinių bandymų"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 0bca9e4..9fd25a5 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Notiek SIM kartes atbloķēšana..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"PIN kods nav pareizs."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Ievadiet PIN, kas sastāv no 4 līdz 8 cipariem."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK kodam ir jābūt vismaz 8 ciparus garam."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Atkārtoti ievadiet pareizo PUK kodu. Ja vairākas reizes ievadīsiet to nepareizi, SIM karte tiks neatgriezeniski atspējota."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN kodi neatbilst."</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Pārāk daudz kombinācijas mēģinājumu"</string>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index 87dee58..59c87f7 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM картны түгжээг гаргаж байна…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Буруу PIN код."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"4-8 тооноос бүтэх PIN-г бичнэ үү."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK код 8-с цөөнгүй тооноос бүтнэ."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Зөв PUK кодыг дахин оруулна уу. Давтан оролдвол SIM нь бүрмөсөн идэвхгүй болгоно."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN кодууд таарахгүй байна"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Хээ оруулах оролдлого хэт олон"</string>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index 8c323de..62cdcaf 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Membuka kunci kad SIM..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Kod PIN salah."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Taipkan PIN yang mengandungi 4 hingga 8 nombor."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Kod PUK mestilah 8 nombor atau lebih."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Masukkan semula kod PIN yang betul. Percubaan berulang akan melumpuhkan SIM secara kekal."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Kod PIN tidak sepadan"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Terlalu banyak percubaan melukis corak"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index c44fe5c..26074f7 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1491,7 +1491,7 @@
<string name="wireless_display_route_description" msgid="9070346425023979651">"Trådløs skjerm"</string>
<string name="media_route_button_content_description" msgid="5758553567065145276">"Medieutgang"</string>
<string name="media_route_chooser_title" msgid="1751618554539087622">"Koble til enheten"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3395541745872017583">"Send skjermen til enheten"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3395541745872017583">"Cast skjermen til enheten"</string>
<string name="media_route_chooser_searching" msgid="4776236202610828706">"Søker etter enheter …"</string>
<string name="media_route_chooser_extended_settings" msgid="87015534236701604">"Innstillinger"</string>
<string name="media_route_controller_disconnect" msgid="8966120286374158649">"Koble fra"</string>
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Låser opp SIM-kortet ..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Feil PIN-kode."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Skriv inn en PIN-kode på fire til åtte sifre."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK-koden skal være på åtte eller flere siffer."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Skriv inn den korrekte PUK-koden på nytt. Gjentatte forsøk kommer til å deaktivere SIM-kortet."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN-kodene stemmer ikke overens"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"For mange forsøk på tegning av mønster"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index e8306cd..da0277e 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Simkaart ontgrendelen..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Onjuiste pincode."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Voer een pincode van 4 tot 8 cijfers in."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"De PUK-code is minimaal acht nummers lang."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Geef de juiste PUK-code opnieuw op. Bij herhaalde pogingen wordt de simkaart permanent uitgeschakeld."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Pincodes komen niet overeen"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Te veel patroonpogingen"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index b3696a4..5838a0b0 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Odblokowuję kartę SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Nieprawidłowy PIN."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Wpisz PIN o długości od 4 do 8 cyfr."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Kod PUK musi mieć co najmniej 8 cyfr."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Ponownie podaj poprawny kod PUK. Nieudane próby spowodują trwałe wyłączenie karty SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Kody PIN nie pasują"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Zbyt wiele prób narysowania wzoru"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 5457280..238f161 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"A desbloquear cartão SIM..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Código PIN incorreto."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Introduza um PIN entre 4 e 8 números."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"O código PUK deve ter 8 ou mais números."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Volte a introduzir o código PUK correto. Demasiadas tentativas consecutivas irão desativar permanentemente o SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Os códigos PIN não correspondem"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Demasiadas tentativas para desenhar sequência"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 0baa66d..2969a3fb 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Desbloqueando o cartão SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Código PIN incorreto."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Digite um PIN com quatro a oito números."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"O código PUK deve ter 8 números ou mais."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Introduza novamente o código PUK correto. Muitas tentativas malsucedidas desativarão permanentemente o SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Os códigos PIN não coincidem"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Muitas tentativas de padrão"</string>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index 8b7ce31..87550879 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -2485,7 +2485,7 @@
<skip />
<!-- no translation found for kg_invalid_sim_pin_hint (8795159358110620001) -->
<skip />
- <!-- no translation found for kg_invalid_sim_puk_hint (7553388325654369575) -->
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
<skip />
<!-- no translation found for kg_invalid_puk (3638289409676051243) -->
<skip />
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 5f6b3a4..4d6f459 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Se deblochează cardul SIM..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Cod PIN incorect."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Introduceţi un cod PIN format din 4 până la 8 cifre."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Codul PUK trebuie să aibă minimum 8 cifre."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Reintroduceţi codul PUK corect. Încercările repetate vor dezactiva definitiv cardul SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Codurile PIN nu coincid"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Prea multe încercări de desenare a modelului"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index aa77029..330b6d3 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -279,9 +279,9 @@
<string name="permdesc_receiveWapPush" msgid="748232190220583385">"Приложение сможет получать и обрабатывать WAP-сообщения. Это значит, что оно сможет отслеживать и удалять отправленные на ваше устройство сообщения, не показывая их."</string>
<string name="permlab_getTasks" msgid="6466095396623933906">"Получение данных о запущенных приложениях"</string>
<string name="permdesc_getTasks" msgid="7454215995847658102">"Приложение сможет получать информацию о недавно запущенных и выполняемых задачах, а следовательно, и о приложениях, используемых на устройстве."</string>
- <string name="permlab_interactAcrossUsers" msgid="7114255281944211682">"разрешить взаимодействие со всеми аккаунтами"</string>
+ <string name="permlab_interactAcrossUsers" msgid="7114255281944211682">"Взаимодействие с аккаунтами всех пользователей"</string>
<string name="permdesc_interactAcrossUsers" msgid="364670963623385786">"Приложение сможет выполнять действия во всех аккаунтах на этом устройстве. При этом защита от вредоносных приложений может быть недостаточной."</string>
- <string name="permlab_interactAcrossUsersFull" msgid="2567734285545074105">"Полное взаимодействие со всеми аккаунтами"</string>
+ <string name="permlab_interactAcrossUsersFull" msgid="2567734285545074105">"Полное взаимодействие с аккаунтами всех пользователей"</string>
<string name="permdesc_interactAcrossUsersFull" msgid="376841368395502366">"Приложение сможет выполнять любые действия во всех аккаунтах на этом устройстве."</string>
<string name="permlab_manageUsers" msgid="1676150911672282428">"Управлять аккаунтами"</string>
<string name="permdesc_manageUsers" msgid="8409306667645355638">"Приложения смогут управлять аккаунтами на этом устройстве (выполнять поиск, создавать и удалять их)"</string>
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Разблокировка SIM-карты…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Неверный PIN-код."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Введите PIN-код (от 4 до 8 цифр)."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK-код должен содержать не менее 8 символов."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Введите правильный PUK-код. После нескольких неудачных попыток SIM-карта будет заблокирована."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN-коды не совпадают"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Слишком много попыток ввода графического ключа"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index eed516e..1137fbf 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Prebieha odomykanie karty SIM..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Nesprávny kód PIN."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Zadajte kód PIN s dĺžkou 4 až 8 číslic."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Kód PUK musí obsahovať 8 alebo viac číslic."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Znova zadajte správny kód PUK. Opakované pokusy zakážu kartu SIM natrvalo."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Kódy PIN sa nezhodujú"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Príliš veľa pokusov o nakreslenie vzoru"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 2427de5..4064d28 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Odklepanje kartice SIM ..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Napačna koda PIN."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Vnesite PIN, ki vsebuje od štiri do osem številk."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Koda PUK mora vsebovati 8 ali več števk."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Vnovič vnesite pravilno kodo PUK. Večkratni poskusi bodo trajno onemogočili kartico SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Kodi PIN se ne ujemata"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Preveč poskusov vzorca"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 9fd8d8e..3ec4982 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Откључавање SIM картице…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"PIN кôд је нетачан."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Унесите PIN који има од 4 до 8 бројева."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK кôд треба да има 8 или више бројева."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Поново унесите исправни PUK кôд. Поновљени покушаји ће трајно онемогућити SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN кодови се не подударају"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Превише покушаја уноса шаблона"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index d1d9d61..33a9a18 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Låser upp SIM-kort …"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Fel PIN-kod."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Ange en PIN-kod med 4 till 8 siffror."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK-koden ska vara minst åtta siffror."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Ange rätt PUK-kod igen. Om försöken upprepas inaktiveras SIM-kortet permanent."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN-koderna stämmer inte överens"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"För många försök med grafiskt lösenord"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 0758a38..45a36f2 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -843,7 +843,7 @@
<string name="lockscreen_password_wrong" msgid="5737815393253165301">"Jaribu tena"</string>
<string name="faceunlock_multiple_failures" msgid="754137583022792429">"Majaribio ya Juu ya Kufungua Uso yamezidishwa"</string>
<string name="lockscreen_plugged_in" msgid="8057762828355572315">"Inachaji <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="lockscreen_charged" msgid="321635745684060624">"Imechajiwa"</string>
+ <string name="lockscreen_charged" msgid="321635745684060624">"Betri imejaa"</string>
<string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Unganisha chaja yako"</string>
<string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"Hakuna SIM kadi"</string>
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Inafungua SIM kadi..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Msimbo wa PIN usio sahihi."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Charaza PIN iliyo na tarakimu kati ya 4 na 8."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Msimbo wa PUK unafaa kuwa na nambari 8 au zaidi."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Ingiza upya msimbo sahihi wa PUK. Majaribio yanayorudiwa yatalemaza SIM kabisa."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Misimbo ya PIN haifanani"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Majaribio mengi mno ya mchoro"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index fbc8c75..05cddc5 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"กำลังปลดล็อกซิมการ์ด…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"รหัส PIN ไม่ถูกต้อง"</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"พิมพ์ PIN ซึ่งเป็นเลข 4 ถึง 8 หลัก"</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"รหัส PUK ต้องเป็นตัวเลขอย่างน้อย 8 หลัก"</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"ใส่รหัส PUK ที่ถูกต้องอีกครั้ง การพยายามซ้ำหลายครั้งจะทำให้ซิมการ์ดถูกปิดใช้งานอย่างถาวร"</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"รหัส PIN ไม่ตรง"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"ลองหลายรูปแบบมากเกินไป"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 632aa1b..1cb0336 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Ina-unlock ang SIM card…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Hindi tamang PIN code."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Mag-type ng PIN na 4 hanggang 8 numero."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Dapat ay 8 numero o higit pa ang PUK code."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Muling ilagay ang tamang PUK code. Permanenteng hindi pagaganahin ang SIM ng mga paulit-ulit na pagtatangka."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Hindi tumutugma ang mga PIN code"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Masyadong maraming pagtatangka sa pattern"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index f052646..7ccf960 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM kart kilidi açılıyor…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Yanlış PIN kodu."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"4-8 rakamdan oluşan bir PIN girin."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK kodu 8 veya daha çok basamaklı bir sayı olmalıdır."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Doğru PUK kodunu tekrar girin. Çok sayıda deneme yapılırsa SIM kart kalıcı olarak devre dışı bırakılır."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN kodları eşleşmiyor"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Çok fazla sayıda desen denemesi yapıldı"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 03c30a3..a707a03 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Розблокування SIM-карти…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Неправильний PIN-код."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Введіть PIN-код із 4–8 цифр."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK-код має складатися зі щонайменше 8 цифр."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Повторно введіть правильний PUK-код. Численні спроби назавжди вимкнуть SIM-карту."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN-коди не збігаються"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Забагато спроб намалювати ключ"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index d08d0ed..d3295a2 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Đang mở khóa thẻ SIM…"</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Mã PIN không chính xác."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Nhập mã PIN có từ 4 đến 8 số."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Mã PUK phải có từ 8 số trở lên."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Hãy nhập lại mã PUK chính xác. Nhiều lần lặp lại sẽ vô hiệu hóa vĩnh viễn thẻ SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Mã PIN không khớp"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Quá nhiều lần nhập hình"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 7fea933..a9900ec 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"正在解锁 SIM 卡..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"PIN 码有误。"</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"请输入 4 至 8 位数的 PIN。"</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK 码应至少包含 8 位数字。"</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"请重新输入正确的 PUK 码。如果尝试错误次数过多,SIM 卡将永久停用。"</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN 码不匹配"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"图案尝试次数过多"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 2f49a8e..eb5c05b 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"正在解開上鎖的 SIM 卡..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"PIN 碼不正確。"</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"請輸入一個 4 至 8 位數的 PIN 碼。"</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK 碼應由 8 個或以上數字組成。"</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"請重新輸入正確的 PUK 碼。如果嘗試輸入的次數過多,SIM 卡將永久停用。"</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN 碼不符"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"圖案嘗試次數過多"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 116e8f1..00254e2 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"正在解除 SIM 卡鎖定..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"PIN 碼不正確。"</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"請輸入 4 到 8 碼的 PIN。"</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"PUK 碼至少必須為 8 碼。"</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"重新輸入正確的 PUK 碼。如果錯誤次數過多,SIM 卡將會永久停用。"</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN 碼不符"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"圖形嘗試次數過多"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 1330ec2..4218dd5 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1526,7 +1526,8 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Ivula ikhadi le-SIM..."</string>
<string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Iphinikhodi engalungile."</string>
<string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Thayipha iphinikhodi enezinombolo ezingu-4 kuya kwezingu-8."</string>
- <string name="kg_invalid_sim_puk_hint" msgid="7553388325654369575">"Ikhodi ye-PUK kufanele ibe yizinombolo ezingu-8 noma eziningi."</string>
+ <!-- no translation found for kg_invalid_sim_puk_hint (6025069204539532000) -->
+ <skip />
<string name="kg_invalid_puk" msgid="3638289409676051243">"Faka kabusha ikhodi ye-PUK elungile. Imizamo ephindiwe izokhubaza unaphakade i-SIM."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"Iphinikhodi ayifani"</string>
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Kunemizamo eminingi kakhulu yephathini"</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index abae8636..785e788 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4190,7 +4190,7 @@
<!-- Message shown when the user enters an invalid SIM pin password in PUK screen -->
<string name="kg_invalid_sim_pin_hint">Type a PIN that is 4 to 8 numbers.</string>
<!-- Message shown when the user enters an invalid PUK code in the PUK screen -->
- <string name="kg_invalid_sim_puk_hint">PUK code should be 8 numbers or more.</string>
+ <string name="kg_invalid_sim_puk_hint">PUK code should be 8 numbers.</string>
<!-- Message shown when the user enters an invalid PUK code -->
<string name="kg_invalid_puk">Re-enter the correct PUK code. Repeated attempts will permanently disable the SIM.</string>
<!-- String shown in PUK screen when PIN codes don't match -->
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index cfb1983..2ec4284 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -156,7 +156,7 @@
* @param canvas The canvas to draw into
*/
public void draw(Canvas canvas) {
- if (canvas != null && canvas.isHardwareAccelerated()) {
+ if (canvas != null && canvas.isHardwareAccelerated() && false) { // temporarily disabled
final HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas;
final DisplayList displayList = getDisplayList(hardwareCanvas);
if (displayList != null) {
diff --git a/include/androidfw/Asset.h b/include/androidfw/Asset.h
new file mode 100644
index 0000000..1fe0e06
--- /dev/null
+++ b/include/androidfw/Asset.h
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Class providing access to a read-only asset. Asset objects are NOT
+// thread-safe, and should not be shared across threads.
+//
+#ifndef __LIBS_ASSET_H
+#define __LIBS_ASSET_H
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <utils/Compat.h>
+#include <utils/Errors.h>
+#include <utils/FileMap.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * Instances of this class provide read-only operations on a byte stream.
+ *
+ * Access may be optimized for streaming, random, or whole buffer modes. All
+ * operations are supported regardless of how the file was opened, but some
+ * things will be less efficient. [pass that in??]
+ *
+ * "Asset" is the base class for all types of assets. The classes below
+ * provide most of the implementation. The AssetManager uses one of the
+ * static "create" functions defined here to create a new instance.
+ */
+class Asset {
+public:
+ virtual ~Asset(void);
+
+ static int32_t getGlobalCount();
+ static String8 getAssetAllocations();
+
+ /* used when opening an asset */
+ typedef enum AccessMode {
+ ACCESS_UNKNOWN = 0,
+
+ /* read chunks, and seek forward and backward */
+ ACCESS_RANDOM,
+
+ /* read sequentially, with an occasional forward seek */
+ ACCESS_STREAMING,
+
+ /* caller plans to ask for a read-only buffer with all data */
+ ACCESS_BUFFER,
+ } AccessMode;
+
+ /*
+ * Read data from the current offset. Returns the actual number of
+ * bytes read, 0 on EOF, or -1 on error.
+ */
+ virtual ssize_t read(void* buf, size_t count) = 0;
+
+ /*
+ * Seek to the specified offset. "whence" uses the same values as
+ * lseek/fseek. Returns the new position on success, or (off64_t) -1
+ * on failure.
+ */
+ virtual off64_t seek(off64_t offset, int whence) = 0;
+
+ /*
+ * Close the asset, freeing all associated resources.
+ */
+ virtual void close(void) = 0;
+
+ /*
+ * Get a pointer to a buffer with the entire contents of the file.
+ */
+ virtual const void* getBuffer(bool wordAligned) = 0;
+
+ /*
+ * Get the total amount of data that can be read.
+ */
+ virtual off64_t getLength(void) const = 0;
+
+ /*
+ * Get the total amount of data that can be read from the current position.
+ */
+ virtual off64_t getRemainingLength(void) const = 0;
+
+ /*
+ * Open a new file descriptor that can be used to read this asset.
+ * Returns -1 if you can not use the file descriptor (for example if the
+ * asset is compressed).
+ */
+ virtual int openFileDescriptor(off64_t* outStart, off64_t* outLength) const = 0;
+
+ /*
+ * Return whether this asset's buffer is allocated in RAM (not mmapped).
+ * Note: not virtual so it is safe to call even when being destroyed.
+ */
+ virtual bool isAllocated(void) const { return false; }
+
+ /*
+ * Get a string identifying the asset's source. This might be a full
+ * path, it might be a colon-separated list of identifiers.
+ *
+ * This is NOT intended to be used for anything except debug output.
+ * DO NOT try to parse this or use it to open a file.
+ */
+ const char* getAssetSource(void) const { return mAssetSource.string(); }
+
+protected:
+ Asset(void); // constructor; only invoked indirectly
+
+ /* handle common seek() housekeeping */
+ off64_t handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t maxPosn);
+
+ /* set the asset source string */
+ void setAssetSource(const String8& path) { mAssetSource = path; }
+
+ AccessMode getAccessMode(void) const { return mAccessMode; }
+
+private:
+ /* these operations are not implemented */
+ Asset(const Asset& src);
+ Asset& operator=(const Asset& src);
+
+ /* AssetManager needs access to our "create" functions */
+ friend class AssetManager;
+
+ /*
+ * Create the asset from a named file on disk.
+ */
+ static Asset* createFromFile(const char* fileName, AccessMode mode);
+
+ /*
+ * Create the asset from a named, compressed file on disk (e.g. ".gz").
+ */
+ static Asset* createFromCompressedFile(const char* fileName,
+ AccessMode mode);
+
+#if 0
+ /*
+ * Create the asset from a segment of an open file. This will fail
+ * if "offset" and "length" don't fit within the bounds of the file.
+ *
+ * The asset takes ownership of the file descriptor.
+ */
+ static Asset* createFromFileSegment(int fd, off64_t offset, size_t length,
+ AccessMode mode);
+
+ /*
+ * Create from compressed data. "fd" should be seeked to the start of
+ * the compressed data. This could be inside a gzip file or part of a
+ * Zip archive.
+ *
+ * The asset takes ownership of the file descriptor.
+ *
+ * This may not verify the validity of the compressed data until first
+ * use.
+ */
+ static Asset* createFromCompressedData(int fd, off64_t offset,
+ int compressionMethod, size_t compressedLength,
+ size_t uncompressedLength, AccessMode mode);
+#endif
+
+ /*
+ * Create the asset from a memory-mapped file segment.
+ *
+ * The asset takes ownership of the FileMap.
+ */
+ static Asset* createFromUncompressedMap(FileMap* dataMap, AccessMode mode);
+
+ /*
+ * Create the asset from a memory-mapped file segment with compressed
+ * data. "method" is a Zip archive compression method constant.
+ *
+ * The asset takes ownership of the FileMap.
+ */
+ static Asset* createFromCompressedMap(FileMap* dataMap, int method,
+ size_t uncompressedLen, AccessMode mode);
+
+
+ /*
+ * Create from a reference-counted chunk of shared memory.
+ */
+ // TODO
+
+ AccessMode mAccessMode; // how the asset was opened
+ String8 mAssetSource; // debug string
+
+ Asset* mNext; // linked list.
+ Asset* mPrev;
+};
+
+
+/*
+ * ===========================================================================
+ *
+ * Innards follow. Do not use these classes directly.
+ */
+
+/*
+ * An asset based on an uncompressed file on disk. It may encompass the
+ * entire file or just a piece of it. Access is through fread/fseek.
+ */
+class _FileAsset : public Asset {
+public:
+ _FileAsset(void);
+ virtual ~_FileAsset(void);
+
+ /*
+ * Use a piece of an already-open file.
+ *
+ * On success, the object takes ownership of "fd".
+ */
+ status_t openChunk(const char* fileName, int fd, off64_t offset, size_t length);
+
+ /*
+ * Use a memory-mapped region.
+ *
+ * On success, the object takes ownership of "dataMap".
+ */
+ status_t openChunk(FileMap* dataMap);
+
+ /*
+ * Standard Asset interfaces.
+ */
+ virtual ssize_t read(void* buf, size_t count);
+ virtual off64_t seek(off64_t offset, int whence);
+ virtual void close(void);
+ virtual const void* getBuffer(bool wordAligned);
+ virtual off64_t getLength(void) const { return mLength; }
+ virtual off64_t getRemainingLength(void) const { return mLength-mOffset; }
+ virtual int openFileDescriptor(off64_t* outStart, off64_t* outLength) const;
+ virtual bool isAllocated(void) const { return mBuf != NULL; }
+
+private:
+ off64_t mStart; // absolute file offset of start of chunk
+ off64_t mLength; // length of the chunk
+ off64_t mOffset; // current local offset, 0 == mStart
+ FILE* mFp; // for read/seek
+ char* mFileName; // for opening
+
+ /*
+ * To support getBuffer() we either need to read the entire thing into
+ * a buffer or memory-map it. For small files it's probably best to
+ * just read them in.
+ */
+ enum { kReadVsMapThreshold = 4096 };
+
+ FileMap* mMap; // for memory map
+ unsigned char* mBuf; // for read
+
+ const void* ensureAlignment(FileMap* map);
+};
+
+
+/*
+ * An asset based on compressed data in a file.
+ */
+class _CompressedAsset : public Asset {
+public:
+ _CompressedAsset(void);
+ virtual ~_CompressedAsset(void);
+
+ /*
+ * Use a piece of an already-open file.
+ *
+ * On success, the object takes ownership of "fd".
+ */
+ status_t openChunk(int fd, off64_t offset, int compressionMethod,
+ size_t uncompressedLen, size_t compressedLen);
+
+ /*
+ * Use a memory-mapped region.
+ *
+ * On success, the object takes ownership of "fd".
+ */
+ status_t openChunk(FileMap* dataMap, int compressionMethod,
+ size_t uncompressedLen);
+
+ /*
+ * Standard Asset interfaces.
+ */
+ virtual ssize_t read(void* buf, size_t count);
+ virtual off64_t seek(off64_t offset, int whence);
+ virtual void close(void);
+ virtual const void* getBuffer(bool wordAligned);
+ virtual off64_t getLength(void) const { return mUncompressedLen; }
+ virtual off64_t getRemainingLength(void) const { return mUncompressedLen-mOffset; }
+ virtual int openFileDescriptor(off64_t* outStart, off64_t* outLength) const { return -1; }
+ virtual bool isAllocated(void) const { return mBuf != NULL; }
+
+private:
+ off64_t mStart; // offset to start of compressed data
+ off64_t mCompressedLen; // length of the compressed data
+ off64_t mUncompressedLen; // length of the uncompressed data
+ off64_t mOffset; // current offset, 0 == start of uncomp data
+
+ FileMap* mMap; // for memory-mapped input
+ int mFd; // for file input
+
+ class StreamingZipInflater* mZipInflater; // for streaming large compressed assets
+
+ unsigned char* mBuf; // for getBuffer()
+};
+
+// need: shared mmap version?
+
+}; // namespace android
+
+#endif // __LIBS_ASSET_H
diff --git a/include/androidfw/AssetDir.h b/include/androidfw/AssetDir.h
new file mode 100644
index 0000000..bd89d7d
--- /dev/null
+++ b/include/androidfw/AssetDir.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Access a chunk of the asset hierarchy as if it were a single directory.
+//
+#ifndef __LIBS_ASSETDIR_H
+#define __LIBS_ASSETDIR_H
+
+#include <androidfw/misc.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/SortedVector.h>
+#include <sys/types.h>
+
+namespace android {
+
+/*
+ * This provides vector-style access to a directory. We do this rather
+ * than modeling opendir/readdir access because it's simpler and the
+ * nature of the operation requires us to have all data on hand anyway.
+ *
+ * The list of files will be sorted in ascending order by ASCII value.
+ *
+ * The contents are populated by our friend, the AssetManager.
+ */
+class AssetDir {
+public:
+ AssetDir(void)
+ : mFileInfo(NULL)
+ {}
+ virtual ~AssetDir(void) {
+ delete mFileInfo;
+ }
+
+ /*
+ * Vector-style access.
+ */
+ size_t getFileCount(void) { return mFileInfo->size(); }
+ const String8& getFileName(int idx) {
+ return mFileInfo->itemAt(idx).getFileName();
+ }
+ const String8& getSourceName(int idx) {
+ return mFileInfo->itemAt(idx).getSourceName();
+ }
+
+ /*
+ * Get the type of a file (usually regular or directory).
+ */
+ FileType getFileType(int idx) {
+ return mFileInfo->itemAt(idx).getFileType();
+ }
+
+private:
+ /* these operations are not implemented */
+ AssetDir(const AssetDir& src);
+ const AssetDir& operator=(const AssetDir& src);
+
+ friend class AssetManager;
+
+ /*
+ * This holds information about files in the asset hierarchy.
+ */
+ class FileInfo {
+ public:
+ FileInfo(void) {}
+ FileInfo(const String8& path) // useful for e.g. svect.indexOf
+ : mFileName(path), mFileType(kFileTypeUnknown)
+ {}
+ ~FileInfo(void) {}
+ FileInfo(const FileInfo& src) {
+ copyMembers(src);
+ }
+ const FileInfo& operator= (const FileInfo& src) {
+ if (this != &src)
+ copyMembers(src);
+ return *this;
+ }
+
+ void copyMembers(const FileInfo& src) {
+ mFileName = src.mFileName;
+ mFileType = src.mFileType;
+ mSourceName = src.mSourceName;
+ }
+
+ /* need this for SortedVector; must compare only on file name */
+ bool operator< (const FileInfo& rhs) const {
+ return mFileName < rhs.mFileName;
+ }
+
+ /* used by AssetManager */
+ bool operator== (const FileInfo& rhs) const {
+ return mFileName == rhs.mFileName;
+ }
+
+ void set(const String8& path, FileType type) {
+ mFileName = path;
+ mFileType = type;
+ }
+
+ const String8& getFileName(void) const { return mFileName; }
+ void setFileName(const String8& path) { mFileName = path; }
+
+ FileType getFileType(void) const { return mFileType; }
+ void setFileType(FileType type) { mFileType = type; }
+
+ const String8& getSourceName(void) const { return mSourceName; }
+ void setSourceName(const String8& path) { mSourceName = path; }
+
+ /*
+ * Handy utility for finding an entry in a sorted vector of FileInfo.
+ * Returns the index of the matching entry, or -1 if none found.
+ */
+ static int findEntry(const SortedVector<FileInfo>* pVector,
+ const String8& fileName);
+
+ private:
+ String8 mFileName; // filename only
+ FileType mFileType; // regular, directory, etc
+
+ String8 mSourceName; // currently debug-only
+ };
+
+ /* AssetManager uses this to initialize us */
+ void setFileList(SortedVector<FileInfo>* list) { mFileInfo = list; }
+
+ SortedVector<FileInfo>* mFileInfo;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ASSETDIR_H
diff --git a/include/androidfw/AssetManager.h b/include/androidfw/AssetManager.h
new file mode 100644
index 0000000..a010957
--- /dev/null
+++ b/include/androidfw/AssetManager.h
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Asset management class. AssetManager objects are thread-safe.
+//
+#ifndef __LIBS_ASSETMANAGER_H
+#define __LIBS_ASSETMANAGER_H
+
+#include <androidfw/Asset.h>
+#include <androidfw/AssetDir.h>
+#include <androidfw/ZipFileRO.h>
+#include <utils/KeyedVector.h>
+#include <utils/SortedVector.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include <utils/Vector.h>
+
+/*
+ * Native-app access is via the opaque typedef struct AAssetManager in the C namespace.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct AAssetManager { };
+
+#ifdef __cplusplus
+};
+#endif
+
+
+/*
+ * Now the proper C++ android-namespace definitions
+ */
+
+namespace android {
+
+class Asset; // fwd decl for things that include Asset.h first
+class ResTable;
+struct ResTable_config;
+
+/*
+ * Every application that uses assets needs one instance of this. A
+ * single instance may be shared across multiple threads, and a single
+ * thread may have more than one instance (the latter is discouraged).
+ *
+ * The purpose of the AssetManager is to create Asset objects. To do
+ * this efficiently it may cache information about the locations of
+ * files it has seen. This can be controlled with the "cacheMode"
+ * argument.
+ *
+ * The asset hierarchy may be examined like a filesystem, using
+ * AssetDir objects to peruse a single directory.
+ */
+class AssetManager : public AAssetManager {
+public:
+ typedef enum CacheMode {
+ CACHE_UNKNOWN = 0,
+ CACHE_OFF, // don't try to cache file locations
+ CACHE_DEFER, // construct cache as pieces are needed
+ //CACHE_SCAN, // scan full(!) asset hierarchy at init() time
+ } CacheMode;
+
+ AssetManager(CacheMode cacheMode = CACHE_OFF);
+ virtual ~AssetManager(void);
+
+ static int32_t getGlobalCount();
+
+ /*
+ * Add a new source for assets. This can be called multiple times to
+ * look in multiple places for assets. It can be either a directory (for
+ * finding assets as raw files on the disk) or a ZIP file. This newly
+ * added asset path will be examined first when searching for assets,
+ * before any that were previously added.
+ *
+ * Returns "true" on success, "false" on failure. If 'cookie' is non-NULL,
+ * then on success, *cookie is set to the value corresponding to the
+ * newly-added asset source.
+ */
+ bool addAssetPath(const String8& path, int32_t* cookie);
+
+ /*
+ * Convenience for adding the standard system assets. Uses the
+ * ANDROID_ROOT environment variable to find them.
+ */
+ bool addDefaultAssets();
+
+ /*
+ * Iterate over the asset paths in this manager. (Previously
+ * added via addAssetPath() and addDefaultAssets().) On first call,
+ * 'cookie' must be 0, resulting in the first cookie being returned.
+ * Each next cookie will be returned there-after, until -1 indicating
+ * the end has been reached.
+ */
+ int32_t nextAssetPath(const int32_t cookie) const;
+
+ /*
+ * Return an asset path in the manager. 'which' must be between 0 and
+ * countAssetPaths().
+ */
+ String8 getAssetPath(const int32_t cookie) const;
+
+ /*
+ * Set the current locale and vendor. The locale can change during
+ * the lifetime of an AssetManager if the user updates the device's
+ * language setting. The vendor is less likely to change.
+ *
+ * Pass in NULL to indicate no preference.
+ */
+ void setLocale(const char* locale);
+ void setVendor(const char* vendor);
+
+ /*
+ * Choose screen orientation for resources values returned.
+ */
+ void setConfiguration(const ResTable_config& config, const char* locale = NULL);
+
+ void getConfiguration(ResTable_config* outConfig) const;
+
+ typedef Asset::AccessMode AccessMode; // typing shortcut
+
+ /*
+ * Open an asset.
+ *
+ * This will search through locale-specific and vendor-specific
+ * directories and packages to find the file.
+ *
+ * The object returned does not depend on the AssetManager. It should
+ * be freed by calling Asset::close().
+ */
+ Asset* open(const char* fileName, AccessMode mode);
+
+ /*
+ * Open a non-asset file as an asset.
+ *
+ * This is for opening files that are included in an asset package
+ * but aren't assets. These sit outside the usual "locale/vendor"
+ * path hierarchy, and will not be seen by "AssetDir" or included
+ * in our filename cache.
+ */
+ Asset* openNonAsset(const char* fileName, AccessMode mode);
+
+ /*
+ * Explicit non-asset file. The file explicitly named by the cookie (the
+ * resource set to look in) and fileName will be opened and returned.
+ */
+ Asset* openNonAsset(const int32_t cookie, const char* fileName, AccessMode mode);
+
+ /*
+ * Open a directory within the asset hierarchy.
+ *
+ * The contents of the directory are an amalgam of vendor-specific,
+ * locale-specific, and generic assets stored loosely or in asset
+ * packages. Depending on the cache setting and previous accesses,
+ * this call may incur significant disk overhead.
+ *
+ * To open the top-level directory, pass in "".
+ */
+ AssetDir* openDir(const char* dirName);
+
+ /*
+ * Open a directory within a particular path of the asset manager.
+ *
+ * The contents of the directory are an amalgam of vendor-specific,
+ * locale-specific, and generic assets stored loosely or in asset
+ * packages. Depending on the cache setting and previous accesses,
+ * this call may incur significant disk overhead.
+ *
+ * To open the top-level directory, pass in "".
+ */
+ AssetDir* openNonAssetDir(const int32_t cookie, const char* dirName);
+
+ /*
+ * Get the type of a file in the asset hierarchy. They will either
+ * be "regular" or "directory". [Currently only works for "regular".]
+ *
+ * Can also be used as a quick test for existence of a file.
+ */
+ FileType getFileType(const char* fileName);
+
+ /*
+ * Return the complete resource table to find things in the package.
+ */
+ const ResTable& getResources(bool required = true) const;
+
+ /*
+ * Discard cached filename information. This only needs to be called
+ * if somebody has updated the set of "loose" files, and we want to
+ * discard our cached notion of what's where.
+ */
+ void purge(void) { purgeFileNameCacheLocked(); }
+
+ /*
+ * Return true if the files this AssetManager references are all
+ * up-to-date (have not been changed since it was created). If false
+ * is returned, you will need to create a new AssetManager to get
+ * the current data.
+ */
+ bool isUpToDate();
+
+ /**
+ * Get the known locales for this asset manager object.
+ */
+ void getLocales(Vector<String8>* locales) const;
+
+private:
+ struct asset_path
+ {
+ String8 path;
+ FileType type;
+ String8 idmap;
+ };
+
+ Asset* openInPathLocked(const char* fileName, AccessMode mode,
+ const asset_path& path);
+ Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
+ const asset_path& path);
+ Asset* openInLocaleVendorLocked(const char* fileName, AccessMode mode,
+ const asset_path& path, const char* locale, const char* vendor);
+ String8 createPathNameLocked(const asset_path& path, const char* locale,
+ const char* vendor);
+ String8 createPathNameLocked(const asset_path& path, const char* rootDir);
+ String8 createZipSourceNameLocked(const String8& zipFileName,
+ const String8& dirName, const String8& fileName);
+
+ ZipFileRO* getZipFileLocked(const asset_path& path);
+ Asset* openAssetFromFileLocked(const String8& fileName, AccessMode mode);
+ Asset* openAssetFromZipLocked(const ZipFileRO* pZipFile,
+ const ZipEntryRO entry, AccessMode mode, const String8& entryName);
+
+ bool scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& path, const char* rootDir, const char* dirName);
+ SortedVector<AssetDir::FileInfo>* scanDirLocked(const String8& path);
+ bool scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& path, const char* rootDir, const char* dirName);
+ void mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const SortedVector<AssetDir::FileInfo>* pContents);
+
+ void loadFileNameCacheLocked(void);
+ void fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const char* dirName);
+ bool fncScanAndMergeDirLocked(
+ SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& path, const char* locale, const char* vendor,
+ const char* dirName);
+ void purgeFileNameCacheLocked(void);
+
+ const ResTable* getResTable(bool required = true) const;
+ void setLocaleLocked(const char* locale);
+ void updateResourceParamsLocked() const;
+
+ bool createIdmapFileLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath);
+
+ bool isIdmapStaleLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath);
+
+ Asset* openIdmapLocked(const struct asset_path& ap) const;
+
+ bool getZipEntryCrcLocked(const String8& zipPath, const char* entryFilename, uint32_t* pCrc);
+
+ class SharedZip : public RefBase {
+ public:
+ static sp<SharedZip> get(const String8& path);
+
+ ZipFileRO* getZip();
+
+ Asset* getResourceTableAsset();
+ Asset* setResourceTableAsset(Asset* asset);
+
+ ResTable* getResourceTable();
+ ResTable* setResourceTable(ResTable* res);
+
+ bool isUpToDate();
+
+ protected:
+ ~SharedZip();
+
+ private:
+ SharedZip(const String8& path, time_t modWhen);
+ SharedZip(); // <-- not implemented
+
+ String8 mPath;
+ ZipFileRO* mZipFile;
+ time_t mModWhen;
+
+ Asset* mResourceTableAsset;
+ ResTable* mResourceTable;
+
+ static Mutex gLock;
+ static DefaultKeyedVector<String8, wp<SharedZip> > gOpen;
+ };
+
+ /*
+ * Manage a set of Zip files. For each file we need a pointer to the
+ * ZipFile and a time_t with the file's modification date.
+ *
+ * We currently only have two zip files (current app, "common" app).
+ * (This was originally written for 8, based on app/locale/vendor.)
+ */
+ class ZipSet {
+ public:
+ ZipSet(void);
+ ~ZipSet(void);
+
+ /*
+ * Return a ZipFileRO structure for a ZipFileRO with the specified
+ * parameters.
+ */
+ ZipFileRO* getZip(const String8& path);
+
+ Asset* getZipResourceTableAsset(const String8& path);
+ Asset* setZipResourceTableAsset(const String8& path, Asset* asset);
+
+ ResTable* getZipResourceTable(const String8& path);
+ ResTable* setZipResourceTable(const String8& path, ResTable* res);
+
+ // generate path, e.g. "common/en-US-noogle.zip"
+ static String8 getPathName(const char* path);
+
+ bool isUpToDate();
+
+ private:
+ void closeZip(int idx);
+
+ int getIndex(const String8& zip) const;
+ mutable Vector<String8> mZipPath;
+ mutable Vector<sp<SharedZip> > mZipFile;
+ };
+
+ // Protect all internal state.
+ mutable Mutex mLock;
+
+ ZipSet mZipSet;
+
+ Vector<asset_path> mAssetPaths;
+ char* mLocale;
+ char* mVendor;
+
+ mutable ResTable* mResources;
+ ResTable_config* mConfig;
+
+ /*
+ * Cached data for "loose" files. This lets us avoid poking at the
+ * filesystem when searching for loose assets. Each entry is the
+ * "extended partial" path, e.g. "default/default/foo/bar.txt". The
+ * full set of files is present, including ".EXCLUDE" entries.
+ *
+ * We do not cache directory names. We don't retain the ".gz",
+ * because to our clients "foo" and "foo.gz" both look like "foo".
+ */
+ CacheMode mCacheMode; // is the cache enabled?
+ bool mCacheValid; // clear when locale or vendor changes
+ SortedVector<AssetDir::FileInfo> mCache;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ASSETMANAGER_H
diff --git a/include/androidfw/BackupHelpers.h b/include/androidfw/BackupHelpers.h
new file mode 100644
index 0000000..1bb04a7
--- /dev/null
+++ b/include/androidfw/BackupHelpers.h
@@ -0,0 +1,169 @@
+/*
+ * 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.
+ */
+
+#ifndef _UTILS_BACKUP_HELPERS_H
+#define _UTILS_BACKUP_HELPERS_H
+
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/KeyedVector.h>
+
+namespace android {
+
+enum {
+ BACKUP_HEADER_ENTITY_V1 = 0x61746144, // Data (little endian)
+};
+
+typedef struct {
+ int type; // BACKUP_HEADER_ENTITY_V1
+ int keyLen; // length of the key name, not including the null terminator
+ int dataSize; // size of the data, not including the padding, -1 means delete
+} entity_header_v1;
+
+struct SnapshotHeader {
+ int magic0;
+ int fileCount;
+ int magic1;
+ int totalSize;
+};
+
+struct FileState {
+ int modTime_sec;
+ int modTime_nsec;
+ int mode;
+ int size;
+ int crc32;
+ int nameLen;
+};
+
+struct FileRec {
+ String8 file;
+ bool deleted;
+ FileState s;
+};
+
+
+/**
+ * Writes the data.
+ *
+ * If an error occurs, it poisons this object and all write calls will fail
+ * with the error that occurred.
+ */
+class BackupDataWriter
+{
+public:
+ BackupDataWriter(int fd);
+ // does not close fd
+ ~BackupDataWriter();
+
+ status_t WriteEntityHeader(const String8& key, size_t dataSize);
+
+ /* Note: WriteEntityData will write arbitrary data into the file without
+ * validation or a previously-supplied header. The full backup implementation
+ * uses it this way to generate a controlled binary stream that is not
+ * entity-structured. If the implementation here is changed, either this
+ * use case must remain valid, or the full backup implementation should be
+ * adjusted to use some other appropriate mechanism.
+ */
+ status_t WriteEntityData(const void* data, size_t size);
+
+ void SetKeyPrefix(const String8& keyPrefix);
+
+private:
+ explicit BackupDataWriter();
+ status_t write_padding_for(int n);
+
+ int m_fd;
+ status_t m_status;
+ ssize_t m_pos;
+ int m_entityCount;
+ String8 m_keyPrefix;
+};
+
+/**
+ * Reads the data.
+ *
+ * If an error occurs, it poisons this object and all write calls will fail
+ * with the error that occurred.
+ */
+class BackupDataReader
+{
+public:
+ BackupDataReader(int fd);
+ // does not close fd
+ ~BackupDataReader();
+
+ status_t Status();
+ status_t ReadNextHeader(bool* done, int* type);
+
+ bool HasEntities();
+ status_t ReadEntityHeader(String8* key, size_t* dataSize);
+ status_t SkipEntityData(); // must be called with the pointer at the beginning of the data.
+ ssize_t ReadEntityData(void* data, size_t size);
+
+private:
+ explicit BackupDataReader();
+ status_t skip_padding();
+
+ int m_fd;
+ bool m_done;
+ status_t m_status;
+ ssize_t m_pos;
+ ssize_t m_dataEndPos;
+ int m_entityCount;
+ union {
+ int type;
+ entity_header_v1 entity;
+ } m_header;
+ String8 m_key;
+};
+
+int back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD,
+ char const* const* files, char const* const *keys, int fileCount);
+
+int write_tarfile(const String8& packageName, const String8& domain,
+ const String8& rootPath, const String8& filePath, BackupDataWriter* outputStream);
+
+class RestoreHelperBase
+{
+public:
+ RestoreHelperBase();
+ ~RestoreHelperBase();
+
+ status_t WriteFile(const String8& filename, BackupDataReader* in);
+ status_t WriteSnapshot(int fd);
+
+private:
+ void* m_buf;
+ bool m_loggedUnknownMetadata;
+ KeyedVector<String8,FileRec> m_files;
+};
+
+#define TEST_BACKUP_HELPERS 1
+
+#if TEST_BACKUP_HELPERS
+int backup_helper_test_empty();
+int backup_helper_test_four();
+int backup_helper_test_files();
+int backup_helper_test_null_base();
+int backup_helper_test_missing_file();
+int backup_helper_test_data_writer();
+int backup_helper_test_data_reader();
+#endif
+
+} // namespace android
+
+#endif // _UTILS_BACKUP_HELPERS_H
diff --git a/include/androidfw/CursorWindow.h b/include/androidfw/CursorWindow.h
new file mode 100644
index 0000000..8a2979a
--- /dev/null
+++ b/include/androidfw/CursorWindow.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+#ifndef _ANDROID__DATABASE_WINDOW_H
+#define _ANDROID__DATABASE_WINDOW_H
+
+#include <cutils/log.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <binder/Parcel.h>
+#include <utils/String8.h>
+
+#if LOG_NDEBUG
+
+#define IF_LOG_WINDOW() if (false)
+#define LOG_WINDOW(...)
+
+#else
+
+#define IF_LOG_WINDOW() IF_ALOG(LOG_DEBUG, "CursorWindow")
+#define LOG_WINDOW(...) ALOG(LOG_DEBUG, "CursorWindow", __VA_ARGS__)
+
+#endif
+
+namespace android {
+
+/**
+ * This class stores a set of rows from a database in a buffer. The begining of the
+ * window has first chunk of RowSlots, which are offsets to the row directory, followed by
+ * an offset to the next chunk in a linked-list of additional chunk of RowSlots in case
+ * the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a
+ * FieldSlot per column, which has the size, offset, and type of the data for that field.
+ * Note that the data types come from sqlite3.h.
+ *
+ * Strings are stored in UTF-8.
+ */
+class CursorWindow {
+ CursorWindow(const String8& name, int ashmemFd,
+ void* data, size_t size, bool readOnly);
+
+public:
+ /* Field types. */
+ enum {
+ FIELD_TYPE_NULL = 0,
+ FIELD_TYPE_INTEGER = 1,
+ FIELD_TYPE_FLOAT = 2,
+ FIELD_TYPE_STRING = 3,
+ FIELD_TYPE_BLOB = 4,
+ };
+
+ /* Opaque type that describes a field slot. */
+ struct FieldSlot {
+ private:
+ int32_t type;
+ union {
+ double d;
+ int64_t l;
+ struct {
+ uint32_t offset;
+ uint32_t size;
+ } buffer;
+ } data;
+
+ friend class CursorWindow;
+ } __attribute((packed));
+
+ ~CursorWindow();
+
+ static status_t create(const String8& name, size_t size, CursorWindow** outCursorWindow);
+ static status_t createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow);
+
+ status_t writeToParcel(Parcel* parcel);
+
+ inline String8 name() { return mName; }
+ inline size_t size() { return mSize; }
+ inline size_t freeSpace() { return mSize - mHeader->freeOffset; }
+ inline uint32_t getNumRows() { return mHeader->numRows; }
+ inline uint32_t getNumColumns() { return mHeader->numColumns; }
+
+ status_t clear();
+ status_t setNumColumns(uint32_t numColumns);
+
+ /**
+ * Allocate a row slot and its directory.
+ * The row is initialized will null entries for each field.
+ */
+ status_t allocRow();
+ status_t freeLastRow();
+
+ status_t putBlob(uint32_t row, uint32_t column, const void* value, size_t size);
+ status_t putString(uint32_t row, uint32_t column, const char* value, size_t sizeIncludingNull);
+ status_t putLong(uint32_t row, uint32_t column, int64_t value);
+ status_t putDouble(uint32_t row, uint32_t column, double value);
+ status_t putNull(uint32_t row, uint32_t column);
+
+ /**
+ * Gets the field slot at the specified row and column.
+ * Returns null if the requested row or column is not in the window.
+ */
+ FieldSlot* getFieldSlot(uint32_t row, uint32_t column);
+
+ inline int32_t getFieldSlotType(FieldSlot* fieldSlot) {
+ return fieldSlot->type;
+ }
+
+ inline int64_t getFieldSlotValueLong(FieldSlot* fieldSlot) {
+ return fieldSlot->data.l;
+ }
+
+ inline double getFieldSlotValueDouble(FieldSlot* fieldSlot) {
+ return fieldSlot->data.d;
+ }
+
+ inline const char* getFieldSlotValueString(FieldSlot* fieldSlot,
+ size_t* outSizeIncludingNull) {
+ *outSizeIncludingNull = fieldSlot->data.buffer.size;
+ return static_cast<char*>(offsetToPtr(fieldSlot->data.buffer.offset));
+ }
+
+ inline const void* getFieldSlotValueBlob(FieldSlot* fieldSlot, size_t* outSize) {
+ *outSize = fieldSlot->data.buffer.size;
+ return offsetToPtr(fieldSlot->data.buffer.offset);
+ }
+
+private:
+ static const size_t ROW_SLOT_CHUNK_NUM_ROWS = 100;
+
+ struct Header {
+ // Offset of the lowest unused byte in the window.
+ uint32_t freeOffset;
+
+ // Offset of the first row slot chunk.
+ uint32_t firstChunkOffset;
+
+ uint32_t numRows;
+ uint32_t numColumns;
+ };
+
+ struct RowSlot {
+ uint32_t offset;
+ };
+
+ struct RowSlotChunk {
+ RowSlot slots[ROW_SLOT_CHUNK_NUM_ROWS];
+ uint32_t nextChunkOffset;
+ };
+
+ String8 mName;
+ int mAshmemFd;
+ void* mData;
+ size_t mSize;
+ bool mReadOnly;
+ Header* mHeader;
+
+ inline void* offsetToPtr(uint32_t offset) {
+ return static_cast<uint8_t*>(mData) + offset;
+ }
+
+ inline uint32_t offsetFromPtr(void* ptr) {
+ return static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(mData);
+ }
+
+ /**
+ * Allocate a portion of the window. Returns the offset
+ * of the allocation, or 0 if there isn't enough space.
+ * If aligned is true, the allocation gets 4 byte alignment.
+ */
+ uint32_t alloc(size_t size, bool aligned = false);
+
+ RowSlot* getRowSlot(uint32_t row);
+ RowSlot* allocRowSlot();
+
+ status_t putBlobOrString(uint32_t row, uint32_t column,
+ const void* value, size_t size, int32_t type);
+};
+
+}; // namespace android
+
+#endif
diff --git a/include/androidfw/ObbFile.h b/include/androidfw/ObbFile.h
new file mode 100644
index 0000000..47559cd
--- /dev/null
+++ b/include/androidfw/ObbFile.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef OBBFILE_H_
+#define OBBFILE_H_
+
+#include <stdint.h>
+#include <strings.h>
+
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+namespace android {
+
+// OBB flags (bit 0)
+#define OBB_OVERLAY (1 << 0)
+#define OBB_SALTED (1 << 1)
+
+class ObbFile : public RefBase {
+protected:
+ virtual ~ObbFile();
+
+public:
+ ObbFile();
+
+ bool readFrom(const char* filename);
+ bool readFrom(int fd);
+ bool writeTo(const char* filename);
+ bool writeTo(int fd);
+ bool removeFrom(const char* filename);
+ bool removeFrom(int fd);
+
+ const char* getFileName() const {
+ return mFileName;
+ }
+
+ const String8 getPackageName() const {
+ return mPackageName;
+ }
+
+ void setPackageName(String8 packageName) {
+ mPackageName = packageName;
+ }
+
+ int32_t getVersion() const {
+ return mVersion;
+ }
+
+ void setVersion(int32_t version) {
+ mVersion = version;
+ }
+
+ int32_t getFlags() const {
+ return mFlags;
+ }
+
+ void setFlags(int32_t flags) {
+ mFlags = flags;
+ }
+
+ const unsigned char* getSalt(size_t* length) const {
+ if ((mFlags & OBB_SALTED) == 0) {
+ *length = 0;
+ return NULL;
+ }
+
+ *length = sizeof(mSalt);
+ return mSalt;
+ }
+
+ bool setSalt(const unsigned char* salt, size_t length) {
+ if (length != sizeof(mSalt)) {
+ return false;
+ }
+
+ memcpy(mSalt, salt, sizeof(mSalt));
+ mFlags |= OBB_SALTED;
+ return true;
+ }
+
+ bool isOverlay() {
+ return (mFlags & OBB_OVERLAY) == OBB_OVERLAY;
+ }
+
+ void setOverlay(bool overlay) {
+ if (overlay) {
+ mFlags |= OBB_OVERLAY;
+ } else {
+ mFlags &= ~OBB_OVERLAY;
+ }
+ }
+
+ static inline uint32_t get4LE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ }
+
+ static inline void put4LE(unsigned char* buf, uint32_t val) {
+ buf[0] = val & 0xFF;
+ buf[1] = (val >> 8) & 0xFF;
+ buf[2] = (val >> 16) & 0xFF;
+ buf[3] = (val >> 24) & 0xFF;
+ }
+
+private:
+ /* Package name this ObbFile is associated with */
+ String8 mPackageName;
+
+ /* Package version this ObbFile is associated with */
+ int32_t mVersion;
+
+ /* Flags for this OBB type. */
+ int32_t mFlags;
+
+ /* Whether the file is salted. */
+ bool mSalted;
+
+ /* The encryption salt. */
+ unsigned char mSalt[8];
+
+ const char* mFileName;
+
+ size_t mFileSize;
+
+ size_t mFooterStart;
+
+ unsigned char* mReadBuf;
+
+ bool parseObbFile(int fd);
+};
+
+}
+#endif /* OBBFILE_H_ */
diff --git a/include/androidfw/PowerManager.h b/include/androidfw/PowerManager.h
new file mode 100644
index 0000000..ba98db0
--- /dev/null
+++ b/include/androidfw/PowerManager.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef _ANDROIDFW_POWER_MANAGER_H
+#define _ANDROIDFW_POWER_MANAGER_H
+
+
+namespace android {
+
+enum {
+ USER_ACTIVITY_EVENT_OTHER = 0,
+ USER_ACTIVITY_EVENT_BUTTON = 1,
+ USER_ACTIVITY_EVENT_TOUCH = 2,
+
+ USER_ACTIVITY_EVENT_LAST = USER_ACTIVITY_EVENT_TOUCH, // Last valid event code.
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_POWER_MANAGER_H
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
new file mode 100644
index 0000000..5151b06
--- /dev/null
+++ b/include/androidfw/ResourceTypes.h
@@ -0,0 +1,1605 @@
+/*
+ * Copyright (C) 2005 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.
+ */
+
+//
+// Definitions of resource data structures.
+//
+#ifndef _LIBS_UTILS_RESOURCE_TYPES_H
+#define _LIBS_UTILS_RESOURCE_TYPES_H
+
+#include <androidfw/Asset.h>
+#include <utils/ByteOrder.h>
+#include <utils/Errors.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+#include <utils/threads.h>
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <android/configuration.h>
+
+namespace android {
+
+/** ********************************************************************
+ * PNG Extensions
+ *
+ * New private chunks that may be placed in PNG images.
+ *
+ *********************************************************************** */
+
+/**
+ * This chunk specifies how to split an image into segments for
+ * scaling.
+ *
+ * There are J horizontal and K vertical segments. These segments divide
+ * the image into J*K regions as follows (where J=4 and K=3):
+ *
+ * F0 S0 F1 S1
+ * +-----+----+------+-------+
+ * S2| 0 | 1 | 2 | 3 |
+ * +-----+----+------+-------+
+ * | | | | |
+ * | | | | |
+ * F2| 4 | 5 | 6 | 7 |
+ * | | | | |
+ * | | | | |
+ * +-----+----+------+-------+
+ * S3| 8 | 9 | 10 | 11 |
+ * +-----+----+------+-------+
+ *
+ * Each horizontal and vertical segment is considered to by either
+ * stretchable (marked by the Sx labels) or fixed (marked by the Fy
+ * labels), in the horizontal or vertical axis, respectively. In the
+ * above example, the first is horizontal segment (F0) is fixed, the
+ * next is stretchable and then they continue to alternate. Note that
+ * the segment list for each axis can begin or end with a stretchable
+ * or fixed segment.
+ *
+ * The relative sizes of the stretchy segments indicates the relative
+ * amount of stretchiness of the regions bordered by the segments. For
+ * example, regions 3, 7 and 11 above will take up more horizontal space
+ * than regions 1, 5 and 9 since the horizontal segment associated with
+ * the first set of regions is larger than the other set of regions. The
+ * ratios of the amount of horizontal (or vertical) space taken by any
+ * two stretchable slices is exactly the ratio of their corresponding
+ * segment lengths.
+ *
+ * xDivs and yDivs point to arrays of horizontal and vertical pixel
+ * indices. The first pair of Divs (in either array) indicate the
+ * starting and ending points of the first stretchable segment in that
+ * axis. The next pair specifies the next stretchable segment, etc. So
+ * in the above example xDiv[0] and xDiv[1] specify the horizontal
+ * coordinates for the regions labeled 1, 5 and 9. xDiv[2] and
+ * xDiv[3] specify the coordinates for regions 3, 7 and 11. Note that
+ * the leftmost slices always start at x=0 and the rightmost slices
+ * always end at the end of the image. So, for example, the regions 0,
+ * 4 and 8 (which are fixed along the X axis) start at x value 0 and
+ * go to xDiv[0] and slices 2, 6 and 10 start at xDiv[1] and end at
+ * xDiv[2].
+ *
+ * The array pointed to by the colors field lists contains hints for
+ * each of the regions. They are ordered according left-to-right and
+ * top-to-bottom as indicated above. For each segment that is a solid
+ * color the array entry will contain that color value; otherwise it
+ * will contain NO_COLOR. Segments that are completely transparent
+ * will always have the value TRANSPARENT_COLOR.
+ *
+ * The PNG chunk type is "npTc".
+ */
+struct Res_png_9patch
+{
+ Res_png_9patch() : wasDeserialized(false), xDivs(NULL),
+ yDivs(NULL), colors(NULL) { }
+
+ int8_t wasDeserialized;
+ int8_t numXDivs;
+ int8_t numYDivs;
+ int8_t numColors;
+
+ // These tell where the next section of a patch starts.
+ // For example, the first patch includes the pixels from
+ // 0 to xDivs[0]-1 and the second patch includes the pixels
+ // from xDivs[0] to xDivs[1]-1.
+ // Note: allocation/free of these pointers is left to the caller.
+ int32_t* xDivs;
+ int32_t* yDivs;
+
+ int32_t paddingLeft, paddingRight;
+ int32_t paddingTop, paddingBottom;
+
+ enum {
+ // The 9 patch segment is not a solid color.
+ NO_COLOR = 0x00000001,
+
+ // The 9 patch segment is completely transparent.
+ TRANSPARENT_COLOR = 0x00000000
+ };
+ // Note: allocation/free of this pointer is left to the caller.
+ uint32_t* colors;
+
+ // Convert data from device representation to PNG file representation.
+ void deviceToFile();
+ // Convert data from PNG file representation to device representation.
+ void fileToDevice();
+ // Serialize/Marshall the patch data into a newly malloc-ed block
+ void* serialize();
+ // Serialize/Marshall the patch data
+ void serialize(void* outData);
+ // Deserialize/Unmarshall the patch data
+ static Res_png_9patch* deserialize(const void* data);
+ // Compute the size of the serialized data structure
+ size_t serializedSize();
+};
+
+/** ********************************************************************
+ * Base Types
+ *
+ * These are standard types that are shared between multiple specific
+ * resource types.
+ *
+ *********************************************************************** */
+
+/**
+ * Header that appears at the front of every data chunk in a resource.
+ */
+struct ResChunk_header
+{
+ // Type identifier for this chunk. The meaning of this value depends
+ // on the containing chunk.
+ uint16_t type;
+
+ // Size of the chunk header (in bytes). Adding this value to
+ // the address of the chunk allows you to find its associated data
+ // (if any).
+ uint16_t headerSize;
+
+ // Total size of this chunk (in bytes). This is the chunkSize plus
+ // the size of any data associated with the chunk. Adding this value
+ // to the chunk allows you to completely skip its contents (including
+ // any child chunks). If this value is the same as chunkSize, there is
+ // no data associated with the chunk.
+ uint32_t size;
+};
+
+enum {
+ RES_NULL_TYPE = 0x0000,
+ RES_STRING_POOL_TYPE = 0x0001,
+ RES_TABLE_TYPE = 0x0002,
+ RES_XML_TYPE = 0x0003,
+
+ // Chunk types in RES_XML_TYPE
+ RES_XML_FIRST_CHUNK_TYPE = 0x0100,
+ RES_XML_START_NAMESPACE_TYPE= 0x0100,
+ RES_XML_END_NAMESPACE_TYPE = 0x0101,
+ RES_XML_START_ELEMENT_TYPE = 0x0102,
+ RES_XML_END_ELEMENT_TYPE = 0x0103,
+ RES_XML_CDATA_TYPE = 0x0104,
+ RES_XML_LAST_CHUNK_TYPE = 0x017f,
+ // This contains a uint32_t array mapping strings in the string
+ // pool back to resource identifiers. It is optional.
+ RES_XML_RESOURCE_MAP_TYPE = 0x0180,
+
+ // Chunk types in RES_TABLE_TYPE
+ RES_TABLE_PACKAGE_TYPE = 0x0200,
+ RES_TABLE_TYPE_TYPE = 0x0201,
+ RES_TABLE_TYPE_SPEC_TYPE = 0x0202
+};
+
+/**
+ * Macros for building/splitting resource identifiers.
+ */
+#define Res_VALIDID(resid) (resid != 0)
+#define Res_CHECKID(resid) ((resid&0xFFFF0000) != 0)
+#define Res_MAKEID(package, type, entry) \
+ (((package+1)<<24) | (((type+1)&0xFF)<<16) | (entry&0xFFFF))
+#define Res_GETPACKAGE(id) ((id>>24)-1)
+#define Res_GETTYPE(id) (((id>>16)&0xFF)-1)
+#define Res_GETENTRY(id) (id&0xFFFF)
+
+#define Res_INTERNALID(resid) ((resid&0xFFFF0000) != 0 && (resid&0xFF0000) == 0)
+#define Res_MAKEINTERNAL(entry) (0x01000000 | (entry&0xFFFF))
+#define Res_MAKEARRAY(entry) (0x02000000 | (entry&0xFFFF))
+
+#define Res_MAXPACKAGE 255
+
+/**
+ * Representation of a value in a resource, supplying type
+ * information.
+ */
+struct Res_value
+{
+ // Number of bytes in this structure.
+ uint16_t size;
+
+ // Always set to 0.
+ uint8_t res0;
+
+ // Type of the data value.
+ enum {
+ // Contains no data.
+ TYPE_NULL = 0x00,
+ // The 'data' holds a ResTable_ref, a reference to another resource
+ // table entry.
+ TYPE_REFERENCE = 0x01,
+ // The 'data' holds an attribute resource identifier.
+ TYPE_ATTRIBUTE = 0x02,
+ // The 'data' holds an index into the containing resource table's
+ // global value string pool.
+ TYPE_STRING = 0x03,
+ // The 'data' holds a single-precision floating point number.
+ TYPE_FLOAT = 0x04,
+ // The 'data' holds a complex number encoding a dimension value,
+ // such as "100in".
+ TYPE_DIMENSION = 0x05,
+ // The 'data' holds a complex number encoding a fraction of a
+ // container.
+ TYPE_FRACTION = 0x06,
+
+ // Beginning of integer flavors...
+ TYPE_FIRST_INT = 0x10,
+
+ // The 'data' is a raw integer value of the form n..n.
+ TYPE_INT_DEC = 0x10,
+ // The 'data' is a raw integer value of the form 0xn..n.
+ TYPE_INT_HEX = 0x11,
+ // The 'data' is either 0 or 1, for input "false" or "true" respectively.
+ TYPE_INT_BOOLEAN = 0x12,
+
+ // Beginning of color integer flavors...
+ TYPE_FIRST_COLOR_INT = 0x1c,
+
+ // The 'data' is a raw integer value of the form #aarrggbb.
+ TYPE_INT_COLOR_ARGB8 = 0x1c,
+ // The 'data' is a raw integer value of the form #rrggbb.
+ TYPE_INT_COLOR_RGB8 = 0x1d,
+ // The 'data' is a raw integer value of the form #argb.
+ TYPE_INT_COLOR_ARGB4 = 0x1e,
+ // The 'data' is a raw integer value of the form #rgb.
+ TYPE_INT_COLOR_RGB4 = 0x1f,
+
+ // ...end of integer flavors.
+ TYPE_LAST_COLOR_INT = 0x1f,
+
+ // ...end of integer flavors.
+ TYPE_LAST_INT = 0x1f
+ };
+ uint8_t dataType;
+
+ // Structure of complex data values (TYPE_UNIT and TYPE_FRACTION)
+ enum {
+ // Where the unit type information is. This gives us 16 possible
+ // types, as defined below.
+ COMPLEX_UNIT_SHIFT = 0,
+ COMPLEX_UNIT_MASK = 0xf,
+
+ // TYPE_DIMENSION: Value is raw pixels.
+ COMPLEX_UNIT_PX = 0,
+ // TYPE_DIMENSION: Value is Device Independent Pixels.
+ COMPLEX_UNIT_DIP = 1,
+ // TYPE_DIMENSION: Value is a Scaled device independent Pixels.
+ COMPLEX_UNIT_SP = 2,
+ // TYPE_DIMENSION: Value is in points.
+ COMPLEX_UNIT_PT = 3,
+ // TYPE_DIMENSION: Value is in inches.
+ COMPLEX_UNIT_IN = 4,
+ // TYPE_DIMENSION: Value is in millimeters.
+ COMPLEX_UNIT_MM = 5,
+
+ // TYPE_FRACTION: A basic fraction of the overall size.
+ COMPLEX_UNIT_FRACTION = 0,
+ // TYPE_FRACTION: A fraction of the parent size.
+ COMPLEX_UNIT_FRACTION_PARENT = 1,
+
+ // Where the radix information is, telling where the decimal place
+ // appears in the mantissa. This give us 4 possible fixed point
+ // representations as defined below.
+ COMPLEX_RADIX_SHIFT = 4,
+ COMPLEX_RADIX_MASK = 0x3,
+
+ // The mantissa is an integral number -- i.e., 0xnnnnnn.0
+ COMPLEX_RADIX_23p0 = 0,
+ // The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
+ COMPLEX_RADIX_16p7 = 1,
+ // The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
+ COMPLEX_RADIX_8p15 = 2,
+ // The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
+ COMPLEX_RADIX_0p23 = 3,
+
+ // Where the actual value is. This gives us 23 bits of
+ // precision. The top bit is the sign.
+ COMPLEX_MANTISSA_SHIFT = 8,
+ COMPLEX_MANTISSA_MASK = 0xffffff
+ };
+
+ // The data for this item, as interpreted according to dataType.
+ uint32_t data;
+
+ void copyFrom_dtoh(const Res_value& src);
+};
+
+/**
+ * This is a reference to a unique entry (a ResTable_entry structure)
+ * in a resource table. The value is structured as: 0xpptteeee,
+ * where pp is the package index, tt is the type index in that
+ * package, and eeee is the entry index in that type. The package
+ * and type values start at 1 for the first item, to help catch cases
+ * where they have not been supplied.
+ */
+struct ResTable_ref
+{
+ uint32_t ident;
+};
+
+/**
+ * Reference to a string in a string pool.
+ */
+struct ResStringPool_ref
+{
+ // Index into the string pool table (uint32_t-offset from the indices
+ // immediately after ResStringPool_header) at which to find the location
+ // of the string data in the pool.
+ uint32_t index;
+};
+
+/** ********************************************************************
+ * String Pool
+ *
+ * A set of strings that can be references by others through a
+ * ResStringPool_ref.
+ *
+ *********************************************************************** */
+
+/**
+ * Definition for a pool of strings. The data of this chunk is an
+ * array of uint32_t providing indices into the pool, relative to
+ * stringsStart. At stringsStart are all of the UTF-16 strings
+ * concatenated together; each starts with a uint16_t of the string's
+ * length and each ends with a 0x0000 terminator. If a string is >
+ * 32767 characters, the high bit of the length is set meaning to take
+ * those 15 bits as a high word and it will be followed by another
+ * uint16_t containing the low word.
+ *
+ * If styleCount is not zero, then immediately following the array of
+ * uint32_t indices into the string table is another array of indices
+ * into a style table starting at stylesStart. Each entry in the
+ * style table is an array of ResStringPool_span structures.
+ */
+struct ResStringPool_header
+{
+ struct ResChunk_header header;
+
+ // Number of strings in this pool (number of uint32_t indices that follow
+ // in the data).
+ uint32_t stringCount;
+
+ // Number of style span arrays in the pool (number of uint32_t indices
+ // follow the string indices).
+ uint32_t styleCount;
+
+ // Flags.
+ enum {
+ // If set, the string index is sorted by the string values (based
+ // on strcmp16()).
+ SORTED_FLAG = 1<<0,
+
+ // String pool is encoded in UTF-8
+ UTF8_FLAG = 1<<8
+ };
+ uint32_t flags;
+
+ // Index from header of the string data.
+ uint32_t stringsStart;
+
+ // Index from header of the style data.
+ uint32_t stylesStart;
+};
+
+/**
+ * This structure defines a span of style information associated with
+ * a string in the pool.
+ */
+struct ResStringPool_span
+{
+ enum {
+ END = 0xFFFFFFFF
+ };
+
+ // This is the name of the span -- that is, the name of the XML
+ // tag that defined it. The special value END (0xFFFFFFFF) indicates
+ // the end of an array of spans.
+ ResStringPool_ref name;
+
+ // The range of characters in the string that this span applies to.
+ uint32_t firstChar, lastChar;
+};
+
+/**
+ * Convenience class for accessing data in a ResStringPool resource.
+ */
+class ResStringPool
+{
+public:
+ ResStringPool();
+ ResStringPool(const void* data, size_t size, bool copyData=false);
+ ~ResStringPool();
+
+ status_t setTo(const void* data, size_t size, bool copyData=false);
+
+ status_t getError() const;
+
+ void uninit();
+
+ // Return string entry as UTF16; if the pool is UTF8, the string will
+ // be converted before returning.
+ inline const char16_t* stringAt(const ResStringPool_ref& ref, size_t* outLen) const {
+ return stringAt(ref.index, outLen);
+ }
+ const char16_t* stringAt(size_t idx, size_t* outLen) const;
+
+ // Note: returns null if the string pool is not UTF8.
+ const char* string8At(size_t idx, size_t* outLen) const;
+
+ // Return string whether the pool is UTF8 or UTF16. Does not allow you
+ // to distinguish null.
+ const String8 string8ObjectAt(size_t idx) const;
+
+ const ResStringPool_span* styleAt(const ResStringPool_ref& ref) const;
+ const ResStringPool_span* styleAt(size_t idx) const;
+
+ ssize_t indexOfString(const char16_t* str, size_t strLen) const;
+
+ size_t size() const;
+ size_t styleCount() const;
+ size_t bytes() const;
+
+ bool isSorted() const;
+ bool isUTF8() const;
+
+private:
+ status_t mError;
+ void* mOwnedData;
+ const ResStringPool_header* mHeader;
+ size_t mSize;
+ mutable Mutex mDecodeLock;
+ const uint32_t* mEntries;
+ const uint32_t* mEntryStyles;
+ const void* mStrings;
+ char16_t mutable** mCache;
+ uint32_t mStringPoolSize; // number of uint16_t
+ const uint32_t* mStyles;
+ uint32_t mStylePoolSize; // number of uint32_t
+};
+
+/** ********************************************************************
+ * XML Tree
+ *
+ * Binary representation of an XML document. This is designed to
+ * express everything in an XML document, in a form that is much
+ * easier to parse on the device.
+ *
+ *********************************************************************** */
+
+/**
+ * XML tree header. This appears at the front of an XML tree,
+ * describing its content. It is followed by a flat array of
+ * ResXMLTree_node structures; the hierarchy of the XML document
+ * is described by the occurrance of RES_XML_START_ELEMENT_TYPE
+ * and corresponding RES_XML_END_ELEMENT_TYPE nodes in the array.
+ */
+struct ResXMLTree_header
+{
+ struct ResChunk_header header;
+};
+
+/**
+ * Basic XML tree node. A single item in the XML document. Extended info
+ * about the node can be found after header.headerSize.
+ */
+struct ResXMLTree_node
+{
+ struct ResChunk_header header;
+
+ // Line number in original source file at which this element appeared.
+ uint32_t lineNumber;
+
+ // Optional XML comment that was associated with this element; -1 if none.
+ struct ResStringPool_ref comment;
+};
+
+/**
+ * Extended XML tree node for CDATA tags -- includes the CDATA string.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+struct ResXMLTree_cdataExt
+{
+ // The raw CDATA character data.
+ struct ResStringPool_ref data;
+
+ // The typed value of the character data if this is a CDATA node.
+ struct Res_value typedData;
+};
+
+/**
+ * Extended XML tree node for namespace start/end nodes.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+struct ResXMLTree_namespaceExt
+{
+ // The prefix of the namespace.
+ struct ResStringPool_ref prefix;
+
+ // The URI of the namespace.
+ struct ResStringPool_ref uri;
+};
+
+/**
+ * Extended XML tree node for element start/end nodes.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+struct ResXMLTree_endElementExt
+{
+ // String of the full namespace of this element.
+ struct ResStringPool_ref ns;
+
+ // String name of this node if it is an ELEMENT; the raw
+ // character data if this is a CDATA node.
+ struct ResStringPool_ref name;
+};
+
+/**
+ * Extended XML tree node for start tags -- includes attribute
+ * information.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+struct ResXMLTree_attrExt
+{
+ // String of the full namespace of this element.
+ struct ResStringPool_ref ns;
+
+ // String name of this node if it is an ELEMENT; the raw
+ // character data if this is a CDATA node.
+ struct ResStringPool_ref name;
+
+ // Byte offset from the start of this structure where the attributes start.
+ uint16_t attributeStart;
+
+ // Size of the ResXMLTree_attribute structures that follow.
+ uint16_t attributeSize;
+
+ // Number of attributes associated with an ELEMENT. These are
+ // available as an array of ResXMLTree_attribute structures
+ // immediately following this node.
+ uint16_t attributeCount;
+
+ // Index (1-based) of the "id" attribute. 0 if none.
+ uint16_t idIndex;
+
+ // Index (1-based) of the "class" attribute. 0 if none.
+ uint16_t classIndex;
+
+ // Index (1-based) of the "style" attribute. 0 if none.
+ uint16_t styleIndex;
+};
+
+struct ResXMLTree_attribute
+{
+ // Namespace of this attribute.
+ struct ResStringPool_ref ns;
+
+ // Name of this attribute.
+ struct ResStringPool_ref name;
+
+ // The original raw string value of this attribute.
+ struct ResStringPool_ref rawValue;
+
+ // Processesd typed value of this attribute.
+ struct Res_value typedValue;
+};
+
+class ResXMLTree;
+
+class ResXMLParser
+{
+public:
+ ResXMLParser(const ResXMLTree& tree);
+
+ enum event_code_t {
+ BAD_DOCUMENT = -1,
+ START_DOCUMENT = 0,
+ END_DOCUMENT = 1,
+
+ FIRST_CHUNK_CODE = RES_XML_FIRST_CHUNK_TYPE,
+
+ START_NAMESPACE = RES_XML_START_NAMESPACE_TYPE,
+ END_NAMESPACE = RES_XML_END_NAMESPACE_TYPE,
+ START_TAG = RES_XML_START_ELEMENT_TYPE,
+ END_TAG = RES_XML_END_ELEMENT_TYPE,
+ TEXT = RES_XML_CDATA_TYPE
+ };
+
+ struct ResXMLPosition
+ {
+ event_code_t eventCode;
+ const ResXMLTree_node* curNode;
+ const void* curExt;
+ };
+
+ void restart();
+
+ const ResStringPool& getStrings() const;
+
+ event_code_t getEventType() const;
+ // Note, unlike XmlPullParser, the first call to next() will return
+ // START_TAG of the first element.
+ event_code_t next();
+
+ // These are available for all nodes:
+ int32_t getCommentID() const;
+ const uint16_t* getComment(size_t* outLen) const;
+ uint32_t getLineNumber() const;
+
+ // This is available for TEXT:
+ int32_t getTextID() const;
+ const uint16_t* getText(size_t* outLen) const;
+ ssize_t getTextValue(Res_value* outValue) const;
+
+ // These are available for START_NAMESPACE and END_NAMESPACE:
+ int32_t getNamespacePrefixID() const;
+ const uint16_t* getNamespacePrefix(size_t* outLen) const;
+ int32_t getNamespaceUriID() const;
+ const uint16_t* getNamespaceUri(size_t* outLen) const;
+
+ // These are available for START_TAG and END_TAG:
+ int32_t getElementNamespaceID() const;
+ const uint16_t* getElementNamespace(size_t* outLen) const;
+ int32_t getElementNameID() const;
+ const uint16_t* getElementName(size_t* outLen) const;
+
+ // Remaining methods are for retrieving information about attributes
+ // associated with a START_TAG:
+
+ size_t getAttributeCount() const;
+
+ // Returns -1 if no namespace, -2 if idx out of range.
+ int32_t getAttributeNamespaceID(size_t idx) const;
+ const uint16_t* getAttributeNamespace(size_t idx, size_t* outLen) const;
+
+ int32_t getAttributeNameID(size_t idx) const;
+ const uint16_t* getAttributeName(size_t idx, size_t* outLen) const;
+ uint32_t getAttributeNameResID(size_t idx) const;
+
+ // These will work only if the underlying string pool is UTF-8.
+ const char* getAttributeNamespace8(size_t idx, size_t* outLen) const;
+ const char* getAttributeName8(size_t idx, size_t* outLen) const;
+
+ int32_t getAttributeValueStringID(size_t idx) const;
+ const uint16_t* getAttributeStringValue(size_t idx, size_t* outLen) const;
+
+ int32_t getAttributeDataType(size_t idx) const;
+ int32_t getAttributeData(size_t idx) const;
+ ssize_t getAttributeValue(size_t idx, Res_value* outValue) const;
+
+ ssize_t indexOfAttribute(const char* ns, const char* attr) const;
+ ssize_t indexOfAttribute(const char16_t* ns, size_t nsLen,
+ const char16_t* attr, size_t attrLen) const;
+
+ ssize_t indexOfID() const;
+ ssize_t indexOfClass() const;
+ ssize_t indexOfStyle() const;
+
+ void getPosition(ResXMLPosition* pos) const;
+ void setPosition(const ResXMLPosition& pos);
+
+private:
+ friend class ResXMLTree;
+
+ event_code_t nextNode();
+
+ const ResXMLTree& mTree;
+ event_code_t mEventCode;
+ const ResXMLTree_node* mCurNode;
+ const void* mCurExt;
+};
+
+/**
+ * Convenience class for accessing data in a ResXMLTree resource.
+ */
+class ResXMLTree : public ResXMLParser
+{
+public:
+ ResXMLTree();
+ ResXMLTree(const void* data, size_t size, bool copyData=false);
+ ~ResXMLTree();
+
+ status_t setTo(const void* data, size_t size, bool copyData=false);
+
+ status_t getError() const;
+
+ void uninit();
+
+private:
+ friend class ResXMLParser;
+
+ status_t validateNode(const ResXMLTree_node* node) const;
+
+ status_t mError;
+ void* mOwnedData;
+ const ResXMLTree_header* mHeader;
+ size_t mSize;
+ const uint8_t* mDataEnd;
+ ResStringPool mStrings;
+ const uint32_t* mResIds;
+ size_t mNumResIds;
+ const ResXMLTree_node* mRootNode;
+ const void* mRootExt;
+ event_code_t mRootCode;
+};
+
+/** ********************************************************************
+ * RESOURCE TABLE
+ *
+ *********************************************************************** */
+
+/**
+ * Header for a resource table. Its data contains a series of
+ * additional chunks:
+ * * A ResStringPool_header containing all table values. This string pool
+ * contains all of the string values in the entire resource table (not
+ * the names of entries or type identifiers however).
+ * * One or more ResTable_package chunks.
+ *
+ * Specific entries within a resource table can be uniquely identified
+ * with a single integer as defined by the ResTable_ref structure.
+ */
+struct ResTable_header
+{
+ struct ResChunk_header header;
+
+ // The number of ResTable_package structures.
+ uint32_t packageCount;
+};
+
+/**
+ * A collection of resource data types within a package. Followed by
+ * one or more ResTable_type and ResTable_typeSpec structures containing the
+ * entry values for each resource type.
+ */
+struct ResTable_package
+{
+ struct ResChunk_header header;
+
+ // If this is a base package, its ID. Package IDs start
+ // at 1 (corresponding to the value of the package bits in a
+ // resource identifier). 0 means this is not a base package.
+ uint32_t id;
+
+ // Actual name of this package, \0-terminated.
+ char16_t name[128];
+
+ // Offset to a ResStringPool_header defining the resource
+ // type symbol table. If zero, this package is inheriting from
+ // another base package (overriding specific values in it).
+ uint32_t typeStrings;
+
+ // Last index into typeStrings that is for public use by others.
+ uint32_t lastPublicType;
+
+ // Offset to a ResStringPool_header defining the resource
+ // key symbol table. If zero, this package is inheriting from
+ // another base package (overriding specific values in it).
+ uint32_t keyStrings;
+
+ // Last index into keyStrings that is for public use by others.
+ uint32_t lastPublicKey;
+};
+
+/**
+ * Describes a particular resource configuration.
+ */
+struct ResTable_config
+{
+ // Number of bytes in this structure.
+ uint32_t size;
+
+ union {
+ struct {
+ // Mobile country code (from SIM). 0 means "any".
+ uint16_t mcc;
+ // Mobile network code (from SIM). 0 means "any".
+ uint16_t mnc;
+ };
+ uint32_t imsi;
+ };
+
+ union {
+ struct {
+ // \0\0 means "any". Otherwise, en, fr, etc.
+ char language[2];
+
+ // \0\0 means "any". Otherwise, US, CA, etc.
+ char country[2];
+ };
+ uint32_t locale;
+ };
+
+ enum {
+ ORIENTATION_ANY = ACONFIGURATION_ORIENTATION_ANY,
+ ORIENTATION_PORT = ACONFIGURATION_ORIENTATION_PORT,
+ ORIENTATION_LAND = ACONFIGURATION_ORIENTATION_LAND,
+ ORIENTATION_SQUARE = ACONFIGURATION_ORIENTATION_SQUARE,
+ };
+
+ enum {
+ TOUCHSCREEN_ANY = ACONFIGURATION_TOUCHSCREEN_ANY,
+ TOUCHSCREEN_NOTOUCH = ACONFIGURATION_TOUCHSCREEN_NOTOUCH,
+ TOUCHSCREEN_STYLUS = ACONFIGURATION_TOUCHSCREEN_STYLUS,
+ TOUCHSCREEN_FINGER = ACONFIGURATION_TOUCHSCREEN_FINGER,
+ };
+
+ enum {
+ DENSITY_DEFAULT = ACONFIGURATION_DENSITY_DEFAULT,
+ DENSITY_LOW = ACONFIGURATION_DENSITY_LOW,
+ DENSITY_MEDIUM = ACONFIGURATION_DENSITY_MEDIUM,
+ DENSITY_TV = ACONFIGURATION_DENSITY_TV,
+ DENSITY_HIGH = ACONFIGURATION_DENSITY_HIGH,
+ DENSITY_XHIGH = ACONFIGURATION_DENSITY_XHIGH,
+ DENSITY_XXHIGH = ACONFIGURATION_DENSITY_XXHIGH,
+ DENSITY_XXXHIGH = ACONFIGURATION_DENSITY_XXXHIGH,
+ DENSITY_NONE = ACONFIGURATION_DENSITY_NONE
+ };
+
+ union {
+ struct {
+ uint8_t orientation;
+ uint8_t touchscreen;
+ uint16_t density;
+ };
+ uint32_t screenType;
+ };
+
+ enum {
+ KEYBOARD_ANY = ACONFIGURATION_KEYBOARD_ANY,
+ KEYBOARD_NOKEYS = ACONFIGURATION_KEYBOARD_NOKEYS,
+ KEYBOARD_QWERTY = ACONFIGURATION_KEYBOARD_QWERTY,
+ KEYBOARD_12KEY = ACONFIGURATION_KEYBOARD_12KEY,
+ };
+
+ enum {
+ NAVIGATION_ANY = ACONFIGURATION_NAVIGATION_ANY,
+ NAVIGATION_NONAV = ACONFIGURATION_NAVIGATION_NONAV,
+ NAVIGATION_DPAD = ACONFIGURATION_NAVIGATION_DPAD,
+ NAVIGATION_TRACKBALL = ACONFIGURATION_NAVIGATION_TRACKBALL,
+ NAVIGATION_WHEEL = ACONFIGURATION_NAVIGATION_WHEEL,
+ };
+
+ enum {
+ MASK_KEYSHIDDEN = 0x0003,
+ KEYSHIDDEN_ANY = ACONFIGURATION_KEYSHIDDEN_ANY,
+ KEYSHIDDEN_NO = ACONFIGURATION_KEYSHIDDEN_NO,
+ KEYSHIDDEN_YES = ACONFIGURATION_KEYSHIDDEN_YES,
+ KEYSHIDDEN_SOFT = ACONFIGURATION_KEYSHIDDEN_SOFT,
+ };
+
+ enum {
+ MASK_NAVHIDDEN = 0x000c,
+ SHIFT_NAVHIDDEN = 2,
+ NAVHIDDEN_ANY = ACONFIGURATION_NAVHIDDEN_ANY << SHIFT_NAVHIDDEN,
+ NAVHIDDEN_NO = ACONFIGURATION_NAVHIDDEN_NO << SHIFT_NAVHIDDEN,
+ NAVHIDDEN_YES = ACONFIGURATION_NAVHIDDEN_YES << SHIFT_NAVHIDDEN,
+ };
+
+ union {
+ struct {
+ uint8_t keyboard;
+ uint8_t navigation;
+ uint8_t inputFlags;
+ uint8_t inputPad0;
+ };
+ uint32_t input;
+ };
+
+ enum {
+ SCREENWIDTH_ANY = 0
+ };
+
+ enum {
+ SCREENHEIGHT_ANY = 0
+ };
+
+ union {
+ struct {
+ uint16_t screenWidth;
+ uint16_t screenHeight;
+ };
+ uint32_t screenSize;
+ };
+
+ enum {
+ SDKVERSION_ANY = 0
+ };
+
+ enum {
+ MINORVERSION_ANY = 0
+ };
+
+ union {
+ struct {
+ uint16_t sdkVersion;
+ // For now minorVersion must always be 0!!! Its meaning
+ // is currently undefined.
+ uint16_t minorVersion;
+ };
+ uint32_t version;
+ };
+
+ enum {
+ // screenLayout bits for screen size class.
+ MASK_SCREENSIZE = 0x0f,
+ SCREENSIZE_ANY = ACONFIGURATION_SCREENSIZE_ANY,
+ SCREENSIZE_SMALL = ACONFIGURATION_SCREENSIZE_SMALL,
+ SCREENSIZE_NORMAL = ACONFIGURATION_SCREENSIZE_NORMAL,
+ SCREENSIZE_LARGE = ACONFIGURATION_SCREENSIZE_LARGE,
+ SCREENSIZE_XLARGE = ACONFIGURATION_SCREENSIZE_XLARGE,
+
+ // screenLayout bits for wide/long screen variation.
+ MASK_SCREENLONG = 0x30,
+ SHIFT_SCREENLONG = 4,
+ SCREENLONG_ANY = ACONFIGURATION_SCREENLONG_ANY << SHIFT_SCREENLONG,
+ SCREENLONG_NO = ACONFIGURATION_SCREENLONG_NO << SHIFT_SCREENLONG,
+ SCREENLONG_YES = ACONFIGURATION_SCREENLONG_YES << SHIFT_SCREENLONG,
+
+ // screenLayout bits for layout direction.
+ MASK_LAYOUTDIR = 0xC0,
+ SHIFT_LAYOUTDIR = 6,
+ LAYOUTDIR_ANY = ACONFIGURATION_LAYOUTDIR_ANY << SHIFT_LAYOUTDIR,
+ LAYOUTDIR_LTR = ACONFIGURATION_LAYOUTDIR_LTR << SHIFT_LAYOUTDIR,
+ LAYOUTDIR_RTL = ACONFIGURATION_LAYOUTDIR_RTL << SHIFT_LAYOUTDIR,
+ };
+
+ enum {
+ // uiMode bits for the mode type.
+ MASK_UI_MODE_TYPE = 0x0f,
+ UI_MODE_TYPE_ANY = ACONFIGURATION_UI_MODE_TYPE_ANY,
+ UI_MODE_TYPE_NORMAL = ACONFIGURATION_UI_MODE_TYPE_NORMAL,
+ UI_MODE_TYPE_DESK = ACONFIGURATION_UI_MODE_TYPE_DESK,
+ UI_MODE_TYPE_CAR = ACONFIGURATION_UI_MODE_TYPE_CAR,
+ UI_MODE_TYPE_TELEVISION = ACONFIGURATION_UI_MODE_TYPE_TELEVISION,
+ UI_MODE_TYPE_APPLIANCE = ACONFIGURATION_UI_MODE_TYPE_APPLIANCE,
+
+ // uiMode bits for the night switch.
+ MASK_UI_MODE_NIGHT = 0x30,
+ SHIFT_UI_MODE_NIGHT = 4,
+ UI_MODE_NIGHT_ANY = ACONFIGURATION_UI_MODE_NIGHT_ANY << SHIFT_UI_MODE_NIGHT,
+ UI_MODE_NIGHT_NO = ACONFIGURATION_UI_MODE_NIGHT_NO << SHIFT_UI_MODE_NIGHT,
+ UI_MODE_NIGHT_YES = ACONFIGURATION_UI_MODE_NIGHT_YES << SHIFT_UI_MODE_NIGHT,
+ };
+
+ union {
+ struct {
+ uint8_t screenLayout;
+ uint8_t uiMode;
+ uint16_t smallestScreenWidthDp;
+ };
+ uint32_t screenConfig;
+ };
+
+ union {
+ struct {
+ uint16_t screenWidthDp;
+ uint16_t screenHeightDp;
+ };
+ uint32_t screenSizeDp;
+ };
+
+ void copyFromDeviceNoSwap(const ResTable_config& o);
+
+ void copyFromDtoH(const ResTable_config& o);
+
+ void swapHtoD();
+
+ int compare(const ResTable_config& o) const;
+ int compareLogical(const ResTable_config& o) const;
+
+ // Flags indicating a set of config values. These flag constants must
+ // match the corresponding ones in android.content.pm.ActivityInfo and
+ // attrs_manifest.xml.
+ enum {
+ CONFIG_MCC = ACONFIGURATION_MCC,
+ CONFIG_MNC = ACONFIGURATION_MNC,
+ CONFIG_LOCALE = ACONFIGURATION_LOCALE,
+ CONFIG_TOUCHSCREEN = ACONFIGURATION_TOUCHSCREEN,
+ CONFIG_KEYBOARD = ACONFIGURATION_KEYBOARD,
+ CONFIG_KEYBOARD_HIDDEN = ACONFIGURATION_KEYBOARD_HIDDEN,
+ CONFIG_NAVIGATION = ACONFIGURATION_NAVIGATION,
+ CONFIG_ORIENTATION = ACONFIGURATION_ORIENTATION,
+ CONFIG_DENSITY = ACONFIGURATION_DENSITY,
+ CONFIG_SCREEN_SIZE = ACONFIGURATION_SCREEN_SIZE,
+ CONFIG_SMALLEST_SCREEN_SIZE = ACONFIGURATION_SMALLEST_SCREEN_SIZE,
+ CONFIG_VERSION = ACONFIGURATION_VERSION,
+ CONFIG_SCREEN_LAYOUT = ACONFIGURATION_SCREEN_LAYOUT,
+ CONFIG_UI_MODE = ACONFIGURATION_UI_MODE,
+ CONFIG_LAYOUTDIR = ACONFIGURATION_LAYOUTDIR,
+ };
+
+ // Compare two configuration, returning CONFIG_* flags set for each value
+ // that is different.
+ int diff(const ResTable_config& o) const;
+
+ // Return true if 'this' is more specific than 'o'.
+ bool isMoreSpecificThan(const ResTable_config& o) const;
+
+ // Return true if 'this' is a better match than 'o' for the 'requested'
+ // configuration. This assumes that match() has already been used to
+ // remove any configurations that don't match the requested configuration
+ // at all; if they are not first filtered, non-matching results can be
+ // considered better than matching ones.
+ // The general rule per attribute: if the request cares about an attribute
+ // (it normally does), if the two (this and o) are equal it's a tie. If
+ // they are not equal then one must be generic because only generic and
+ // '==requested' will pass the match() call. So if this is not generic,
+ // it wins. If this IS generic, o wins (return false).
+ bool isBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
+
+ // Return true if 'this' can be considered a match for the parameters in
+ // 'settings'.
+ // Note this is asymetric. A default piece of data will match every request
+ // but a request for the default should not match odd specifics
+ // (ie, request with no mcc should not match a particular mcc's data)
+ // settings is the requested settings
+ bool match(const ResTable_config& settings) const;
+
+ void getLocale(char str[6]) const;
+
+ String8 toString() const;
+};
+
+/**
+ * A specification of the resources defined by a particular type.
+ *
+ * There should be one of these chunks for each resource type.
+ *
+ * This structure is followed by an array of integers providing the set of
+ * configuration change flags (ResTable_config::CONFIG_*) that have multiple
+ * resources for that configuration. In addition, the high bit is set if that
+ * resource has been made public.
+ */
+struct ResTable_typeSpec
+{
+ struct ResChunk_header header;
+
+ // The type identifier this chunk is holding. Type IDs start
+ // at 1 (corresponding to the value of the type bits in a
+ // resource identifier). 0 is invalid.
+ uint8_t id;
+
+ // Must be 0.
+ uint8_t res0;
+ // Must be 0.
+ uint16_t res1;
+
+ // Number of uint32_t entry configuration masks that follow.
+ uint32_t entryCount;
+
+ enum {
+ // Additional flag indicating an entry is public.
+ SPEC_PUBLIC = 0x40000000
+ };
+};
+
+/**
+ * A collection of resource entries for a particular resource data
+ * type. Followed by an array of uint32_t defining the resource
+ * values, corresponding to the array of type strings in the
+ * ResTable_package::typeStrings string block. Each of these hold an
+ * index from entriesStart; a value of NO_ENTRY means that entry is
+ * not defined.
+ *
+ * There may be multiple of these chunks for a particular resource type,
+ * supply different configuration variations for the resource values of
+ * that type.
+ *
+ * It would be nice to have an additional ordered index of entries, so
+ * we can do a binary search if trying to find a resource by string name.
+ */
+struct ResTable_type
+{
+ struct ResChunk_header header;
+
+ enum {
+ NO_ENTRY = 0xFFFFFFFF
+ };
+
+ // The type identifier this chunk is holding. Type IDs start
+ // at 1 (corresponding to the value of the type bits in a
+ // resource identifier). 0 is invalid.
+ uint8_t id;
+
+ // Must be 0.
+ uint8_t res0;
+ // Must be 0.
+ uint16_t res1;
+
+ // Number of uint32_t entry indices that follow.
+ uint32_t entryCount;
+
+ // Offset from header where ResTable_entry data starts.
+ uint32_t entriesStart;
+
+ // Configuration this collection of entries is designed for.
+ ResTable_config config;
+};
+
+/**
+ * This is the beginning of information about an entry in the resource
+ * table. It holds the reference to the name of this entry, and is
+ * immediately followed by one of:
+ * * A Res_value structure, if FLAG_COMPLEX is -not- set.
+ * * An array of ResTable_map structures, if FLAG_COMPLEX is set.
+ * These supply a set of name/value mappings of data.
+ */
+struct ResTable_entry
+{
+ // Number of bytes in this structure.
+ uint16_t size;
+
+ enum {
+ // If set, this is a complex entry, holding a set of name/value
+ // mappings. It is followed by an array of ResTable_map structures.
+ FLAG_COMPLEX = 0x0001,
+ // If set, this resource has been declared public, so libraries
+ // are allowed to reference it.
+ FLAG_PUBLIC = 0x0002
+ };
+ uint16_t flags;
+
+ // Reference into ResTable_package::keyStrings identifying this entry.
+ struct ResStringPool_ref key;
+};
+
+/**
+ * Extended form of a ResTable_entry for map entries, defining a parent map
+ * resource from which to inherit values.
+ */
+struct ResTable_map_entry : public ResTable_entry
+{
+ // Resource identifier of the parent mapping, or 0 if there is none.
+ ResTable_ref parent;
+ // Number of name/value pairs that follow for FLAG_COMPLEX.
+ uint32_t count;
+};
+
+/**
+ * A single name/value mapping that is part of a complex resource
+ * entry.
+ */
+struct ResTable_map
+{
+ // The resource identifier defining this mapping's name. For attribute
+ // resources, 'name' can be one of the following special resource types
+ // to supply meta-data about the attribute; for all other resource types
+ // it must be an attribute resource.
+ ResTable_ref name;
+
+ // Special values for 'name' when defining attribute resources.
+ enum {
+ // This entry holds the attribute's type code.
+ ATTR_TYPE = Res_MAKEINTERNAL(0),
+
+ // For integral attributes, this is the minimum value it can hold.
+ ATTR_MIN = Res_MAKEINTERNAL(1),
+
+ // For integral attributes, this is the maximum value it can hold.
+ ATTR_MAX = Res_MAKEINTERNAL(2),
+
+ // Localization of this resource is can be encouraged or required with
+ // an aapt flag if this is set
+ ATTR_L10N = Res_MAKEINTERNAL(3),
+
+ // for plural support, see android.content.res.PluralRules#attrForQuantity(int)
+ ATTR_OTHER = Res_MAKEINTERNAL(4),
+ ATTR_ZERO = Res_MAKEINTERNAL(5),
+ ATTR_ONE = Res_MAKEINTERNAL(6),
+ ATTR_TWO = Res_MAKEINTERNAL(7),
+ ATTR_FEW = Res_MAKEINTERNAL(8),
+ ATTR_MANY = Res_MAKEINTERNAL(9)
+
+ };
+
+ // Bit mask of allowed types, for use with ATTR_TYPE.
+ enum {
+ // No type has been defined for this attribute, use generic
+ // type handling. The low 16 bits are for types that can be
+ // handled generically; the upper 16 require additional information
+ // in the bag so can not be handled generically for TYPE_ANY.
+ TYPE_ANY = 0x0000FFFF,
+
+ // Attribute holds a references to another resource.
+ TYPE_REFERENCE = 1<<0,
+
+ // Attribute holds a generic string.
+ TYPE_STRING = 1<<1,
+
+ // Attribute holds an integer value. ATTR_MIN and ATTR_MIN can
+ // optionally specify a constrained range of possible integer values.
+ TYPE_INTEGER = 1<<2,
+
+ // Attribute holds a boolean integer.
+ TYPE_BOOLEAN = 1<<3,
+
+ // Attribute holds a color value.
+ TYPE_COLOR = 1<<4,
+
+ // Attribute holds a floating point value.
+ TYPE_FLOAT = 1<<5,
+
+ // Attribute holds a dimension value, such as "20px".
+ TYPE_DIMENSION = 1<<6,
+
+ // Attribute holds a fraction value, such as "20%".
+ TYPE_FRACTION = 1<<7,
+
+ // Attribute holds an enumeration. The enumeration values are
+ // supplied as additional entries in the map.
+ TYPE_ENUM = 1<<16,
+
+ // Attribute holds a bitmaks of flags. The flag bit values are
+ // supplied as additional entries in the map.
+ TYPE_FLAGS = 1<<17
+ };
+
+ // Enum of localization modes, for use with ATTR_L10N.
+ enum {
+ L10N_NOT_REQUIRED = 0,
+ L10N_SUGGESTED = 1
+ };
+
+ // This mapping's value.
+ Res_value value;
+};
+
+/**
+ * Convenience class for accessing data in a ResTable resource.
+ */
+class ResTable
+{
+public:
+ ResTable();
+ ResTable(const void* data, size_t size, void* cookie,
+ bool copyData=false);
+ ~ResTable();
+
+ status_t add(const void* data, size_t size, void* cookie,
+ bool copyData=false, const void* idmap = NULL);
+ status_t add(Asset* asset, void* cookie,
+ bool copyData=false, const void* idmap = NULL);
+ status_t add(ResTable* src);
+
+ status_t getError() const;
+
+ void uninit();
+
+ struct resource_name
+ {
+ const char16_t* package;
+ size_t packageLen;
+ const char16_t* type;
+ const char* type8;
+ size_t typeLen;
+ const char16_t* name;
+ const char* name8;
+ size_t nameLen;
+ };
+
+ bool getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const;
+
+ /**
+ * Retrieve the value of a resource. If the resource is found, returns a
+ * value >= 0 indicating the table it is in (for use with
+ * getTableStringBlock() and getTableCookie()) and fills in 'outValue'. If
+ * not found, returns a negative error code.
+ *
+ * Note that this function does not do reference traversal. If you want
+ * to follow references to other resources to get the "real" value to
+ * use, you need to call resolveReference() after this function.
+ *
+ * @param resID The desired resoruce identifier.
+ * @param outValue Filled in with the resource data that was found.
+ *
+ * @return ssize_t Either a >= 0 table index or a negative error code.
+ */
+ ssize_t getResource(uint32_t resID, Res_value* outValue, bool mayBeBag = false,
+ uint16_t density = 0,
+ uint32_t* outSpecFlags = NULL,
+ ResTable_config* outConfig = NULL) const;
+
+ inline ssize_t getResource(const ResTable_ref& res, Res_value* outValue,
+ uint32_t* outSpecFlags=NULL) const {
+ return getResource(res.ident, outValue, false, 0, outSpecFlags, NULL);
+ }
+
+ ssize_t resolveReference(Res_value* inOutValue,
+ ssize_t blockIndex,
+ uint32_t* outLastRef = NULL,
+ uint32_t* inoutTypeSpecFlags = NULL,
+ ResTable_config* outConfig = NULL) const;
+
+ enum {
+ TMP_BUFFER_SIZE = 16
+ };
+ const char16_t* valueToString(const Res_value* value, size_t stringBlock,
+ char16_t tmpBuffer[TMP_BUFFER_SIZE],
+ size_t* outLen);
+
+ struct bag_entry {
+ ssize_t stringBlock;
+ ResTable_map map;
+ };
+
+ /**
+ * Retrieve the bag of a resource. If the resoruce is found, returns the
+ * number of bags it contains and 'outBag' points to an array of their
+ * values. If not found, a negative error code is returned.
+ *
+ * Note that this function -does- do reference traversal of the bag data.
+ *
+ * @param resID The desired resource identifier.
+ * @param outBag Filled inm with a pointer to the bag mappings.
+ *
+ * @return ssize_t Either a >= 0 bag count of negative error code.
+ */
+ ssize_t lockBag(uint32_t resID, const bag_entry** outBag) const;
+
+ void unlockBag(const bag_entry* bag) const;
+
+ void lock() const;
+
+ ssize_t getBagLocked(uint32_t resID, const bag_entry** outBag,
+ uint32_t* outTypeSpecFlags=NULL) const;
+
+ void unlock() const;
+
+ class Theme {
+ public:
+ Theme(const ResTable& table);
+ ~Theme();
+
+ inline const ResTable& getResTable() const { return mTable; }
+
+ status_t applyStyle(uint32_t resID, bool force=false);
+ status_t setTo(const Theme& other);
+
+ /**
+ * Retrieve a value in the theme. If the theme defines this
+ * value, returns a value >= 0 indicating the table it is in
+ * (for use with getTableStringBlock() and getTableCookie) and
+ * fills in 'outValue'. If not found, returns a negative error
+ * code.
+ *
+ * Note that this function does not do reference traversal. If you want
+ * to follow references to other resources to get the "real" value to
+ * use, you need to call resolveReference() after this function.
+ *
+ * @param resID A resource identifier naming the desired theme
+ * attribute.
+ * @param outValue Filled in with the theme value that was
+ * found.
+ *
+ * @return ssize_t Either a >= 0 table index or a negative error code.
+ */
+ ssize_t getAttribute(uint32_t resID, Res_value* outValue,
+ uint32_t* outTypeSpecFlags = NULL) const;
+
+ /**
+ * This is like ResTable::resolveReference(), but also takes
+ * care of resolving attribute references to the theme.
+ */
+ ssize_t resolveAttributeReference(Res_value* inOutValue,
+ ssize_t blockIndex, uint32_t* outLastRef = NULL,
+ uint32_t* inoutTypeSpecFlags = NULL,
+ ResTable_config* inoutConfig = NULL) const;
+
+ void dumpToLog() const;
+
+ private:
+ Theme(const Theme&);
+ Theme& operator=(const Theme&);
+
+ struct theme_entry {
+ ssize_t stringBlock;
+ uint32_t typeSpecFlags;
+ Res_value value;
+ };
+ struct type_info {
+ size_t numEntries;
+ theme_entry* entries;
+ };
+ struct package_info {
+ size_t numTypes;
+ type_info types[];
+ };
+
+ void free_package(package_info* pi);
+ package_info* copy_package(package_info* pi);
+
+ const ResTable& mTable;
+ package_info* mPackages[Res_MAXPACKAGE];
+ };
+
+ void setParameters(const ResTable_config* params);
+ void getParameters(ResTable_config* params) const;
+
+ // Retrieve an identifier (which can be passed to getResource)
+ // for a given resource name. The 'name' can be fully qualified
+ // (<package>:<type>.<basename>) or the package or type components
+ // can be dropped if default values are supplied here.
+ //
+ // Returns 0 if no such resource was found, else a valid resource ID.
+ uint32_t identifierForName(const char16_t* name, size_t nameLen,
+ const char16_t* type = 0, size_t typeLen = 0,
+ const char16_t* defPackage = 0,
+ size_t defPackageLen = 0,
+ uint32_t* outTypeSpecFlags = NULL) const;
+
+ static bool expandResourceRef(const uint16_t* refStr, size_t refLen,
+ String16* outPackage,
+ String16* outType,
+ String16* outName,
+ const String16* defType = NULL,
+ const String16* defPackage = NULL,
+ const char** outErrorMsg = NULL,
+ bool* outPublicOnly = NULL);
+
+ static bool stringToInt(const char16_t* s, size_t len, Res_value* outValue);
+ static bool stringToFloat(const char16_t* s, size_t len, Res_value* outValue);
+
+ // Used with stringToValue.
+ class Accessor
+ {
+ public:
+ inline virtual ~Accessor() { }
+
+ virtual uint32_t getCustomResource(const String16& package,
+ const String16& type,
+ const String16& name) const = 0;
+ virtual uint32_t getCustomResourceWithCreation(const String16& package,
+ const String16& type,
+ const String16& name,
+ const bool createIfNeeded = false) = 0;
+ virtual uint32_t getRemappedPackage(uint32_t origPackage) const = 0;
+ virtual bool getAttributeType(uint32_t attrID, uint32_t* outType) = 0;
+ virtual bool getAttributeMin(uint32_t attrID, uint32_t* outMin) = 0;
+ virtual bool getAttributeMax(uint32_t attrID, uint32_t* outMax) = 0;
+ virtual bool getAttributeEnum(uint32_t attrID,
+ const char16_t* name, size_t nameLen,
+ Res_value* outValue) = 0;
+ virtual bool getAttributeFlags(uint32_t attrID,
+ const char16_t* name, size_t nameLen,
+ Res_value* outValue) = 0;
+ virtual uint32_t getAttributeL10N(uint32_t attrID) = 0;
+ virtual bool getLocalizationSetting() = 0;
+ virtual void reportError(void* accessorCookie, const char* fmt, ...) = 0;
+ };
+
+ // Convert a string to a resource value. Handles standard "@res",
+ // "#color", "123", and "0x1bd" types; performs escaping of strings.
+ // The resulting value is placed in 'outValue'; if it is a string type,
+ // 'outString' receives the string. If 'attrID' is supplied, the value is
+ // type checked against this attribute and it is used to perform enum
+ // evaluation. If 'acccessor' is supplied, it will be used to attempt to
+ // resolve resources that do not exist in this ResTable. If 'attrType' is
+ // supplied, the value will be type checked for this format if 'attrID'
+ // is not supplied or found.
+ bool stringToValue(Res_value* outValue, String16* outString,
+ const char16_t* s, size_t len,
+ bool preserveSpaces, bool coerceType,
+ uint32_t attrID = 0,
+ const String16* defType = NULL,
+ const String16* defPackage = NULL,
+ Accessor* accessor = NULL,
+ void* accessorCookie = NULL,
+ uint32_t attrType = ResTable_map::TYPE_ANY,
+ bool enforcePrivate = true) const;
+
+ // Perform processing of escapes and quotes in a string.
+ static bool collectString(String16* outString,
+ const char16_t* s, size_t len,
+ bool preserveSpaces,
+ const char** outErrorMsg = NULL,
+ bool append = false);
+
+ size_t getBasePackageCount() const;
+ const char16_t* getBasePackageName(size_t idx) const;
+ uint32_t getBasePackageId(size_t idx) const;
+
+ // Return the number of resource tables that the object contains.
+ size_t getTableCount() const;
+ // Return the values string pool for the resource table at the given
+ // index. This string pool contains all of the strings for values
+ // contained in the resource table -- that is the item values themselves,
+ // but not the names their entries or types.
+ const ResStringPool* getTableStringBlock(size_t index) const;
+ // Return unique cookie identifier for the given resource table.
+ void* getTableCookie(size_t index) const;
+
+ // Return the configurations (ResTable_config) that we know about
+ void getConfigurations(Vector<ResTable_config>* configs) const;
+
+ void getLocales(Vector<String8>* locales) const;
+
+ // Generate an idmap.
+ //
+ // Return value: on success: NO_ERROR; caller is responsible for free-ing
+ // outData (using free(3)). On failure, any status_t value other than
+ // NO_ERROR; the caller should not free outData.
+ status_t createIdmap(const ResTable& overlay, uint32_t originalCrc, uint32_t overlayCrc,
+ void** outData, size_t* outSize) const;
+
+ enum {
+ IDMAP_HEADER_SIZE_BYTES = 3 * sizeof(uint32_t),
+ };
+ // Retrieve idmap meta-data.
+ //
+ // This function only requires the idmap header (the first
+ // IDMAP_HEADER_SIZE_BYTES) bytes of an idmap file.
+ static bool getIdmapInfo(const void* idmap, size_t size,
+ uint32_t* pOriginalCrc, uint32_t* pOverlayCrc);
+
+ void print(bool inclValues) const;
+ static String8 normalizeForOutput(const char* input);
+
+private:
+ struct Header;
+ struct Type;
+ struct Package;
+ struct PackageGroup;
+ struct bag_set;
+
+ status_t add(const void* data, size_t size, void* cookie,
+ Asset* asset, bool copyData, const Asset* idmap);
+
+ ssize_t getResourcePackageIndex(uint32_t resID) const;
+ ssize_t getEntry(
+ const Package* package, int typeIndex, int entryIndex,
+ const ResTable_config* config,
+ const ResTable_type** outType, const ResTable_entry** outEntry,
+ const Type** outTypeClass) const;
+ status_t parsePackage(
+ const ResTable_package* const pkg, const Header* const header, uint32_t idmap_id);
+
+ void print_value(const Package* pkg, const Res_value& value) const;
+
+ mutable Mutex mLock;
+
+ status_t mError;
+
+ ResTable_config mParams;
+
+ // Array of all resource tables.
+ Vector<Header*> mHeaders;
+
+ // Array of packages in all resource tables.
+ Vector<PackageGroup*> mPackageGroups;
+
+ // Mapping from resource package IDs to indices into the internal
+ // package array.
+ uint8_t mPackageMap[256];
+};
+
+} // namespace android
+
+#endif // _LIBS_UTILS_RESOURCE_TYPES_H
diff --git a/include/androidfw/StreamingZipInflater.h b/include/androidfw/StreamingZipInflater.h
new file mode 100644
index 0000000..3ace5d5
--- /dev/null
+++ b/include/androidfw/StreamingZipInflater.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef __LIBS_STREAMINGZIPINFLATER_H
+#define __LIBS_STREAMINGZIPINFLATER_H
+
+#include <unistd.h>
+#include <inttypes.h>
+#include <zlib.h>
+
+#include <utils/Compat.h>
+
+namespace android {
+
+class StreamingZipInflater {
+public:
+ static const size_t INPUT_CHUNK_SIZE = 64 * 1024;
+ static const size_t OUTPUT_CHUNK_SIZE = 64 * 1024;
+
+ // Flavor that pages in the compressed data from a fd
+ StreamingZipInflater(int fd, off64_t compDataStart, size_t uncompSize, size_t compSize);
+
+ // Flavor that gets the compressed data from an in-memory buffer
+ StreamingZipInflater(class FileMap* dataMap, size_t uncompSize);
+
+ ~StreamingZipInflater();
+
+ // read 'count' bytes of uncompressed data from the current position. outBuf may
+ // be NULL, in which case the data is consumed and discarded.
+ ssize_t read(void* outBuf, size_t count);
+
+ // seeking backwards requires uncompressing fom the beginning, so is very
+ // expensive. seeking forwards only requires uncompressing from the current
+ // position to the destination.
+ off64_t seekAbsolute(off64_t absoluteInputPosition);
+
+private:
+ void initInflateState();
+ int readNextChunk();
+
+ // where to find the uncompressed data
+ int mFd;
+ off64_t mInFileStart; // where the compressed data lives in the file
+ class FileMap* mDataMap;
+
+ z_stream mInflateState;
+ bool mStreamNeedsInit;
+
+ // output invariants for this asset
+ uint8_t* mOutBuf; // output buf for decompressed bytes
+ size_t mOutBufSize; // allocated size of mOutBuf
+ size_t mOutTotalSize; // total uncompressed size of the blob
+
+ // current output state bookkeeping
+ off64_t mOutCurPosition; // current position in total offset
+ size_t mOutLastDecoded; // last decoded byte + 1 in mOutbuf
+ size_t mOutDeliverable; // next undelivered byte of decoded output in mOutBuf
+
+ // input invariants
+ uint8_t* mInBuf;
+ size_t mInBufSize; // allocated size of mInBuf;
+ size_t mInTotalSize; // total size of compressed data for this blob
+
+ // input state bookkeeping
+ size_t mInNextChunkOffset; // offset from start of blob at which the next input chunk lies
+ // the z_stream contains state about input block consumption
+};
+
+}
+
+#endif
diff --git a/include/androidfw/ZipFileRO.h b/include/androidfw/ZipFileRO.h
new file mode 100644
index 0000000..ad5be12
--- /dev/null
+++ b/include/androidfw/ZipFileRO.h
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+/*
+ * Read-only access to Zip archives, with minimal heap allocation.
+ *
+ * This is similar to the more-complete ZipFile class, but no attempt
+ * has been made to make them interchangeable. This class operates under
+ * a very different set of assumptions and constraints.
+ *
+ * One such assumption is that if you're getting file descriptors for
+ * use with this class as a child of a fork() operation, you must be on
+ * a pread() to guarantee correct operation. This is because pread() can
+ * atomically read at a file offset without worrying about a lock around an
+ * lseek() + read() pair.
+ */
+#ifndef __LIBS_ZIPFILERO_H
+#define __LIBS_ZIPFILERO_H
+
+#include <utils/Compat.h>
+#include <utils/Errors.h>
+#include <utils/FileMap.h>
+#include <utils/threads.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+typedef void* ZipArchiveHandle;
+
+namespace android {
+
+/*
+ * Trivial typedef to ensure that ZipEntryRO is not treated as a simple
+ * integer. We use NULL to indicate an invalid value.
+ */
+typedef void* ZipEntryRO;
+
+/*
+ * Open a Zip archive for reading.
+ *
+ * Implemented as a thin wrapper over system/core/libziparchive.
+ *
+ * "open" and "find entry by name" are fast operations and use as little
+ * memory as possible.
+ *
+ * We also support fast iteration over all entries in the file (with a
+ * stable, but unspecified iteration order).
+ *
+ * NOTE: If this is used on file descriptors inherited from a fork() operation,
+ * you must be on a platform that implements pread() to guarantee correctness
+ * on the shared file descriptors.
+ */
+class ZipFileRO {
+public:
+ /* Zip compression methods we support */
+ enum {
+ kCompressStored = 0, // no compression
+ kCompressDeflated = 8, // standard deflate
+ };
+
+ /*
+ * Open an archive.
+ */
+ static ZipFileRO* open(const char* zipFileName);
+
+ /*
+ * Find an entry, by name. Returns the entry identifier, or NULL if
+ * not found.
+ */
+ ZipEntryRO findEntryByName(const char* entryName) const;
+
+
+ /*
+ * Start iterating over the list of entries in the zip file. Requires
+ * a matching call to endIteration with the same cookie.
+ */
+ bool startIteration(void** cookie);
+
+ /**
+ * Return the next entry in iteration order, or NULL if there are no more
+ * entries in this archive.
+ */
+ ZipEntryRO nextEntry(void* cookie);
+
+ void endIteration(void* cookie);
+
+ void releaseEntry(ZipEntryRO entry) const;
+
+ /*
+ * Return the #of entries in the Zip archive.
+ */
+ int getNumEntries();
+
+ /*
+ * Copy the filename into the supplied buffer. Returns 0 on success,
+ * -1 if "entry" is invalid, or the filename length if it didn't fit. The
+ * length, and the returned string, include the null-termination.
+ */
+ int getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen) const;
+
+ /*
+ * Get the vital stats for an entry. Pass in NULL pointers for anything
+ * you don't need.
+ *
+ * "*pOffset" holds the Zip file offset of the entry's data.
+ *
+ * Returns "false" if "entry" is bogus or if the data in the Zip file
+ * appears to be bad.
+ */
+ bool getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
+ size_t* pCompLen, off64_t* pOffset, long* pModWhen, long* pCrc32) const;
+
+ /*
+ * Create a new FileMap object that maps a subset of the archive. For
+ * an uncompressed entry this effectively provides a pointer to the
+ * actual data, for a compressed entry this provides the input buffer
+ * for inflate().
+ */
+ FileMap* createEntryFileMap(ZipEntryRO entry) const;
+
+ /*
+ * Uncompress the data into a buffer. Depending on the compression
+ * format, this is either an "inflate" operation or a memcpy.
+ *
+ * Use "uncompLen" from getEntryInfo() to determine the required
+ * buffer size.
+ *
+ * Returns "true" on success.
+ */
+ bool uncompressEntry(ZipEntryRO entry, void* buffer, size_t size) const;
+
+ /*
+ * Uncompress the data to an open file descriptor.
+ */
+ bool uncompressEntry(ZipEntryRO entry, int fd) const;
+
+ ~ZipFileRO();
+
+private:
+ /* these are private and not defined */
+ ZipFileRO(const ZipFileRO& src);
+ ZipFileRO& operator=(const ZipFileRO& src);
+
+ ZipFileRO(ZipArchiveHandle handle, char* fileName) : mHandle(handle),
+ mFileName(fileName)
+ {
+ }
+
+ const ZipArchiveHandle mHandle;
+ char* mFileName;
+};
+
+}; // namespace android
+
+#endif /*__LIBS_ZIPFILERO_H*/
diff --git a/include/androidfw/ZipUtils.h b/include/androidfw/ZipUtils.h
new file mode 100644
index 0000000..6bea25a
--- /dev/null
+++ b/include/androidfw/ZipUtils.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+//
+// Miscellaneous zip/gzip utility functions.
+//
+#ifndef __LIBS_ZIPUTILS_H
+#define __LIBS_ZIPUTILS_H
+
+#include <stdio.h>
+#include <time.h>
+
+namespace android {
+
+/*
+ * Container class for utility functions, primarily for namespace reasons.
+ */
+class ZipUtils {
+public:
+ /*
+ * General utility function for uncompressing "deflate" data from a file
+ * to a buffer.
+ */
+ static bool inflateToBuffer(FILE* fp, void* buf, long uncompressedLen,
+ long compressedLen);
+ static bool inflateToBuffer(int fd, void* buf, long uncompressedLen,
+ long compressedLen);
+ static bool inflateToBuffer(void *in, void* buf, long uncompressedLen,
+ long compressedLen);
+
+ /*
+ * Someday we might want to make this generic and handle bzip2 ".bz2"
+ * files too.
+ *
+ * We could declare gzip to be a sub-class of zip that has exactly
+ * one always-compressed entry, but we currently want to treat Zip
+ * and gzip as distinct, so there's no value.
+ *
+ * The zlib library has some gzip utilities, but it has no interface
+ * for extracting the uncompressed length of the file (you do *not*
+ * want to gzseek to the end).
+ *
+ * Pass in a seeked file pointer for the gzip file. If this is a gzip
+ * file, we set our return values appropriately and return "true" with
+ * the file seeked to the start of the compressed data.
+ */
+ static bool examineGzip(FILE* fp, int* pCompressionMethod,
+ long* pUncompressedLen, long* pCompressedLen, unsigned long* pCRC32);
+
+ /*
+ * Utility function to convert ZIP's time format to a timespec struct.
+ */
+ static inline void zipTimeToTimespec(long when, struct tm* timespec) {
+ const long date = when >> 16;
+ timespec->tm_year = ((date >> 9) & 0x7F) + 80; // Zip is years since 1980
+ timespec->tm_mon = (date >> 5) & 0x0F;
+ timespec->tm_mday = date & 0x1F;
+
+ timespec->tm_hour = (when >> 11) & 0x1F;
+ timespec->tm_min = (when >> 5) & 0x3F;
+ timespec->tm_sec = (when & 0x1F) << 1;
+ }
+private:
+ ZipUtils() {}
+ ~ZipUtils() {}
+};
+
+}; // namespace android
+
+#endif /*__LIBS_ZIPUTILS_H*/
diff --git a/include/androidfw/misc.h b/include/androidfw/misc.h
new file mode 100644
index 0000000..5a5a0e2
--- /dev/null
+++ b/include/androidfw/misc.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2005 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.
+ */
+
+#include <sys/types.h>
+
+//
+// Handy utility functions and portability code.
+//
+#ifndef _LIBS_ANDROID_FW_MISC_H
+#define _LIBS_ANDROID_FW_MISC_H
+
+namespace android {
+
+/*
+ * Some utility functions for working with files. These could be made
+ * part of a "File" class.
+ */
+typedef enum FileType {
+ kFileTypeUnknown = 0,
+ kFileTypeNonexistent, // i.e. ENOENT
+ kFileTypeRegular,
+ kFileTypeDirectory,
+ kFileTypeCharDev,
+ kFileTypeBlockDev,
+ kFileTypeFifo,
+ kFileTypeSymlink,
+ kFileTypeSocket,
+} FileType;
+/* get the file's type; follows symlinks */
+FileType getFileType(const char* fileName);
+/* get the file's modification date; returns -1 w/errno set on failure */
+time_t getFileModDate(const char* fileName);
+
+}; // namespace android
+
+#endif // _LIBS_ANDROID_FW_MISC_H
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
new file mode 100644
index 0000000..d21197e
--- /dev/null
+++ b/libs/androidfw/Android.mk
@@ -0,0 +1,96 @@
+# Copyright (C) 2010 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# libandroidfw is partially built for the host (used by obbtool and others)
+# These files are common to host and target builds.
+
+commonSources := \
+ Asset.cpp \
+ AssetDir.cpp \
+ AssetManager.cpp \
+ misc.cpp \
+ ObbFile.cpp \
+ ResourceTypes.cpp \
+ StreamingZipInflater.cpp \
+ ZipFileRO.cpp \
+ ZipUtils.cpp
+
+deviceSources := \
+ $(commonSources) \
+ BackupData.cpp \
+ BackupHelpers.cpp \
+ CursorWindow.cpp
+
+hostSources := \
+ $(commonSources)
+
+# For the host
+# =====================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= $(hostSources)
+
+LOCAL_MODULE:= libandroidfw
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS
+
+LOCAL_C_INCLUDES := \
+ external/zlib
+
+LOCAL_STATIC_LIBRARIES := liblog libziparchive-host libutils
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+
+# For the device
+# =====================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= $(deviceSources)
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ liblog \
+ libcutils \
+ libutils \
+ libz
+
+LOCAL_STATIC_LIBRARIES := libziparchive
+
+LOCAL_C_INCLUDES := \
+ external/icu4c/common \
+ external/zlib \
+ system/core/include
+
+LOCAL_MODULE:= libandroidfw
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
+
+
+# Include subdirectory makefiles
+# ============================================================
+
+# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework
+# team really wants is to build the stuff defined by this makefile.
+ifeq (,$(ONE_SHOT_MAKEFILE))
+include $(call first-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
new file mode 100644
index 0000000..ce6cc38
--- /dev/null
+++ b/libs/androidfw/Asset.cpp
@@ -0,0 +1,897 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Provide access to a read-only asset.
+//
+
+#define LOG_TAG "asset"
+//#define NDEBUG 0
+
+#include <androidfw/Asset.h>
+#include <androidfw/StreamingZipInflater.h>
+#include <androidfw/ZipFileRO.h>
+#include <androidfw/ZipUtils.h>
+#include <utils/Atomic.h>
+#include <utils/FileMap.h>
+#include <utils/Log.h>
+#include <utils/threads.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <memory.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace android;
+
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+static Mutex gAssetLock;
+static int32_t gCount = 0;
+static Asset* gHead = NULL;
+static Asset* gTail = NULL;
+
+int32_t Asset::getGlobalCount()
+{
+ AutoMutex _l(gAssetLock);
+ return gCount;
+}
+
+String8 Asset::getAssetAllocations()
+{
+ AutoMutex _l(gAssetLock);
+ String8 res;
+ Asset* cur = gHead;
+ while (cur != NULL) {
+ if (cur->isAllocated()) {
+ res.append(" ");
+ res.append(cur->getAssetSource());
+ off64_t size = (cur->getLength()+512)/1024;
+ char buf[64];
+ sprintf(buf, ": %dK\n", (int)size);
+ res.append(buf);
+ }
+ cur = cur->mNext;
+ }
+
+ return res;
+}
+
+Asset::Asset(void)
+ : mAccessMode(ACCESS_UNKNOWN)
+{
+ AutoMutex _l(gAssetLock);
+ gCount++;
+ mNext = mPrev = NULL;
+ if (gTail == NULL) {
+ gHead = gTail = this;
+ } else {
+ mPrev = gTail;
+ gTail->mNext = this;
+ gTail = this;
+ }
+ //ALOGI("Creating Asset %p #%d\n", this, gCount);
+}
+
+Asset::~Asset(void)
+{
+ AutoMutex _l(gAssetLock);
+ gCount--;
+ if (gHead == this) {
+ gHead = mNext;
+ }
+ if (gTail == this) {
+ gTail = mPrev;
+ }
+ if (mNext != NULL) {
+ mNext->mPrev = mPrev;
+ }
+ if (mPrev != NULL) {
+ mPrev->mNext = mNext;
+ }
+ mNext = mPrev = NULL;
+ //ALOGI("Destroying Asset in %p #%d\n", this, gCount);
+}
+
+/*
+ * Create a new Asset from a file on disk. There is a fair chance that
+ * the file doesn't actually exist.
+ *
+ * We can use "mode" to decide how we want to go about it.
+ */
+/*static*/ Asset* Asset::createFromFile(const char* fileName, AccessMode mode)
+{
+ _FileAsset* pAsset;
+ status_t result;
+ off64_t length;
+ int fd;
+
+ fd = open(fileName, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ return NULL;
+
+ /*
+ * Under Linux, the lseek fails if we actually opened a directory. To
+ * be correct we should test the file type explicitly, but since we
+ * always open things read-only it doesn't really matter, so there's
+ * no value in incurring the extra overhead of an fstat() call.
+ */
+ // TODO(kroot): replace this with fstat despite the plea above.
+#if 1
+ length = lseek64(fd, 0, SEEK_END);
+ if (length < 0) {
+ ::close(fd);
+ return NULL;
+ }
+ (void) lseek64(fd, 0, SEEK_SET);
+#else
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ ::close(fd);
+ return NULL;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ ::close(fd);
+ return NULL;
+ }
+#endif
+
+ pAsset = new _FileAsset;
+ result = pAsset->openChunk(fileName, fd, 0, length);
+ if (result != NO_ERROR) {
+ delete pAsset;
+ return NULL;
+ }
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+
+
+/*
+ * Create a new Asset from a compressed file on disk. There is a fair chance
+ * that the file doesn't actually exist.
+ *
+ * We currently support gzip files. We might want to handle .bz2 someday.
+ */
+/*static*/ Asset* Asset::createFromCompressedFile(const char* fileName,
+ AccessMode mode)
+{
+ _CompressedAsset* pAsset;
+ status_t result;
+ off64_t fileLen;
+ bool scanResult;
+ long offset;
+ int method;
+ long uncompressedLen, compressedLen;
+ int fd;
+
+ fd = open(fileName, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ return NULL;
+
+ fileLen = lseek(fd, 0, SEEK_END);
+ if (fileLen < 0) {
+ ::close(fd);
+ return NULL;
+ }
+ (void) lseek(fd, 0, SEEK_SET);
+
+ /* want buffered I/O for the file scan; must dup so fclose() is safe */
+ FILE* fp = fdopen(dup(fd), "rb");
+ if (fp == NULL) {
+ ::close(fd);
+ return NULL;
+ }
+
+ unsigned long crc32;
+ scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
+ &compressedLen, &crc32);
+ offset = ftell(fp);
+ fclose(fp);
+ if (!scanResult) {
+ ALOGD("File '%s' is not in gzip format\n", fileName);
+ ::close(fd);
+ return NULL;
+ }
+
+ pAsset = new _CompressedAsset;
+ result = pAsset->openChunk(fd, offset, method, uncompressedLen,
+ compressedLen);
+ if (result != NO_ERROR) {
+ delete pAsset;
+ return NULL;
+ }
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+
+
+#if 0
+/*
+ * Create a new Asset from part of an open file.
+ */
+/*static*/ Asset* Asset::createFromFileSegment(int fd, off64_t offset,
+ size_t length, AccessMode mode)
+{
+ _FileAsset* pAsset;
+ status_t result;
+
+ pAsset = new _FileAsset;
+ result = pAsset->openChunk(NULL, fd, offset, length);
+ if (result != NO_ERROR)
+ return NULL;
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+
+/*
+ * Create a new Asset from compressed data in an open file.
+ */
+/*static*/ Asset* Asset::createFromCompressedData(int fd, off64_t offset,
+ int compressionMethod, size_t uncompressedLen, size_t compressedLen,
+ AccessMode mode)
+{
+ _CompressedAsset* pAsset;
+ status_t result;
+
+ pAsset = new _CompressedAsset;
+ result = pAsset->openChunk(fd, offset, compressionMethod,
+ uncompressedLen, compressedLen);
+ if (result != NO_ERROR)
+ return NULL;
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+#endif
+
+/*
+ * Create a new Asset from a memory mapping.
+ */
+/*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap,
+ AccessMode mode)
+{
+ _FileAsset* pAsset;
+ status_t result;
+
+ pAsset = new _FileAsset;
+ result = pAsset->openChunk(dataMap);
+ if (result != NO_ERROR)
+ return NULL;
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+
+/*
+ * Create a new Asset from compressed data in a memory mapping.
+ */
+/*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap,
+ int method, size_t uncompressedLen, AccessMode mode)
+{
+ _CompressedAsset* pAsset;
+ status_t result;
+
+ pAsset = new _CompressedAsset;
+ result = pAsset->openChunk(dataMap, method, uncompressedLen);
+ if (result != NO_ERROR)
+ return NULL;
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+
+
+/*
+ * Do generic seek() housekeeping. Pass in the offset/whence values from
+ * the seek request, along with the current chunk offset and the chunk
+ * length.
+ *
+ * Returns the new chunk offset, or -1 if the seek is illegal.
+ */
+off64_t Asset::handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t maxPosn)
+{
+ off64_t newOffset;
+
+ switch (whence) {
+ case SEEK_SET:
+ newOffset = offset;
+ break;
+ case SEEK_CUR:
+ newOffset = curPosn + offset;
+ break;
+ case SEEK_END:
+ newOffset = maxPosn + offset;
+ break;
+ default:
+ ALOGW("unexpected whence %d\n", whence);
+ // this was happening due to an off64_t size mismatch
+ assert(false);
+ return (off64_t) -1;
+ }
+
+ if (newOffset < 0 || newOffset > maxPosn) {
+ ALOGW("seek out of range: want %ld, end=%ld\n",
+ (long) newOffset, (long) maxPosn);
+ return (off64_t) -1;
+ }
+
+ return newOffset;
+}
+
+
+/*
+ * ===========================================================================
+ * _FileAsset
+ * ===========================================================================
+ */
+
+/*
+ * Constructor.
+ */
+_FileAsset::_FileAsset(void)
+ : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL)
+{
+}
+
+/*
+ * Destructor. Release resources.
+ */
+_FileAsset::~_FileAsset(void)
+{
+ close();
+}
+
+/*
+ * Operate on a chunk of an uncompressed file.
+ *
+ * Zero-length chunks are allowed.
+ */
+status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
+{
+ assert(mFp == NULL); // no reopen
+ assert(mMap == NULL);
+ assert(fd >= 0);
+ assert(offset >= 0);
+
+ /*
+ * Seek to end to get file length.
+ */
+ off64_t fileLength;
+ fileLength = lseek64(fd, 0, SEEK_END);
+ if (fileLength == (off64_t) -1) {
+ // probably a bad file descriptor
+ ALOGD("failed lseek (errno=%d)\n", errno);
+ return UNKNOWN_ERROR;
+ }
+
+ if ((off64_t) (offset + length) > fileLength) {
+ ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
+ (long) offset, (long) length, (long) fileLength);
+ return BAD_INDEX;
+ }
+
+ /* after fdopen, the fd will be closed on fclose() */
+ mFp = fdopen(fd, "rb");
+ if (mFp == NULL)
+ return UNKNOWN_ERROR;
+
+ mStart = offset;
+ mLength = length;
+ assert(mOffset == 0);
+
+ /* seek the FILE* to the start of chunk */
+ if (fseek(mFp, mStart, SEEK_SET) != 0) {
+ assert(false);
+ }
+
+ mFileName = fileName != NULL ? strdup(fileName) : NULL;
+
+ return NO_ERROR;
+}
+
+/*
+ * Create the chunk from the map.
+ */
+status_t _FileAsset::openChunk(FileMap* dataMap)
+{
+ assert(mFp == NULL); // no reopen
+ assert(mMap == NULL);
+ assert(dataMap != NULL);
+
+ mMap = dataMap;
+ mStart = -1; // not used
+ mLength = dataMap->getDataLength();
+ assert(mOffset == 0);
+
+ return NO_ERROR;
+}
+
+/*
+ * Read a chunk of data.
+ */
+ssize_t _FileAsset::read(void* buf, size_t count)
+{
+ size_t maxLen;
+ size_t actual;
+
+ assert(mOffset >= 0 && mOffset <= mLength);
+
+ if (getAccessMode() == ACCESS_BUFFER) {
+ /*
+ * On first access, read or map the entire file. The caller has
+ * requested buffer access, either because they're going to be
+ * using the buffer or because what they're doing has appropriate
+ * performance needs and access patterns.
+ */
+ if (mBuf == NULL)
+ getBuffer(false);
+ }
+
+ /* adjust count if we're near EOF */
+ maxLen = mLength - mOffset;
+ if (count > maxLen)
+ count = maxLen;
+
+ if (!count)
+ return 0;
+
+ if (mMap != NULL) {
+ /* copy from mapped area */
+ //printf("map read\n");
+ memcpy(buf, (char*)mMap->getDataPtr() + mOffset, count);
+ actual = count;
+ } else if (mBuf != NULL) {
+ /* copy from buffer */
+ //printf("buf read\n");
+ memcpy(buf, (char*)mBuf + mOffset, count);
+ actual = count;
+ } else {
+ /* read from the file */
+ //printf("file read\n");
+ if (ftell(mFp) != mStart + mOffset) {
+ ALOGE("Hosed: %ld != %ld+%ld\n",
+ ftell(mFp), (long) mStart, (long) mOffset);
+ assert(false);
+ }
+
+ /*
+ * This returns 0 on error or eof. We need to use ferror() or feof()
+ * to tell the difference, but we don't currently have those on the
+ * device. However, we know how much data is *supposed* to be in the
+ * file, so if we don't read the full amount we know something is
+ * hosed.
+ */
+ actual = fread(buf, 1, count, mFp);
+ if (actual == 0) // something failed -- I/O error?
+ return -1;
+
+ assert(actual == count);
+ }
+
+ mOffset += actual;
+ return actual;
+}
+
+/*
+ * Seek to a new position.
+ */
+off64_t _FileAsset::seek(off64_t offset, int whence)
+{
+ off64_t newPosn;
+ off64_t actualOffset;
+
+ // compute new position within chunk
+ newPosn = handleSeek(offset, whence, mOffset, mLength);
+ if (newPosn == (off64_t) -1)
+ return newPosn;
+
+ actualOffset = mStart + newPosn;
+
+ if (mFp != NULL) {
+ if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
+ return (off64_t) -1;
+ }
+
+ mOffset = actualOffset - mStart;
+ return mOffset;
+}
+
+/*
+ * Close the asset.
+ */
+void _FileAsset::close(void)
+{
+ if (mMap != NULL) {
+ mMap->release();
+ mMap = NULL;
+ }
+ if (mBuf != NULL) {
+ delete[] mBuf;
+ mBuf = NULL;
+ }
+
+ if (mFileName != NULL) {
+ free(mFileName);
+ mFileName = NULL;
+ }
+
+ if (mFp != NULL) {
+ // can only be NULL when called from destructor
+ // (otherwise we would never return this object)
+ fclose(mFp);
+ mFp = NULL;
+ }
+}
+
+/*
+ * Return a read-only pointer to a buffer.
+ *
+ * We can either read the whole thing in or map the relevant piece of
+ * the source file. Ideally a map would be established at a higher
+ * level and we'd be using a different object, but we didn't, so we
+ * deal with it here.
+ */
+const void* _FileAsset::getBuffer(bool wordAligned)
+{
+ /* subsequent requests just use what we did previously */
+ if (mBuf != NULL)
+ return mBuf;
+ if (mMap != NULL) {
+ if (!wordAligned) {
+ return mMap->getDataPtr();
+ }
+ return ensureAlignment(mMap);
+ }
+
+ assert(mFp != NULL);
+
+ if (mLength < kReadVsMapThreshold) {
+ unsigned char* buf;
+ long allocLen;
+
+ /* zero-length files are allowed; not sure about zero-len allocs */
+ /* (works fine with gcc + x86linux) */
+ allocLen = mLength;
+ if (mLength == 0)
+ allocLen = 1;
+
+ buf = new unsigned char[allocLen];
+ if (buf == NULL) {
+ ALOGE("alloc of %ld bytes failed\n", (long) allocLen);
+ return NULL;
+ }
+
+ ALOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
+ if (mLength > 0) {
+ long oldPosn = ftell(mFp);
+ fseek(mFp, mStart, SEEK_SET);
+ if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
+ ALOGE("failed reading %ld bytes\n", (long) mLength);
+ delete[] buf;
+ return NULL;
+ }
+ fseek(mFp, oldPosn, SEEK_SET);
+ }
+
+ ALOGV(" getBuffer: loaded into buffer\n");
+
+ mBuf = buf;
+ return mBuf;
+ } else {
+ FileMap* map;
+
+ map = new FileMap;
+ if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
+ map->release();
+ return NULL;
+ }
+
+ ALOGV(" getBuffer: mapped\n");
+
+ mMap = map;
+ if (!wordAligned) {
+ return mMap->getDataPtr();
+ }
+ return ensureAlignment(mMap);
+ }
+}
+
+int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
+{
+ if (mMap != NULL) {
+ const char* fname = mMap->getFileName();
+ if (fname == NULL) {
+ fname = mFileName;
+ }
+ if (fname == NULL) {
+ return -1;
+ }
+ *outStart = mMap->getDataOffset();
+ *outLength = mMap->getDataLength();
+ return open(fname, O_RDONLY | O_BINARY);
+ }
+ if (mFileName == NULL) {
+ return -1;
+ }
+ *outStart = mStart;
+ *outLength = mLength;
+ return open(mFileName, O_RDONLY | O_BINARY);
+}
+
+const void* _FileAsset::ensureAlignment(FileMap* map)
+{
+ void* data = map->getDataPtr();
+ if ((((size_t)data)&0x3) == 0) {
+ // We can return this directly if it is aligned on a word
+ // boundary.
+ ALOGV("Returning aligned FileAsset %p (%s).", this,
+ getAssetSource());
+ return data;
+ }
+ // If not aligned on a word boundary, then we need to copy it into
+ // our own buffer.
+ ALOGV("Copying FileAsset %p (%s) to buffer size %d to make it aligned.", this,
+ getAssetSource(), (int)mLength);
+ unsigned char* buf = new unsigned char[mLength];
+ if (buf == NULL) {
+ ALOGE("alloc of %ld bytes failed\n", (long) mLength);
+ return NULL;
+ }
+ memcpy(buf, data, mLength);
+ mBuf = buf;
+ return buf;
+}
+
+/*
+ * ===========================================================================
+ * _CompressedAsset
+ * ===========================================================================
+ */
+
+/*
+ * Constructor.
+ */
+_CompressedAsset::_CompressedAsset(void)
+ : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0),
+ mMap(NULL), mFd(-1), mZipInflater(NULL), mBuf(NULL)
+{
+}
+
+/*
+ * Destructor. Release resources.
+ */
+_CompressedAsset::~_CompressedAsset(void)
+{
+ close();
+}
+
+/*
+ * Open a chunk of compressed data inside a file.
+ *
+ * This currently just sets up some values and returns. On the first
+ * read, we expand the entire file into a buffer and return data from it.
+ */
+status_t _CompressedAsset::openChunk(int fd, off64_t offset,
+ int compressionMethod, size_t uncompressedLen, size_t compressedLen)
+{
+ assert(mFd < 0); // no re-open
+ assert(mMap == NULL);
+ assert(fd >= 0);
+ assert(offset >= 0);
+ assert(compressedLen > 0);
+
+ if (compressionMethod != ZipFileRO::kCompressDeflated) {
+ assert(false);
+ return UNKNOWN_ERROR;
+ }
+
+ mStart = offset;
+ mCompressedLen = compressedLen;
+ mUncompressedLen = uncompressedLen;
+ assert(mOffset == 0);
+ mFd = fd;
+ assert(mBuf == NULL);
+
+ if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
+ mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Open a chunk of compressed data in a mapped region.
+ *
+ * Nothing is expanded until the first read call.
+ */
+status_t _CompressedAsset::openChunk(FileMap* dataMap, int compressionMethod,
+ size_t uncompressedLen)
+{
+ assert(mFd < 0); // no re-open
+ assert(mMap == NULL);
+ assert(dataMap != NULL);
+
+ if (compressionMethod != ZipFileRO::kCompressDeflated) {
+ assert(false);
+ return UNKNOWN_ERROR;
+ }
+
+ mMap = dataMap;
+ mStart = -1; // not used
+ mCompressedLen = dataMap->getDataLength();
+ mUncompressedLen = uncompressedLen;
+ assert(mOffset == 0);
+
+ if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
+ mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
+ }
+ return NO_ERROR;
+}
+
+/*
+ * Read data from a chunk of compressed data.
+ *
+ * [For now, that's just copying data out of a buffer.]
+ */
+ssize_t _CompressedAsset::read(void* buf, size_t count)
+{
+ size_t maxLen;
+ size_t actual;
+
+ assert(mOffset >= 0 && mOffset <= mUncompressedLen);
+
+ /* If we're relying on a streaming inflater, go through that */
+ if (mZipInflater) {
+ actual = mZipInflater->read(buf, count);
+ } else {
+ if (mBuf == NULL) {
+ if (getBuffer(false) == NULL)
+ return -1;
+ }
+ assert(mBuf != NULL);
+
+ /* adjust count if we're near EOF */
+ maxLen = mUncompressedLen - mOffset;
+ if (count > maxLen)
+ count = maxLen;
+
+ if (!count)
+ return 0;
+
+ /* copy from buffer */
+ //printf("comp buf read\n");
+ memcpy(buf, (char*)mBuf + mOffset, count);
+ actual = count;
+ }
+
+ mOffset += actual;
+ return actual;
+}
+
+/*
+ * Handle a seek request.
+ *
+ * If we're working in a streaming mode, this is going to be fairly
+ * expensive, because it requires plowing through a bunch of compressed
+ * data.
+ */
+off64_t _CompressedAsset::seek(off64_t offset, int whence)
+{
+ off64_t newPosn;
+
+ // compute new position within chunk
+ newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
+ if (newPosn == (off64_t) -1)
+ return newPosn;
+
+ if (mZipInflater) {
+ mZipInflater->seekAbsolute(newPosn);
+ }
+ mOffset = newPosn;
+ return mOffset;
+}
+
+/*
+ * Close the asset.
+ */
+void _CompressedAsset::close(void)
+{
+ if (mMap != NULL) {
+ mMap->release();
+ mMap = NULL;
+ }
+
+ delete[] mBuf;
+ mBuf = NULL;
+
+ delete mZipInflater;
+ mZipInflater = NULL;
+
+ if (mFd > 0) {
+ ::close(mFd);
+ mFd = -1;
+ }
+}
+
+/*
+ * Get a pointer to a read-only buffer of data.
+ *
+ * The first time this is called, we expand the compressed data into a
+ * buffer.
+ */
+const void* _CompressedAsset::getBuffer(bool)
+{
+ unsigned char* buf = NULL;
+
+ if (mBuf != NULL)
+ return mBuf;
+
+ /*
+ * Allocate a buffer and read the file into it.
+ */
+ buf = new unsigned char[mUncompressedLen];
+ if (buf == NULL) {
+ ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
+ goto bail;
+ }
+
+ if (mMap != NULL) {
+ if (!ZipUtils::inflateToBuffer(mMap->getDataPtr(), buf,
+ mUncompressedLen, mCompressedLen))
+ goto bail;
+ } else {
+ assert(mFd >= 0);
+
+ /*
+ * Seek to the start of the compressed data.
+ */
+ if (lseek(mFd, mStart, SEEK_SET) != mStart)
+ goto bail;
+
+ /*
+ * Expand the data into it.
+ */
+ if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
+ mCompressedLen))
+ goto bail;
+ }
+
+ /*
+ * Success - now that we have the full asset in RAM we
+ * no longer need the streaming inflater
+ */
+ delete mZipInflater;
+ mZipInflater = NULL;
+
+ mBuf = buf;
+ buf = NULL;
+
+bail:
+ delete[] buf;
+ return mBuf;
+}
+
diff --git a/libs/androidfw/AssetDir.cpp b/libs/androidfw/AssetDir.cpp
new file mode 100644
index 0000000..475f521
--- /dev/null
+++ b/libs/androidfw/AssetDir.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Provide access to a virtual directory in "asset space". Most of the
+// implementation is in the header file or in friend functions in
+// AssetManager.
+//
+#include <androidfw/AssetDir.h>
+
+using namespace android;
+
+
+/*
+ * Find a matching entry in a vector of FileInfo. Because it's sorted, we
+ * can use a binary search.
+ *
+ * Assumes the vector is sorted in ascending order.
+ */
+/*static*/ int AssetDir::FileInfo::findEntry(const SortedVector<FileInfo>* pVector,
+ const String8& fileName)
+{
+ FileInfo tmpInfo;
+
+ tmpInfo.setFileName(fileName);
+ return pVector->indexOf(tmpInfo);
+
+#if 0 // don't need this after all (uses 1/2 compares of SortedVector though)
+ int lo, hi, cur;
+
+ lo = 0;
+ hi = pVector->size() -1;
+ while (lo <= hi) {
+ int cmp;
+
+ cur = (hi + lo) / 2;
+ cmp = strcmp(pVector->itemAt(cur).getFileName(), fileName);
+ if (cmp == 0) {
+ /* match, bail */
+ return cur;
+ } else if (cmp < 0) {
+ /* too low */
+ lo = cur + 1;
+ } else {
+ /* too high */
+ hi = cur -1;
+ }
+ }
+
+ return -1;
+#endif
+}
+
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
new file mode 100644
index 0000000..785e5d4
--- /dev/null
+++ b/libs/androidfw/AssetManager.cpp
@@ -0,0 +1,2035 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Provide access to read-only assets.
+//
+
+#define LOG_TAG "asset"
+#define ATRACE_TAG ATRACE_TAG_RESOURCES
+//#define LOG_NDEBUG 0
+
+#include <androidfw/Asset.h>
+#include <androidfw/AssetDir.h>
+#include <androidfw/AssetManager.h>
+#include <androidfw/misc.h>
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/ZipFileRO.h>
+#include <utils/Atomic.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#ifdef HAVE_ANDROID_OS
+#include <cutils/trace.h>
+#endif
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifndef TEMP_FAILURE_RETRY
+/* Used to retry syscalls that can return EINTR. */
+#define TEMP_FAILURE_RETRY(exp) ({ \
+ typeof (exp) _rc; \
+ do { \
+ _rc = (exp); \
+ } while (_rc == -1 && errno == EINTR); \
+ _rc; })
+#endif
+
+#ifdef HAVE_ANDROID_OS
+#define MY_TRACE_BEGIN(x) ATRACE_BEGIN(x)
+#define MY_TRACE_END() ATRACE_END()
+#else
+#define MY_TRACE_BEGIN(x)
+#define MY_TRACE_END()
+#endif
+
+using namespace android;
+
+/*
+ * Names for default app, locale, and vendor. We might want to change
+ * these to be an actual locale, e.g. always use en-US as the default.
+ */
+static const char* kDefaultLocale = "default";
+static const char* kDefaultVendor = "default";
+static const char* kAssetsRoot = "assets";
+static const char* kAppZipName = NULL; //"classes.jar";
+static const char* kSystemAssets = "framework/framework-res.apk";
+static const char* kIdmapCacheDir = "resource-cache";
+
+static const char* kExcludeExtension = ".EXCLUDE";
+
+static Asset* const kExcludedAsset = (Asset*) 0xd000000d;
+
+static volatile int32_t gCount = 0;
+
+namespace {
+ // Transform string /a/b/c.apk to /data/resource-cache/a@b@c.apk@idmap
+ String8 idmapPathForPackagePath(const String8& pkgPath)
+ {
+ const char* root = getenv("ANDROID_DATA");
+ LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set");
+ String8 path(root);
+ path.appendPath(kIdmapCacheDir);
+
+ char buf[256]; // 256 chars should be enough for anyone...
+ strncpy(buf, pkgPath.string(), 255);
+ buf[255] = '\0';
+ char* filename = buf;
+ while (*filename && *filename == '/') {
+ ++filename;
+ }
+ char* p = filename;
+ while (*p) {
+ if (*p == '/') {
+ *p = '@';
+ }
+ ++p;
+ }
+ path.appendPath(filename);
+ path.append("@idmap");
+
+ return path;
+ }
+
+ /*
+ * Like strdup(), but uses C++ "new" operator instead of malloc.
+ */
+ static char* strdupNew(const char* str)
+ {
+ char* newStr;
+ int len;
+
+ if (str == NULL)
+ return NULL;
+
+ len = strlen(str);
+ newStr = new char[len+1];
+ memcpy(newStr, str, len+1);
+
+ return newStr;
+ }
+}
+
+/*
+ * ===========================================================================
+ * AssetManager
+ * ===========================================================================
+ */
+
+int32_t AssetManager::getGlobalCount()
+{
+ return gCount;
+}
+
+AssetManager::AssetManager(CacheMode cacheMode)
+ : mLocale(NULL), mVendor(NULL),
+ mResources(NULL), mConfig(new ResTable_config),
+ mCacheMode(cacheMode), mCacheValid(false)
+{
+ int count = android_atomic_inc(&gCount)+1;
+ //ALOGI("Creating AssetManager %p #%d\n", this, count);
+ memset(mConfig, 0, sizeof(ResTable_config));
+}
+
+AssetManager::~AssetManager(void)
+{
+ int count = android_atomic_dec(&gCount);
+ //ALOGI("Destroying AssetManager in %p #%d\n", this, count);
+
+ delete mConfig;
+ delete mResources;
+
+ // don't have a String class yet, so make sure we clean up
+ delete[] mLocale;
+ delete[] mVendor;
+}
+
+bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
+{
+ AutoMutex _l(mLock);
+
+ asset_path ap;
+
+ String8 realPath(path);
+ if (kAppZipName) {
+ realPath.appendPath(kAppZipName);
+ }
+ ap.type = ::getFileType(realPath.string());
+ if (ap.type == kFileTypeRegular) {
+ ap.path = realPath;
+ } else {
+ ap.path = path;
+ ap.type = ::getFileType(path.string());
+ if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
+ ALOGW("Asset path %s is neither a directory nor file (type=%d).",
+ path.string(), (int)ap.type);
+ return false;
+ }
+ }
+
+ // Skip if we have it already.
+ for (size_t i=0; i<mAssetPaths.size(); i++) {
+ if (mAssetPaths[i].path == ap.path) {
+ if (cookie) {
+ *cookie = static_cast<int32_t>(i+1);
+ }
+ return true;
+ }
+ }
+
+ ALOGV("In %p Asset %s path: %s", this,
+ ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
+
+ mAssetPaths.add(ap);
+
+ // new paths are always added at the end
+ if (cookie) {
+ *cookie = static_cast<int32_t>(mAssetPaths.size());
+ }
+
+ // add overlay packages for /system/framework; apps are handled by the
+ // (Java) package manager
+ if (strncmp(path.string(), "/system/framework/", 18) == 0) {
+ // When there is an environment variable for /vendor, this
+ // should be changed to something similar to how ANDROID_ROOT
+ // and ANDROID_DATA are used in this file.
+ String8 overlayPath("/vendor/overlay/framework/");
+ overlayPath.append(path.getPathLeaf());
+ if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) {
+ asset_path oap;
+ oap.path = overlayPath;
+ oap.type = ::getFileType(overlayPath.string());
+ bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay
+ if (addOverlay) {
+ oap.idmap = idmapPathForPackagePath(overlayPath);
+
+ if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {
+ addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);
+ }
+ }
+ if (addOverlay) {
+ mAssetPaths.add(oap);
+ } else {
+ ALOGW("failed to add overlay package %s\n", overlayPath.string());
+ }
+ }
+ }
+
+ return true;
+}
+
+bool AssetManager::isIdmapStaleLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath)
+{
+ struct stat st;
+ if (TEMP_FAILURE_RETRY(stat(idmapPath.string(), &st)) == -1) {
+ if (errno == ENOENT) {
+ return true; // non-existing idmap is always stale
+ } else {
+ ALOGW("failed to stat file %s: %s\n", idmapPath.string(), strerror(errno));
+ return false;
+ }
+ }
+ if (st.st_size < ResTable::IDMAP_HEADER_SIZE_BYTES) {
+ ALOGW("file %s has unexpectedly small size=%zd\n", idmapPath.string(), (size_t)st.st_size);
+ return false;
+ }
+ int fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_RDONLY));
+ if (fd == -1) {
+ ALOGW("failed to open file %s: %s\n", idmapPath.string(), strerror(errno));
+ return false;
+ }
+ char buf[ResTable::IDMAP_HEADER_SIZE_BYTES];
+ ssize_t bytesLeft = ResTable::IDMAP_HEADER_SIZE_BYTES;
+ for (;;) {
+ ssize_t r = TEMP_FAILURE_RETRY(read(fd, buf + ResTable::IDMAP_HEADER_SIZE_BYTES - bytesLeft,
+ bytesLeft));
+ if (r < 0) {
+ TEMP_FAILURE_RETRY(close(fd));
+ return false;
+ }
+ bytesLeft -= r;
+ if (bytesLeft == 0) {
+ break;
+ }
+ }
+ TEMP_FAILURE_RETRY(close(fd));
+
+ uint32_t cachedOriginalCrc, cachedOverlayCrc;
+ if (!ResTable::getIdmapInfo(buf, ResTable::IDMAP_HEADER_SIZE_BYTES,
+ &cachedOriginalCrc, &cachedOverlayCrc)) {
+ return false;
+ }
+
+ uint32_t actualOriginalCrc, actualOverlayCrc;
+ if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &actualOriginalCrc)) {
+ return false;
+ }
+ if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &actualOverlayCrc)) {
+ return false;
+ }
+ return cachedOriginalCrc != actualOriginalCrc || cachedOverlayCrc != actualOverlayCrc;
+}
+
+bool AssetManager::getZipEntryCrcLocked(const String8& zipPath, const char* entryFilename,
+ uint32_t* pCrc)
+{
+ asset_path ap;
+ ap.path = zipPath;
+ const ZipFileRO* zip = getZipFileLocked(ap);
+ if (zip == NULL) {
+ return false;
+ }
+ const ZipEntryRO entry = zip->findEntryByName(entryFilename);
+ if (entry == NULL) {
+ return false;
+ }
+
+ const bool gotInfo = zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, (long*)pCrc);
+ zip->releaseEntry(entry);
+
+ return gotInfo;
+}
+
+bool AssetManager::createIdmapFileLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath)
+{
+ ALOGD("%s: originalPath=%s overlayPath=%s idmapPath=%s\n",
+ __FUNCTION__, originalPath.string(), overlayPath.string(), idmapPath.string());
+ ResTable tables[2];
+ const String8* paths[2] = { &originalPath, &overlayPath };
+ uint32_t originalCrc, overlayCrc;
+ bool retval = false;
+ ssize_t offset = 0;
+ int fd = 0;
+ uint32_t* data = NULL;
+ size_t size;
+
+ for (int i = 0; i < 2; ++i) {
+ asset_path ap;
+ ap.type = kFileTypeRegular;
+ ap.path = *paths[i];
+ Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
+ if (ass == NULL) {
+ ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
+ goto error;
+ }
+ tables[i].add(ass, (void*)1, false);
+ }
+
+ if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &originalCrc)) {
+ ALOGW("failed to retrieve crc for resources.arsc in %s\n", originalPath.string());
+ goto error;
+ }
+ if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &overlayCrc)) {
+ ALOGW("failed to retrieve crc for resources.arsc in %s\n", overlayPath.string());
+ goto error;
+ }
+
+ if (tables[0].createIdmap(tables[1], originalCrc, overlayCrc,
+ (void**)&data, &size) != NO_ERROR) {
+ ALOGW("failed to generate idmap data for file %s\n", idmapPath.string());
+ goto error;
+ }
+
+ // This should be abstracted (eg replaced by a stand-alone
+ // application like dexopt, triggered by something equivalent to
+ // installd).
+ fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_WRONLY | O_CREAT | O_TRUNC, 0644));
+ if (fd == -1) {
+ ALOGW("failed to write idmap file %s (open: %s)\n", idmapPath.string(), strerror(errno));
+ goto error_free;
+ }
+ for (;;) {
+ ssize_t written = TEMP_FAILURE_RETRY(write(fd, data + offset, size));
+ if (written < 0) {
+ ALOGW("failed to write idmap file %s (write: %s)\n", idmapPath.string(),
+ strerror(errno));
+ goto error_close;
+ }
+ size -= (size_t)written;
+ offset += written;
+ if (size == 0) {
+ break;
+ }
+ }
+
+ retval = true;
+error_close:
+ TEMP_FAILURE_RETRY(close(fd));
+error_free:
+ free(data);
+error:
+ return retval;
+}
+
+bool AssetManager::addDefaultAssets()
+{
+ const char* root = getenv("ANDROID_ROOT");
+ LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
+
+ String8 path(root);
+ path.appendPath(kSystemAssets);
+
+ return addAssetPath(path, NULL);
+}
+
+int32_t AssetManager::nextAssetPath(const int32_t cookie) const
+{
+ AutoMutex _l(mLock);
+ const size_t next = static_cast<size_t>(cookie) + 1;
+ return next > mAssetPaths.size() ? -1 : next;
+}
+
+String8 AssetManager::getAssetPath(const int32_t cookie) const
+{
+ AutoMutex _l(mLock);
+ const size_t which = static_cast<size_t>(cookie) - 1;
+ if (which < mAssetPaths.size()) {
+ return mAssetPaths[which].path;
+ }
+ return String8();
+}
+
+/*
+ * Set the current locale. Use NULL to indicate no locale.
+ *
+ * Close and reopen Zip archives as appropriate, and reset cached
+ * information in the locale-specific sections of the tree.
+ */
+void AssetManager::setLocale(const char* locale)
+{
+ AutoMutex _l(mLock);
+ setLocaleLocked(locale);
+}
+
+void AssetManager::setLocaleLocked(const char* locale)
+{
+ if (mLocale != NULL) {
+ /* previously set, purge cached data */
+ purgeFileNameCacheLocked();
+ //mZipSet.purgeLocale();
+ delete[] mLocale;
+ }
+ mLocale = strdupNew(locale);
+
+ updateResourceParamsLocked();
+}
+
+/*
+ * Set the current vendor. Use NULL to indicate no vendor.
+ *
+ * Close and reopen Zip archives as appropriate, and reset cached
+ * information in the vendor-specific sections of the tree.
+ */
+void AssetManager::setVendor(const char* vendor)
+{
+ AutoMutex _l(mLock);
+
+ if (mVendor != NULL) {
+ /* previously set, purge cached data */
+ purgeFileNameCacheLocked();
+ //mZipSet.purgeVendor();
+ delete[] mVendor;
+ }
+ mVendor = strdupNew(vendor);
+}
+
+void AssetManager::setConfiguration(const ResTable_config& config, const char* locale)
+{
+ AutoMutex _l(mLock);
+ *mConfig = config;
+ if (locale) {
+ setLocaleLocked(locale);
+ } else if (config.language[0] != 0) {
+ char spec[9];
+ spec[0] = config.language[0];
+ spec[1] = config.language[1];
+ if (config.country[0] != 0) {
+ spec[2] = '_';
+ spec[3] = config.country[0];
+ spec[4] = config.country[1];
+ spec[5] = 0;
+ } else {
+ spec[3] = 0;
+ }
+ setLocaleLocked(spec);
+ } else {
+ updateResourceParamsLocked();
+ }
+}
+
+void AssetManager::getConfiguration(ResTable_config* outConfig) const
+{
+ AutoMutex _l(mLock);
+ *outConfig = *mConfig;
+}
+
+/*
+ * Open an asset.
+ *
+ * The data could be;
+ * - In a file on disk (assetBase + fileName).
+ * - In a compressed file on disk (assetBase + fileName.gz).
+ * - In a Zip archive, uncompressed or compressed.
+ *
+ * It can be in a number of different directories and Zip archives.
+ * The search order is:
+ * - [appname]
+ * - locale + vendor
+ * - "default" + vendor
+ * - locale + "default"
+ * - "default + "default"
+ * - "common"
+ * - (same as above)
+ *
+ * To find a particular file, we have to try up to eight paths with
+ * all three forms of data.
+ *
+ * We should probably reject requests for "illegal" filenames, e.g. those
+ * with illegal characters or "../" backward relative paths.
+ */
+Asset* AssetManager::open(const char* fileName, AccessMode mode)
+{
+ AutoMutex _l(mLock);
+
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ loadFileNameCacheLocked();
+
+ String8 assetName(kAssetsRoot);
+ assetName.appendPath(fileName);
+
+ /*
+ * For each top-level asset path, search for the asset.
+ */
+
+ size_t i = mAssetPaths.size();
+ while (i > 0) {
+ i--;
+ ALOGV("Looking for asset '%s' in '%s'\n",
+ assetName.string(), mAssetPaths.itemAt(i).path.string());
+ Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i));
+ if (pAsset != NULL) {
+ return pAsset != kExcludedAsset ? pAsset : NULL;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Open a non-asset file as if it were an asset.
+ *
+ * The "fileName" is the partial path starting from the application
+ * name.
+ */
+Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode)
+{
+ AutoMutex _l(mLock);
+
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ loadFileNameCacheLocked();
+
+ /*
+ * For each top-level asset path, search for the asset.
+ */
+
+ size_t i = mAssetPaths.size();
+ while (i > 0) {
+ i--;
+ ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string());
+ Asset* pAsset = openNonAssetInPathLocked(
+ fileName, mode, mAssetPaths.itemAt(i));
+ if (pAsset != NULL) {
+ return pAsset != kExcludedAsset ? pAsset : NULL;
+ }
+ }
+
+ return NULL;
+}
+
+Asset* AssetManager::openNonAsset(const int32_t cookie, const char* fileName, AccessMode mode)
+{
+ const size_t which = static_cast<size_t>(cookie) - 1;
+
+ AutoMutex _l(mLock);
+
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ loadFileNameCacheLocked();
+
+ if (which < mAssetPaths.size()) {
+ ALOGV("Looking for non-asset '%s' in '%s'\n", fileName,
+ mAssetPaths.itemAt(which).path.string());
+ Asset* pAsset = openNonAssetInPathLocked(
+ fileName, mode, mAssetPaths.itemAt(which));
+ if (pAsset != NULL) {
+ return pAsset != kExcludedAsset ? pAsset : NULL;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Get the type of a file in the asset namespace.
+ *
+ * This currently only works for regular files. All others (including
+ * directories) will return kFileTypeNonexistent.
+ */
+FileType AssetManager::getFileType(const char* fileName)
+{
+ Asset* pAsset = NULL;
+
+ /*
+ * Open the asset. This is less efficient than simply finding the
+ * file, but it's not too bad (we don't uncompress or mmap data until
+ * the first read() call).
+ */
+ pAsset = open(fileName, Asset::ACCESS_STREAMING);
+ delete pAsset;
+
+ if (pAsset == NULL)
+ return kFileTypeNonexistent;
+ else
+ return kFileTypeRegular;
+}
+
+const ResTable* AssetManager::getResTable(bool required) const
+{
+ ResTable* rt = mResources;
+ if (rt) {
+ return rt;
+ }
+
+ // Iterate through all asset packages, collecting resources from each.
+
+ AutoMutex _l(mLock);
+
+ if (mResources != NULL) {
+ return mResources;
+ }
+
+ if (required) {
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+ }
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ const_cast<AssetManager*>(this)->loadFileNameCacheLocked();
+
+ const size_t N = mAssetPaths.size();
+ for (size_t i=0; i<N; i++) {
+ Asset* ass = NULL;
+ ResTable* sharedRes = NULL;
+ bool shared = true;
+ const asset_path& ap = mAssetPaths.itemAt(i);
+ MY_TRACE_BEGIN(ap.path.string());
+ Asset* idmap = openIdmapLocked(ap);
+ ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
+ if (ap.type != kFileTypeDirectory) {
+ if (i == 0) {
+ // The first item is typically the framework resources,
+ // which we want to avoid parsing every time.
+ sharedRes = const_cast<AssetManager*>(this)->
+ mZipSet.getZipResourceTable(ap.path);
+ }
+ if (sharedRes == NULL) {
+ ass = const_cast<AssetManager*>(this)->
+ mZipSet.getZipResourceTableAsset(ap.path);
+ if (ass == NULL) {
+ ALOGV("loading resource table %s\n", ap.path.string());
+ ass = const_cast<AssetManager*>(this)->
+ openNonAssetInPathLocked("resources.arsc",
+ Asset::ACCESS_BUFFER,
+ ap);
+ if (ass != NULL && ass != kExcludedAsset) {
+ ass = const_cast<AssetManager*>(this)->
+ mZipSet.setZipResourceTableAsset(ap.path, ass);
+ }
+ }
+
+ if (i == 0 && ass != NULL) {
+ // If this is the first resource table in the asset
+ // manager, then we are going to cache it so that we
+ // can quickly copy it out for others.
+ ALOGV("Creating shared resources for %s", ap.path.string());
+ sharedRes = new ResTable();
+ sharedRes->add(ass, (void*)(i+1), false, idmap);
+ sharedRes = const_cast<AssetManager*>(this)->
+ mZipSet.setZipResourceTable(ap.path, sharedRes);
+ }
+ }
+ } else {
+ ALOGV("loading resource table %s\n", ap.path.string());
+ ass = const_cast<AssetManager*>(this)->
+ openNonAssetInPathLocked("resources.arsc",
+ Asset::ACCESS_BUFFER,
+ ap);
+ shared = false;
+ }
+ if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
+ if (rt == NULL) {
+ mResources = rt = new ResTable();
+ updateResourceParamsLocked();
+ }
+ ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
+ if (sharedRes != NULL) {
+ ALOGV("Copying existing resources for %s", ap.path.string());
+ rt->add(sharedRes);
+ } else {
+ ALOGV("Parsing resources for %s", ap.path.string());
+ rt->add(ass, (void*)(i+1), !shared, idmap);
+ }
+
+ if (!shared) {
+ delete ass;
+ }
+ }
+ if (idmap != NULL) {
+ delete idmap;
+ }
+ MY_TRACE_END();
+ }
+
+ if (required && !rt) ALOGW("Unable to find resources file resources.arsc");
+ if (!rt) {
+ mResources = rt = new ResTable();
+ }
+ return rt;
+}
+
+void AssetManager::updateResourceParamsLocked() const
+{
+ ResTable* res = mResources;
+ if (!res) {
+ return;
+ }
+
+ size_t llen = mLocale ? strlen(mLocale) : 0;
+ mConfig->language[0] = 0;
+ mConfig->language[1] = 0;
+ mConfig->country[0] = 0;
+ mConfig->country[1] = 0;
+ if (llen >= 2) {
+ mConfig->language[0] = mLocale[0];
+ mConfig->language[1] = mLocale[1];
+ }
+ if (llen >= 5) {
+ mConfig->country[0] = mLocale[3];
+ mConfig->country[1] = mLocale[4];
+ }
+ mConfig->size = sizeof(*mConfig);
+
+ res->setParameters(mConfig);
+}
+
+Asset* AssetManager::openIdmapLocked(const struct asset_path& ap) const
+{
+ Asset* ass = NULL;
+ if (ap.idmap.size() != 0) {
+ ass = const_cast<AssetManager*>(this)->
+ openAssetFromFileLocked(ap.idmap, Asset::ACCESS_BUFFER);
+ if (ass) {
+ ALOGV("loading idmap %s\n", ap.idmap.string());
+ } else {
+ ALOGW("failed to load idmap %s\n", ap.idmap.string());
+ }
+ }
+ return ass;
+}
+
+const ResTable& AssetManager::getResources(bool required) const
+{
+ const ResTable* rt = getResTable(required);
+ return *rt;
+}
+
+bool AssetManager::isUpToDate()
+{
+ AutoMutex _l(mLock);
+ return mZipSet.isUpToDate();
+}
+
+void AssetManager::getLocales(Vector<String8>* locales) const
+{
+ ResTable* res = mResources;
+ if (res != NULL) {
+ res->getLocales(locales);
+ }
+}
+
+/*
+ * Open a non-asset file as if it were an asset, searching for it in the
+ * specified app.
+ *
+ * Pass in a NULL values for "appName" if the common app directory should
+ * be used.
+ */
+Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
+ const asset_path& ap)
+{
+ Asset* pAsset = NULL;
+
+ /* look at the filesystem on disk */
+ if (ap.type == kFileTypeDirectory) {
+ String8 path(ap.path);
+ path.appendPath(fileName);
+
+ pAsset = openAssetFromFileLocked(path, mode);
+
+ if (pAsset == NULL) {
+ /* try again, this time with ".gz" */
+ path.append(".gz");
+ pAsset = openAssetFromFileLocked(path, mode);
+ }
+
+ if (pAsset != NULL) {
+ //printf("FOUND NA '%s' on disk\n", fileName);
+ pAsset->setAssetSource(path);
+ }
+
+ /* look inside the zip file */
+ } else {
+ String8 path(fileName);
+
+ /* check the appropriate Zip file */
+ ZipFileRO* pZip = getZipFileLocked(ap);
+ if (pZip != NULL) {
+ //printf("GOT zip, checking NA '%s'\n", (const char*) path);
+ ZipEntryRO entry = pZip->findEntryByName(path.string());
+ if (entry != NULL) {
+ //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
+ pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
+ pZip->releaseEntry(entry);
+ }
+ }
+
+ if (pAsset != NULL) {
+ /* create a "source" name, for debug/display */
+ pAsset->setAssetSource(
+ createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""),
+ String8(fileName)));
+ }
+ }
+
+ return pAsset;
+}
+
+/*
+ * Open an asset, searching for it in the directory hierarchy for the
+ * specified app.
+ *
+ * Pass in a NULL values for "appName" if the common app directory should
+ * be used.
+ */
+Asset* AssetManager::openInPathLocked(const char* fileName, AccessMode mode,
+ const asset_path& ap)
+{
+ Asset* pAsset = NULL;
+
+ /*
+ * Try various combinations of locale and vendor.
+ */
+ if (mLocale != NULL && mVendor != NULL)
+ pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, mVendor);
+ if (pAsset == NULL && mVendor != NULL)
+ pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, mVendor);
+ if (pAsset == NULL && mLocale != NULL)
+ pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, NULL);
+ if (pAsset == NULL)
+ pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, NULL);
+
+ return pAsset;
+}
+
+/*
+ * Open an asset, searching for it in the directory hierarchy for the
+ * specified locale and vendor.
+ *
+ * We also search in "app.jar".
+ *
+ * Pass in NULL values for "appName", "locale", and "vendor" if the
+ * defaults should be used.
+ */
+Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode mode,
+ const asset_path& ap, const char* locale, const char* vendor)
+{
+ Asset* pAsset = NULL;
+
+ if (ap.type == kFileTypeDirectory) {
+ if (mCacheMode == CACHE_OFF) {
+ /* look at the filesystem on disk */
+ String8 path(createPathNameLocked(ap, locale, vendor));
+ path.appendPath(fileName);
+
+ String8 excludeName(path);
+ excludeName.append(kExcludeExtension);
+ if (::getFileType(excludeName.string()) != kFileTypeNonexistent) {
+ /* say no more */
+ //printf("+++ excluding '%s'\n", (const char*) excludeName);
+ return kExcludedAsset;
+ }
+
+ pAsset = openAssetFromFileLocked(path, mode);
+
+ if (pAsset == NULL) {
+ /* try again, this time with ".gz" */
+ path.append(".gz");
+ pAsset = openAssetFromFileLocked(path, mode);
+ }
+
+ if (pAsset != NULL)
+ pAsset->setAssetSource(path);
+ } else {
+ /* find in cache */
+ String8 path(createPathNameLocked(ap, locale, vendor));
+ path.appendPath(fileName);
+
+ AssetDir::FileInfo tmpInfo;
+ bool found = false;
+
+ String8 excludeName(path);
+ excludeName.append(kExcludeExtension);
+
+ if (mCache.indexOf(excludeName) != NAME_NOT_FOUND) {
+ /* go no farther */
+ //printf("+++ Excluding '%s'\n", (const char*) excludeName);
+ return kExcludedAsset;
+ }
+
+ /*
+ * File compression extensions (".gz") don't get stored in the
+ * name cache, so we have to try both here.
+ */
+ if (mCache.indexOf(path) != NAME_NOT_FOUND) {
+ found = true;
+ pAsset = openAssetFromFileLocked(path, mode);
+ if (pAsset == NULL) {
+ /* try again, this time with ".gz" */
+ path.append(".gz");
+ pAsset = openAssetFromFileLocked(path, mode);
+ }
+ }
+
+ if (pAsset != NULL)
+ pAsset->setAssetSource(path);
+
+ /*
+ * Don't continue the search into the Zip files. Our cached info
+ * said it was a file on disk; to be consistent with openDir()
+ * we want to return the loose asset. If the cached file gets
+ * removed, we fail.
+ *
+ * The alternative is to update our cache when files get deleted,
+ * or make some sort of "best effort" promise, but for now I'm
+ * taking the hard line.
+ */
+ if (found) {
+ if (pAsset == NULL)
+ ALOGD("Expected file not found: '%s'\n", path.string());
+ return pAsset;
+ }
+ }
+ }
+
+ /*
+ * Either it wasn't found on disk or on the cached view of the disk.
+ * Dig through the currently-opened set of Zip files. If caching
+ * is disabled, the Zip file may get reopened.
+ */
+ if (pAsset == NULL && ap.type == kFileTypeRegular) {
+ String8 path;
+
+ path.appendPath((locale != NULL) ? locale : kDefaultLocale);
+ path.appendPath((vendor != NULL) ? vendor : kDefaultVendor);
+ path.appendPath(fileName);
+
+ /* check the appropriate Zip file */
+ ZipFileRO* pZip = getZipFileLocked(ap);
+ if (pZip != NULL) {
+ //printf("GOT zip, checking '%s'\n", (const char*) path);
+ ZipEntryRO entry = pZip->findEntryByName(path.string());
+ if (entry != NULL) {
+ //printf("FOUND in Zip file for %s/%s-%s\n",
+ // appName, locale, vendor);
+ pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
+ pZip->releaseEntry(entry);
+ }
+ }
+
+ if (pAsset != NULL) {
+ /* create a "source" name, for debug/display */
+ pAsset->setAssetSource(createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()),
+ String8(""), String8(fileName)));
+ }
+ }
+
+ return pAsset;
+}
+
+/*
+ * Create a "source name" for a file from a Zip archive.
+ */
+String8 AssetManager::createZipSourceNameLocked(const String8& zipFileName,
+ const String8& dirName, const String8& fileName)
+{
+ String8 sourceName("zip:");
+ sourceName.append(zipFileName);
+ sourceName.append(":");
+ if (dirName.length() > 0) {
+ sourceName.appendPath(dirName);
+ }
+ sourceName.appendPath(fileName);
+ return sourceName;
+}
+
+/*
+ * Create a path to a loose asset (asset-base/app/locale/vendor).
+ */
+String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* locale,
+ const char* vendor)
+{
+ String8 path(ap.path);
+ path.appendPath((locale != NULL) ? locale : kDefaultLocale);
+ path.appendPath((vendor != NULL) ? vendor : kDefaultVendor);
+ return path;
+}
+
+/*
+ * Create a path to a loose asset (asset-base/app/rootDir).
+ */
+String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* rootDir)
+{
+ String8 path(ap.path);
+ if (rootDir != NULL) path.appendPath(rootDir);
+ return path;
+}
+
+/*
+ * Return a pointer to one of our open Zip archives. Returns NULL if no
+ * matching Zip file exists.
+ *
+ * Right now we have 2 possible Zip files (1 each in app/"common").
+ *
+ * If caching is set to CACHE_OFF, to get the expected behavior we
+ * need to reopen the Zip file on every request. That would be silly
+ * and expensive, so instead we just check the file modification date.
+ *
+ * Pass in NULL values for "appName", "locale", and "vendor" if the
+ * generics should be used.
+ */
+ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap)
+{
+ ALOGV("getZipFileLocked() in %p\n", this);
+
+ return mZipSet.getZip(ap.path);
+}
+
+/*
+ * Try to open an asset from a file on disk.
+ *
+ * If the file is compressed with gzip, we seek to the start of the
+ * deflated data and pass that in (just like we would for a Zip archive).
+ *
+ * For uncompressed data, we may already have an mmap()ed version sitting
+ * around. If so, we want to hand that to the Asset instead.
+ *
+ * This returns NULL if the file doesn't exist, couldn't be opened, or
+ * claims to be a ".gz" but isn't.
+ */
+Asset* AssetManager::openAssetFromFileLocked(const String8& pathName,
+ AccessMode mode)
+{
+ Asset* pAsset = NULL;
+
+ if (strcasecmp(pathName.getPathExtension().string(), ".gz") == 0) {
+ //printf("TRYING '%s'\n", (const char*) pathName);
+ pAsset = Asset::createFromCompressedFile(pathName.string(), mode);
+ } else {
+ //printf("TRYING '%s'\n", (const char*) pathName);
+ pAsset = Asset::createFromFile(pathName.string(), mode);
+ }
+
+ return pAsset;
+}
+
+/*
+ * Given an entry in a Zip archive, create a new Asset object.
+ *
+ * If the entry is uncompressed, we may want to create or share a
+ * slice of shared memory.
+ */
+Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile,
+ const ZipEntryRO entry, AccessMode mode, const String8& entryName)
+{
+ Asset* pAsset = NULL;
+
+ // TODO: look for previously-created shared memory slice?
+ int method;
+ size_t uncompressedLen;
+
+ //printf("USING Zip '%s'\n", pEntry->getFileName());
+
+ //pZipFile->getEntryInfo(entry, &method, &uncompressedLen, &compressedLen,
+ // &offset);
+ if (!pZipFile->getEntryInfo(entry, &method, &uncompressedLen, NULL, NULL,
+ NULL, NULL))
+ {
+ ALOGW("getEntryInfo failed\n");
+ return NULL;
+ }
+
+ FileMap* dataMap = pZipFile->createEntryFileMap(entry);
+ if (dataMap == NULL) {
+ ALOGW("create map from entry failed\n");
+ return NULL;
+ }
+
+ if (method == ZipFileRO::kCompressStored) {
+ pAsset = Asset::createFromUncompressedMap(dataMap, mode);
+ ALOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(),
+ dataMap->getFileName(), mode, pAsset);
+ } else {
+ pAsset = Asset::createFromCompressedMap(dataMap, method,
+ uncompressedLen, mode);
+ ALOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.string(),
+ dataMap->getFileName(), mode, pAsset);
+ }
+ if (pAsset == NULL) {
+ /* unexpected */
+ ALOGW("create from segment failed\n");
+ }
+
+ return pAsset;
+}
+
+
+
+/*
+ * Open a directory in the asset namespace.
+ *
+ * An "asset directory" is simply the combination of all files in all
+ * locations, with ".gz" stripped for loose files. With app, locale, and
+ * vendor defined, we have 8 directories and 2 Zip archives to scan.
+ *
+ * Pass in "" for the root dir.
+ */
+AssetDir* AssetManager::openDir(const char* dirName)
+{
+ AutoMutex _l(mLock);
+
+ AssetDir* pDir = NULL;
+ SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL;
+
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+ assert(dirName != NULL);
+
+ //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase);
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ loadFileNameCacheLocked();
+
+ pDir = new AssetDir;
+
+ /*
+ * Scan the various directories, merging what we find into a single
+ * vector. We want to scan them in reverse priority order so that
+ * the ".EXCLUDE" processing works correctly. Also, if we decide we
+ * want to remember where the file is coming from, we'll get the right
+ * version.
+ *
+ * We start with Zip archives, then do loose files.
+ */
+ pMergedInfo = new SortedVector<AssetDir::FileInfo>;
+
+ size_t i = mAssetPaths.size();
+ while (i > 0) {
+ i--;
+ const asset_path& ap = mAssetPaths.itemAt(i);
+ if (ap.type == kFileTypeRegular) {
+ ALOGV("Adding directory %s from zip %s", dirName, ap.path.string());
+ scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName);
+ } else {
+ ALOGV("Adding directory %s from dir %s", dirName, ap.path.string());
+ scanAndMergeDirLocked(pMergedInfo, ap, kAssetsRoot, dirName);
+ }
+ }
+
+#if 0
+ printf("FILE LIST:\n");
+ for (i = 0; i < (size_t) pMergedInfo->size(); i++) {
+ printf(" %d: (%d) '%s'\n", i,
+ pMergedInfo->itemAt(i).getFileType(),
+ (const char*) pMergedInfo->itemAt(i).getFileName());
+ }
+#endif
+
+ pDir->setFileList(pMergedInfo);
+ return pDir;
+}
+
+/*
+ * Open a directory in the non-asset namespace.
+ *
+ * An "asset directory" is simply the combination of all files in all
+ * locations, with ".gz" stripped for loose files. With app, locale, and
+ * vendor defined, we have 8 directories and 2 Zip archives to scan.
+ *
+ * Pass in "" for the root dir.
+ */
+AssetDir* AssetManager::openNonAssetDir(const int32_t cookie, const char* dirName)
+{
+ AutoMutex _l(mLock);
+
+ AssetDir* pDir = NULL;
+ SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL;
+
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+ assert(dirName != NULL);
+
+ //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase);
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ loadFileNameCacheLocked();
+
+ pDir = new AssetDir;
+
+ pMergedInfo = new SortedVector<AssetDir::FileInfo>;
+
+ const size_t which = static_cast<size_t>(cookie) - 1;
+
+ if (which < mAssetPaths.size()) {
+ const asset_path& ap = mAssetPaths.itemAt(which);
+ if (ap.type == kFileTypeRegular) {
+ ALOGV("Adding directory %s from zip %s", dirName, ap.path.string());
+ scanAndMergeZipLocked(pMergedInfo, ap, NULL, dirName);
+ } else {
+ ALOGV("Adding directory %s from dir %s", dirName, ap.path.string());
+ scanAndMergeDirLocked(pMergedInfo, ap, NULL, dirName);
+ }
+ }
+
+#if 0
+ printf("FILE LIST:\n");
+ for (i = 0; i < (size_t) pMergedInfo->size(); i++) {
+ printf(" %d: (%d) '%s'\n", i,
+ pMergedInfo->itemAt(i).getFileType(),
+ (const char*) pMergedInfo->itemAt(i).getFileName());
+ }
+#endif
+
+ pDir->setFileList(pMergedInfo);
+ return pDir;
+}
+
+/*
+ * Scan the contents of the specified directory and merge them into the
+ * "pMergedInfo" vector, removing previous entries if we find "exclude"
+ * directives.
+ *
+ * Returns "false" if we found nothing to contribute.
+ */
+bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& ap, const char* rootDir, const char* dirName)
+{
+ SortedVector<AssetDir::FileInfo>* pContents;
+ String8 path;
+
+ assert(pMergedInfo != NULL);
+
+ //printf("scanAndMergeDir: %s %s %s %s\n", appName, locale, vendor,dirName);
+
+ if (mCacheValid) {
+ int i, start, count;
+
+ pContents = new SortedVector<AssetDir::FileInfo>;
+
+ /*
+ * Get the basic partial path and find it in the cache. That's
+ * the start point for the search.
+ */
+ path = createPathNameLocked(ap, rootDir);
+ if (dirName[0] != '\0')
+ path.appendPath(dirName);
+
+ start = mCache.indexOf(path);
+ if (start == NAME_NOT_FOUND) {
+ //printf("+++ not found in cache: dir '%s'\n", (const char*) path);
+ delete pContents;
+ return false;
+ }
+
+ /*
+ * The match string looks like "common/default/default/foo/bar/".
+ * The '/' on the end ensures that we don't match on the directory
+ * itself or on ".../foo/barfy/".
+ */
+ path.append("/");
+
+ count = mCache.size();
+
+ /*
+ * Pick out the stuff in the current dir by examining the pathname.
+ * It needs to match the partial pathname prefix, and not have a '/'
+ * (fssep) anywhere after the prefix.
+ */
+ for (i = start+1; i < count; i++) {
+ if (mCache[i].getFileName().length() > path.length() &&
+ strncmp(mCache[i].getFileName().string(), path.string(), path.length()) == 0)
+ {
+ const char* name = mCache[i].getFileName().string();
+ // XXX THIS IS BROKEN! Looks like we need to store the full
+ // path prefix separately from the file path.
+ if (strchr(name + path.length(), '/') == NULL) {
+ /* grab it, reducing path to just the filename component */
+ AssetDir::FileInfo tmp = mCache[i];
+ tmp.setFileName(tmp.getFileName().getPathLeaf());
+ pContents->add(tmp);
+ }
+ } else {
+ /* no longer in the dir or its subdirs */
+ break;
+ }
+
+ }
+ } else {
+ path = createPathNameLocked(ap, rootDir);
+ if (dirName[0] != '\0')
+ path.appendPath(dirName);
+ pContents = scanDirLocked(path);
+ if (pContents == NULL)
+ return false;
+ }
+
+ // if we wanted to do an incremental cache fill, we would do it here
+
+ /*
+ * Process "exclude" directives. If we find a filename that ends with
+ * ".EXCLUDE", we look for a matching entry in the "merged" set, and
+ * remove it if we find it. We also delete the "exclude" entry.
+ */
+ int i, count, exclExtLen;
+
+ count = pContents->size();
+ exclExtLen = strlen(kExcludeExtension);
+ for (i = 0; i < count; i++) {
+ const char* name;
+ int nameLen;
+
+ name = pContents->itemAt(i).getFileName().string();
+ nameLen = strlen(name);
+ if (nameLen > exclExtLen &&
+ strcmp(name + (nameLen - exclExtLen), kExcludeExtension) == 0)
+ {
+ String8 match(name, nameLen - exclExtLen);
+ int matchIdx;
+
+ matchIdx = AssetDir::FileInfo::findEntry(pMergedInfo, match);
+ if (matchIdx > 0) {
+ ALOGV("Excluding '%s' [%s]\n",
+ pMergedInfo->itemAt(matchIdx).getFileName().string(),
+ pMergedInfo->itemAt(matchIdx).getSourceName().string());
+ pMergedInfo->removeAt(matchIdx);
+ } else {
+ //printf("+++ no match on '%s'\n", (const char*) match);
+ }
+
+ ALOGD("HEY: size=%d removing %d\n", (int)pContents->size(), i);
+ pContents->removeAt(i);
+ i--; // adjust "for" loop
+ count--; // and loop limit
+ }
+ }
+
+ mergeInfoLocked(pMergedInfo, pContents);
+
+ delete pContents;
+
+ return true;
+}
+
+/*
+ * Scan the contents of the specified directory, and stuff what we find
+ * into a newly-allocated vector.
+ *
+ * Files ending in ".gz" will have their extensions removed.
+ *
+ * We should probably think about skipping files with "illegal" names,
+ * e.g. illegal characters (/\:) or excessive length.
+ *
+ * Returns NULL if the specified directory doesn't exist.
+ */
+SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& path)
+{
+ SortedVector<AssetDir::FileInfo>* pContents = NULL;
+ DIR* dir;
+ struct dirent* entry;
+ FileType fileType;
+
+ ALOGV("Scanning dir '%s'\n", path.string());
+
+ dir = opendir(path.string());
+ if (dir == NULL)
+ return NULL;
+
+ pContents = new SortedVector<AssetDir::FileInfo>;
+
+ while (1) {
+ entry = readdir(dir);
+ if (entry == NULL)
+ break;
+
+ if (strcmp(entry->d_name, ".") == 0 ||
+ strcmp(entry->d_name, "..") == 0)
+ continue;
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (entry->d_type == DT_REG)
+ fileType = kFileTypeRegular;
+ else if (entry->d_type == DT_DIR)
+ fileType = kFileTypeDirectory;
+ else
+ fileType = kFileTypeUnknown;
+#else
+ // stat the file
+ fileType = ::getFileType(path.appendPathCopy(entry->d_name).string());
+#endif
+
+ if (fileType != kFileTypeRegular && fileType != kFileTypeDirectory)
+ continue;
+
+ AssetDir::FileInfo info;
+ info.set(String8(entry->d_name), fileType);
+ if (strcasecmp(info.getFileName().getPathExtension().string(), ".gz") == 0)
+ info.setFileName(info.getFileName().getBasePath());
+ info.setSourceName(path.appendPathCopy(info.getFileName()));
+ pContents->add(info);
+ }
+
+ closedir(dir);
+ return pContents;
+}
+
+/*
+ * Scan the contents out of the specified Zip archive, and merge what we
+ * find into "pMergedInfo". If the Zip archive in question doesn't exist,
+ * we return immediately.
+ *
+ * Returns "false" if we found nothing to contribute.
+ */
+bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& ap, const char* rootDir, const char* baseDirName)
+{
+ ZipFileRO* pZip;
+ Vector<String8> dirs;
+ AssetDir::FileInfo info;
+ SortedVector<AssetDir::FileInfo> contents;
+ String8 sourceName, zipName, dirName;
+
+ pZip = mZipSet.getZip(ap.path);
+ if (pZip == NULL) {
+ ALOGW("Failure opening zip %s\n", ap.path.string());
+ return false;
+ }
+
+ zipName = ZipSet::getPathName(ap.path.string());
+
+ /* convert "sounds" to "rootDir/sounds" */
+ if (rootDir != NULL) dirName = rootDir;
+ dirName.appendPath(baseDirName);
+
+ /*
+ * Scan through the list of files, looking for a match. The files in
+ * the Zip table of contents are not in sorted order, so we have to
+ * process the entire list. We're looking for a string that begins
+ * with the characters in "dirName", is followed by a '/', and has no
+ * subsequent '/' in the stuff that follows.
+ *
+ * What makes this especially fun is that directories are not stored
+ * explicitly in Zip archives, so we have to infer them from context.
+ * When we see "sounds/foo.wav" we have to leave a note to ourselves
+ * to insert a directory called "sounds" into the list. We store
+ * these in temporary vector so that we only return each one once.
+ *
+ * Name comparisons are case-sensitive to match UNIX filesystem
+ * semantics.
+ */
+ int dirNameLen = dirName.length();
+ void *iterationCookie;
+ if (!pZip->startIteration(&iterationCookie)) {
+ ALOGW("ZipFileRO::startIteration returned false");
+ return false;
+ }
+
+ ZipEntryRO entry;
+ while ((entry = pZip->nextEntry(iterationCookie)) != NULL) {
+ char nameBuf[256];
+
+ if (pZip->getEntryFileName(entry, nameBuf, sizeof(nameBuf)) != 0) {
+ // TODO: fix this if we expect to have long names
+ ALOGE("ARGH: name too long?\n");
+ continue;
+ }
+ //printf("Comparing %s in %s?\n", nameBuf, dirName.string());
+ if (dirNameLen == 0 ||
+ (strncmp(nameBuf, dirName.string(), dirNameLen) == 0 &&
+ nameBuf[dirNameLen] == '/'))
+ {
+ const char* cp;
+ const char* nextSlash;
+
+ cp = nameBuf + dirNameLen;
+ if (dirNameLen != 0)
+ cp++; // advance past the '/'
+
+ nextSlash = strchr(cp, '/');
+//xxx this may break if there are bare directory entries
+ if (nextSlash == NULL) {
+ /* this is a file in the requested directory */
+
+ info.set(String8(nameBuf).getPathLeaf(), kFileTypeRegular);
+
+ info.setSourceName(
+ createZipSourceNameLocked(zipName, dirName, info.getFileName()));
+
+ contents.add(info);
+ //printf("FOUND: file '%s'\n", info.getFileName().string());
+ } else {
+ /* this is a subdir; add it if we don't already have it*/
+ String8 subdirName(cp, nextSlash - cp);
+ size_t j;
+ size_t N = dirs.size();
+
+ for (j = 0; j < N; j++) {
+ if (subdirName == dirs[j]) {
+ break;
+ }
+ }
+ if (j == N) {
+ dirs.add(subdirName);
+ }
+
+ //printf("FOUND: dir '%s'\n", subdirName.string());
+ }
+ }
+ }
+
+ pZip->endIteration(iterationCookie);
+
+ /*
+ * Add the set of unique directories.
+ */
+ for (int i = 0; i < (int) dirs.size(); i++) {
+ info.set(dirs[i], kFileTypeDirectory);
+ info.setSourceName(
+ createZipSourceNameLocked(zipName, dirName, info.getFileName()));
+ contents.add(info);
+ }
+
+ mergeInfoLocked(pMergedInfo, &contents);
+
+ return true;
+}
+
+
+/*
+ * Merge two vectors of FileInfo.
+ *
+ * The merged contents will be stuffed into *pMergedInfo.
+ *
+ * If an entry for a file exists in both "pMergedInfo" and "pContents",
+ * we use the newer "pContents" entry.
+ */
+void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const SortedVector<AssetDir::FileInfo>* pContents)
+{
+ /*
+ * Merge what we found in this directory with what we found in
+ * other places.
+ *
+ * Two basic approaches:
+ * (1) Create a new array that holds the unique values of the two
+ * arrays.
+ * (2) Take the elements from pContents and shove them into pMergedInfo.
+ *
+ * Because these are vectors of complex objects, moving elements around
+ * inside the vector requires constructing new objects and allocating
+ * storage for members. With approach #1, we're always adding to the
+ * end, whereas with #2 we could be inserting multiple elements at the
+ * front of the vector. Approach #1 requires a full copy of the
+ * contents of pMergedInfo, but approach #2 requires the same copy for
+ * every insertion at the front of pMergedInfo.
+ *
+ * (We should probably use a SortedVector interface that allows us to
+ * just stuff items in, trusting us to maintain the sort order.)
+ */
+ SortedVector<AssetDir::FileInfo>* pNewSorted;
+ int mergeMax, contMax;
+ int mergeIdx, contIdx;
+
+ pNewSorted = new SortedVector<AssetDir::FileInfo>;
+ mergeMax = pMergedInfo->size();
+ contMax = pContents->size();
+ mergeIdx = contIdx = 0;
+
+ while (mergeIdx < mergeMax || contIdx < contMax) {
+ if (mergeIdx == mergeMax) {
+ /* hit end of "merge" list, copy rest of "contents" */
+ pNewSorted->add(pContents->itemAt(contIdx));
+ contIdx++;
+ } else if (contIdx == contMax) {
+ /* hit end of "cont" list, copy rest of "merge" */
+ pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
+ mergeIdx++;
+ } else if (pMergedInfo->itemAt(mergeIdx) == pContents->itemAt(contIdx))
+ {
+ /* items are identical, add newer and advance both indices */
+ pNewSorted->add(pContents->itemAt(contIdx));
+ mergeIdx++;
+ contIdx++;
+ } else if (pMergedInfo->itemAt(mergeIdx) < pContents->itemAt(contIdx))
+ {
+ /* "merge" is lower, add that one */
+ pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
+ mergeIdx++;
+ } else {
+ /* "cont" is lower, add that one */
+ assert(pContents->itemAt(contIdx) < pMergedInfo->itemAt(mergeIdx));
+ pNewSorted->add(pContents->itemAt(contIdx));
+ contIdx++;
+ }
+ }
+
+ /*
+ * Overwrite the "merged" list with the new stuff.
+ */
+ *pMergedInfo = *pNewSorted;
+ delete pNewSorted;
+
+#if 0 // for Vector, rather than SortedVector
+ int i, j;
+ for (i = pContents->size() -1; i >= 0; i--) {
+ bool add = true;
+
+ for (j = pMergedInfo->size() -1; j >= 0; j--) {
+ /* case-sensitive comparisons, to behave like UNIX fs */
+ if (strcmp(pContents->itemAt(i).mFileName,
+ pMergedInfo->itemAt(j).mFileName) == 0)
+ {
+ /* match, don't add this entry */
+ add = false;
+ break;
+ }
+ }
+
+ if (add)
+ pMergedInfo->add(pContents->itemAt(i));
+ }
+#endif
+}
+
+
+/*
+ * Load all files into the file name cache. We want to do this across
+ * all combinations of { appname, locale, vendor }, performing a recursive
+ * directory traversal.
+ *
+ * This is not the most efficient data structure. Also, gathering the
+ * information as we needed it (file-by-file or directory-by-directory)
+ * would be faster. However, on the actual device, 99% of the files will
+ * live in Zip archives, so this list will be very small. The trouble
+ * is that we have to check the "loose" files first, so it's important
+ * that we don't beat the filesystem silly looking for files that aren't
+ * there.
+ *
+ * Note on thread safety: this is the only function that causes updates
+ * to mCache, and anybody who tries to use it will call here if !mCacheValid,
+ * so we need to employ a mutex here.
+ */
+void AssetManager::loadFileNameCacheLocked(void)
+{
+ assert(!mCacheValid);
+ assert(mCache.size() == 0);
+
+#ifdef DO_TIMINGS // need to link against -lrt for this now
+ DurationTimer timer;
+ timer.start();
+#endif
+
+ fncScanLocked(&mCache, "");
+
+#ifdef DO_TIMINGS
+ timer.stop();
+ ALOGD("Cache scan took %.3fms\n",
+ timer.durationUsecs() / 1000.0);
+#endif
+
+#if 0
+ int i;
+ printf("CACHED FILE LIST (%d entries):\n", mCache.size());
+ for (i = 0; i < (int) mCache.size(); i++) {
+ printf(" %d: (%d) '%s'\n", i,
+ mCache.itemAt(i).getFileType(),
+ (const char*) mCache.itemAt(i).getFileName());
+ }
+#endif
+
+ mCacheValid = true;
+}
+
+/*
+ * Scan up to 8 versions of the specified directory.
+ */
+void AssetManager::fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const char* dirName)
+{
+ size_t i = mAssetPaths.size();
+ while (i > 0) {
+ i--;
+ const asset_path& ap = mAssetPaths.itemAt(i);
+ fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, NULL, dirName);
+ if (mLocale != NULL)
+ fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, NULL, dirName);
+ if (mVendor != NULL)
+ fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, mVendor, dirName);
+ if (mLocale != NULL && mVendor != NULL)
+ fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, mVendor, dirName);
+ }
+}
+
+/*
+ * Recursively scan this directory and all subdirs.
+ *
+ * This is similar to scanAndMergeDir, but we don't remove the .EXCLUDE
+ * files, and we prepend the extended partial path to the filenames.
+ */
+bool AssetManager::fncScanAndMergeDirLocked(
+ SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& ap, const char* locale, const char* vendor,
+ const char* dirName)
+{
+ SortedVector<AssetDir::FileInfo>* pContents;
+ String8 partialPath;
+ String8 fullPath;
+
+ // XXX This is broken -- the filename cache needs to hold the base
+ // asset path separately from its filename.
+
+ partialPath = createPathNameLocked(ap, locale, vendor);
+ if (dirName[0] != '\0') {
+ partialPath.appendPath(dirName);
+ }
+
+ fullPath = partialPath;
+ pContents = scanDirLocked(fullPath);
+ if (pContents == NULL) {
+ return false; // directory did not exist
+ }
+
+ /*
+ * Scan all subdirectories of the current dir, merging what we find
+ * into "pMergedInfo".
+ */
+ for (int i = 0; i < (int) pContents->size(); i++) {
+ if (pContents->itemAt(i).getFileType() == kFileTypeDirectory) {
+ String8 subdir(dirName);
+ subdir.appendPath(pContents->itemAt(i).getFileName());
+
+ fncScanAndMergeDirLocked(pMergedInfo, ap, locale, vendor, subdir.string());
+ }
+ }
+
+ /*
+ * To be consistent, we want entries for the root directory. If
+ * we're the root, add one now.
+ */
+ if (dirName[0] == '\0') {
+ AssetDir::FileInfo tmpInfo;
+
+ tmpInfo.set(String8(""), kFileTypeDirectory);
+ tmpInfo.setSourceName(createPathNameLocked(ap, locale, vendor));
+ pContents->add(tmpInfo);
+ }
+
+ /*
+ * We want to prepend the extended partial path to every entry in
+ * "pContents". It's the same value for each entry, so this will
+ * not change the sorting order of the vector contents.
+ */
+ for (int i = 0; i < (int) pContents->size(); i++) {
+ const AssetDir::FileInfo& info = pContents->itemAt(i);
+ pContents->editItemAt(i).setFileName(partialPath.appendPathCopy(info.getFileName()));
+ }
+
+ mergeInfoLocked(pMergedInfo, pContents);
+ return true;
+}
+
+/*
+ * Trash the cache.
+ */
+void AssetManager::purgeFileNameCacheLocked(void)
+{
+ mCacheValid = false;
+ mCache.clear();
+}
+
+/*
+ * ===========================================================================
+ * AssetManager::SharedZip
+ * ===========================================================================
+ */
+
+
+Mutex AssetManager::SharedZip::gLock;
+DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen;
+
+AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen)
+ : mPath(path), mZipFile(NULL), mModWhen(modWhen),
+ mResourceTableAsset(NULL), mResourceTable(NULL)
+{
+ //ALOGI("Creating SharedZip %p %s\n", this, (const char*)mPath);
+ ALOGV("+++ opening zip '%s'\n", mPath.string());
+ mZipFile = ZipFileRO::open(mPath.string());
+ if (mZipFile == NULL) {
+ ALOGD("failed to open Zip archive '%s'\n", mPath.string());
+ }
+}
+
+sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path)
+{
+ AutoMutex _l(gLock);
+ time_t modWhen = getFileModDate(path);
+ sp<SharedZip> zip = gOpen.valueFor(path).promote();
+ if (zip != NULL && zip->mModWhen == modWhen) {
+ return zip;
+ }
+ zip = new SharedZip(path, modWhen);
+ gOpen.add(path, zip);
+ return zip;
+
+}
+
+ZipFileRO* AssetManager::SharedZip::getZip()
+{
+ return mZipFile;
+}
+
+Asset* AssetManager::SharedZip::getResourceTableAsset()
+{
+ ALOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset);
+ return mResourceTableAsset;
+}
+
+Asset* AssetManager::SharedZip::setResourceTableAsset(Asset* asset)
+{
+ {
+ AutoMutex _l(gLock);
+ if (mResourceTableAsset == NULL) {
+ mResourceTableAsset = asset;
+ // This is not thread safe the first time it is called, so
+ // do it here with the global lock held.
+ asset->getBuffer(true);
+ return asset;
+ }
+ }
+ delete asset;
+ return mResourceTableAsset;
+}
+
+ResTable* AssetManager::SharedZip::getResourceTable()
+{
+ ALOGV("Getting from SharedZip %p resource table %p\n", this, mResourceTable);
+ return mResourceTable;
+}
+
+ResTable* AssetManager::SharedZip::setResourceTable(ResTable* res)
+{
+ {
+ AutoMutex _l(gLock);
+ if (mResourceTable == NULL) {
+ mResourceTable = res;
+ return res;
+ }
+ }
+ delete res;
+ return mResourceTable;
+}
+
+bool AssetManager::SharedZip::isUpToDate()
+{
+ time_t modWhen = getFileModDate(mPath.string());
+ return mModWhen == modWhen;
+}
+
+AssetManager::SharedZip::~SharedZip()
+{
+ //ALOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath);
+ if (mResourceTable != NULL) {
+ delete mResourceTable;
+ }
+ if (mResourceTableAsset != NULL) {
+ delete mResourceTableAsset;
+ }
+ if (mZipFile != NULL) {
+ delete mZipFile;
+ ALOGV("Closed '%s'\n", mPath.string());
+ }
+}
+
+/*
+ * ===========================================================================
+ * AssetManager::ZipSet
+ * ===========================================================================
+ */
+
+/*
+ * Constructor.
+ */
+AssetManager::ZipSet::ZipSet(void)
+{
+}
+
+/*
+ * Destructor. Close any open archives.
+ */
+AssetManager::ZipSet::~ZipSet(void)
+{
+ size_t N = mZipFile.size();
+ for (size_t i = 0; i < N; i++)
+ closeZip(i);
+}
+
+/*
+ * Close a Zip file and reset the entry.
+ */
+void AssetManager::ZipSet::closeZip(int idx)
+{
+ mZipFile.editItemAt(idx) = NULL;
+}
+
+
+/*
+ * Retrieve the appropriate Zip file from the set.
+ */
+ZipFileRO* AssetManager::ZipSet::getZip(const String8& path)
+{
+ int idx = getIndex(path);
+ sp<SharedZip> zip = mZipFile[idx];
+ if (zip == NULL) {
+ zip = SharedZip::get(path);
+ mZipFile.editItemAt(idx) = zip;
+ }
+ return zip->getZip();
+}
+
+Asset* AssetManager::ZipSet::getZipResourceTableAsset(const String8& path)
+{
+ int idx = getIndex(path);
+ sp<SharedZip> zip = mZipFile[idx];
+ if (zip == NULL) {
+ zip = SharedZip::get(path);
+ mZipFile.editItemAt(idx) = zip;
+ }
+ return zip->getResourceTableAsset();
+}
+
+Asset* AssetManager::ZipSet::setZipResourceTableAsset(const String8& path,
+ Asset* asset)
+{
+ int idx = getIndex(path);
+ sp<SharedZip> zip = mZipFile[idx];
+ // doesn't make sense to call before previously accessing.
+ return zip->setResourceTableAsset(asset);
+}
+
+ResTable* AssetManager::ZipSet::getZipResourceTable(const String8& path)
+{
+ int idx = getIndex(path);
+ sp<SharedZip> zip = mZipFile[idx];
+ if (zip == NULL) {
+ zip = SharedZip::get(path);
+ mZipFile.editItemAt(idx) = zip;
+ }
+ return zip->getResourceTable();
+}
+
+ResTable* AssetManager::ZipSet::setZipResourceTable(const String8& path,
+ ResTable* res)
+{
+ int idx = getIndex(path);
+ sp<SharedZip> zip = mZipFile[idx];
+ // doesn't make sense to call before previously accessing.
+ return zip->setResourceTable(res);
+}
+
+/*
+ * Generate the partial pathname for the specified archive. The caller
+ * gets to prepend the asset root directory.
+ *
+ * Returns something like "common/en-US-noogle.jar".
+ */
+/*static*/ String8 AssetManager::ZipSet::getPathName(const char* zipPath)
+{
+ return String8(zipPath);
+}
+
+bool AssetManager::ZipSet::isUpToDate()
+{
+ const size_t N = mZipFile.size();
+ for (size_t i=0; i<N; i++) {
+ if (mZipFile[i] != NULL && !mZipFile[i]->isUpToDate()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * Compute the zip file's index.
+ *
+ * "appName", "locale", and "vendor" should be set to NULL to indicate the
+ * default directory.
+ */
+int AssetManager::ZipSet::getIndex(const String8& zip) const
+{
+ const size_t N = mZipPath.size();
+ for (size_t i=0; i<N; i++) {
+ if (mZipPath[i] == zip) {
+ return i;
+ }
+ }
+
+ mZipPath.add(zip);
+ mZipFile.add(NULL);
+
+ return mZipPath.size()-1;
+}
diff --git a/libs/androidfw/BackupData.cpp b/libs/androidfw/BackupData.cpp
new file mode 100644
index 0000000..4e3b522
--- /dev/null
+++ b/libs/androidfw/BackupData.cpp
@@ -0,0 +1,382 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "backup_data"
+
+#include <androidfw/BackupHelpers.h>
+#include <utils/ByteOrder.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <cutils/log.h>
+
+namespace android {
+
+static const bool DEBUG = false;
+
+/*
+ * File Format (v1):
+ *
+ * All ints are stored little-endian.
+ *
+ * - An app_header_v1 struct.
+ * - The name of the package, utf-8, null terminated, padded to 4-byte boundary.
+ * - A sequence of zero or more key/value paires (entities), each with
+ * - A entity_header_v1 struct
+ * - The key, utf-8, null terminated, padded to 4-byte boundary.
+ * - The value, padded to 4 byte boundary
+ */
+
+const static int ROUND_UP[4] = { 0, 3, 2, 1 };
+
+static inline size_t
+round_up(size_t n)
+{
+ return n + ROUND_UP[n % 4];
+}
+
+static inline size_t
+padding_extra(size_t n)
+{
+ return ROUND_UP[n % 4];
+}
+
+BackupDataWriter::BackupDataWriter(int fd)
+ :m_fd(fd),
+ m_status(NO_ERROR),
+ m_pos(0),
+ m_entityCount(0)
+{
+}
+
+BackupDataWriter::~BackupDataWriter()
+{
+}
+
+// Pad out anything they've previously written to the next 4 byte boundary.
+status_t
+BackupDataWriter::write_padding_for(int n)
+{
+ ssize_t amt;
+ ssize_t paddingSize;
+
+ paddingSize = padding_extra(n);
+ if (paddingSize > 0) {
+ uint32_t padding = 0xbcbcbcbc;
+ if (DEBUG) ALOGI("writing %d padding bytes for %d", paddingSize, n);
+ amt = write(m_fd, &padding, paddingSize);
+ if (amt != paddingSize) {
+ m_status = errno;
+ return m_status;
+ }
+ m_pos += amt;
+ }
+ return NO_ERROR;
+}
+
+status_t
+BackupDataWriter::WriteEntityHeader(const String8& key, size_t dataSize)
+{
+ if (m_status != NO_ERROR) {
+ return m_status;
+ }
+
+ ssize_t amt;
+
+ amt = write_padding_for(m_pos);
+ if (amt != 0) {
+ return amt;
+ }
+
+ String8 k;
+ if (m_keyPrefix.length() > 0) {
+ k = m_keyPrefix;
+ k += ":";
+ k += key;
+ } else {
+ k = key;
+ }
+ if (DEBUG) {
+ ALOGD("Writing header: prefix='%s' key='%s' dataSize=%d", m_keyPrefix.string(),
+ key.string(), dataSize);
+ }
+
+ entity_header_v1 header;
+ ssize_t keyLen;
+
+ keyLen = k.length();
+
+ header.type = tolel(BACKUP_HEADER_ENTITY_V1);
+ header.keyLen = tolel(keyLen);
+ header.dataSize = tolel(dataSize);
+
+ if (DEBUG) ALOGI("writing entity header, %d bytes", sizeof(entity_header_v1));
+ amt = write(m_fd, &header, sizeof(entity_header_v1));
+ if (amt != sizeof(entity_header_v1)) {
+ m_status = errno;
+ return m_status;
+ }
+ m_pos += amt;
+
+ if (DEBUG) ALOGI("writing entity header key, %d bytes", keyLen+1);
+ amt = write(m_fd, k.string(), keyLen+1);
+ if (amt != keyLen+1) {
+ m_status = errno;
+ return m_status;
+ }
+ m_pos += amt;
+
+ amt = write_padding_for(keyLen+1);
+
+ m_entityCount++;
+
+ return amt;
+}
+
+status_t
+BackupDataWriter::WriteEntityData(const void* data, size_t size)
+{
+ if (DEBUG) ALOGD("Writing data: size=%lu", (unsigned long) size);
+
+ if (m_status != NO_ERROR) {
+ if (DEBUG) {
+ ALOGD("Not writing data - stream in error state %d (%s)", m_status, strerror(m_status));
+ }
+ return m_status;
+ }
+
+ // We don't write padding here, because they're allowed to call this several
+ // times with smaller buffers. We write it at the end of WriteEntityHeader
+ // instead.
+ ssize_t amt = write(m_fd, data, size);
+ if (amt != (ssize_t)size) {
+ m_status = errno;
+ if (DEBUG) ALOGD("write returned error %d (%s)", m_status, strerror(m_status));
+ return m_status;
+ }
+ m_pos += amt;
+ return NO_ERROR;
+}
+
+void
+BackupDataWriter::SetKeyPrefix(const String8& keyPrefix)
+{
+ m_keyPrefix = keyPrefix;
+}
+
+
+BackupDataReader::BackupDataReader(int fd)
+ :m_fd(fd),
+ m_done(false),
+ m_status(NO_ERROR),
+ m_pos(0),
+ m_entityCount(0)
+{
+ memset(&m_header, 0, sizeof(m_header));
+}
+
+BackupDataReader::~BackupDataReader()
+{
+}
+
+status_t
+BackupDataReader::Status()
+{
+ return m_status;
+}
+
+#define CHECK_SIZE(actual, expected) \
+ do { \
+ if ((actual) != (expected)) { \
+ if ((actual) == 0) { \
+ m_status = EIO; \
+ m_done = true; \
+ } else { \
+ m_status = errno; \
+ ALOGD("CHECK_SIZE(a=%ld e=%ld) failed at line %d m_status='%s'", \
+ long(actual), long(expected), __LINE__, strerror(m_status)); \
+ } \
+ return m_status; \
+ } \
+ } while(0)
+#define SKIP_PADDING() \
+ do { \
+ status_t err = skip_padding(); \
+ if (err != NO_ERROR) { \
+ ALOGD("SKIP_PADDING FAILED at line %d", __LINE__); \
+ m_status = err; \
+ return err; \
+ } \
+ } while(0)
+
+status_t
+BackupDataReader::ReadNextHeader(bool* done, int* type)
+{
+ *done = m_done;
+ if (m_status != NO_ERROR) {
+ return m_status;
+ }
+
+ int amt;
+
+ amt = skip_padding();
+ if (amt == EIO) {
+ *done = m_done = true;
+ return NO_ERROR;
+ }
+ else if (amt != NO_ERROR) {
+ return amt;
+ }
+ amt = read(m_fd, &m_header, sizeof(m_header));
+ *done = m_done = (amt == 0);
+ if (*done) {
+ return NO_ERROR;
+ }
+ CHECK_SIZE(amt, sizeof(m_header));
+ m_pos += sizeof(m_header);
+ if (type) {
+ *type = m_header.type;
+ }
+
+ // validate and fix up the fields.
+ m_header.type = fromlel(m_header.type);
+ switch (m_header.type)
+ {
+ case BACKUP_HEADER_ENTITY_V1:
+ {
+ m_header.entity.keyLen = fromlel(m_header.entity.keyLen);
+ if (m_header.entity.keyLen <= 0) {
+ ALOGD("Entity header at %d has keyLen<=0: 0x%08x\n", (int)m_pos,
+ (int)m_header.entity.keyLen);
+ m_status = EINVAL;
+ }
+ m_header.entity.dataSize = fromlel(m_header.entity.dataSize);
+ m_entityCount++;
+
+ // read the rest of the header (filename)
+ size_t size = m_header.entity.keyLen;
+ char* buf = m_key.lockBuffer(size);
+ if (buf == NULL) {
+ m_status = ENOMEM;
+ return m_status;
+ }
+ int amt = read(m_fd, buf, size+1);
+ CHECK_SIZE(amt, (int)size+1);
+ m_key.unlockBuffer(size);
+ m_pos += size+1;
+ SKIP_PADDING();
+ m_dataEndPos = m_pos + m_header.entity.dataSize;
+
+ break;
+ }
+ default:
+ ALOGD("Chunk header at %d has invalid type: 0x%08x",
+ (int)(m_pos - sizeof(m_header)), (int)m_header.type);
+ m_status = EINVAL;
+ }
+
+ return m_status;
+}
+
+bool
+BackupDataReader::HasEntities()
+{
+ return m_status == NO_ERROR && m_header.type == BACKUP_HEADER_ENTITY_V1;
+}
+
+status_t
+BackupDataReader::ReadEntityHeader(String8* key, size_t* dataSize)
+{
+ if (m_status != NO_ERROR) {
+ return m_status;
+ }
+ if (m_header.type != BACKUP_HEADER_ENTITY_V1) {
+ return EINVAL;
+ }
+ *key = m_key;
+ *dataSize = m_header.entity.dataSize;
+ return NO_ERROR;
+}
+
+status_t
+BackupDataReader::SkipEntityData()
+{
+ if (m_status != NO_ERROR) {
+ return m_status;
+ }
+ if (m_header.type != BACKUP_HEADER_ENTITY_V1) {
+ return EINVAL;
+ }
+ if (m_header.entity.dataSize > 0) {
+ int pos = lseek(m_fd, m_dataEndPos, SEEK_SET);
+ if (pos == -1) {
+ return errno;
+ }
+ m_pos = pos;
+ }
+ SKIP_PADDING();
+ return NO_ERROR;
+}
+
+ssize_t
+BackupDataReader::ReadEntityData(void* data, size_t size)
+{
+ if (m_status != NO_ERROR) {
+ return -1;
+ }
+ int remaining = m_dataEndPos - m_pos;
+ //ALOGD("ReadEntityData size=%d m_pos=0x%x m_dataEndPos=0x%x remaining=%d\n",
+ // size, m_pos, m_dataEndPos, remaining);
+ if (remaining <= 0) {
+ return 0;
+ }
+ if (((int)size) > remaining) {
+ size = remaining;
+ }
+ //ALOGD(" reading %d bytes", size);
+ int amt = read(m_fd, data, size);
+ if (amt < 0) {
+ m_status = errno;
+ return -1;
+ }
+ if (amt == 0) {
+ m_status = EIO;
+ m_done = true;
+ }
+ m_pos += amt;
+ return amt;
+}
+
+status_t
+BackupDataReader::skip_padding()
+{
+ ssize_t amt;
+ ssize_t paddingSize;
+
+ paddingSize = padding_extra(m_pos);
+ if (paddingSize > 0) {
+ uint32_t padding;
+ amt = read(m_fd, &padding, paddingSize);
+ CHECK_SIZE(amt, paddingSize);
+ m_pos += amt;
+ }
+ return NO_ERROR;
+}
+
+
+} // namespace android
diff --git a/libs/androidfw/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp
new file mode 100644
index 0000000..b8d3f48
--- /dev/null
+++ b/libs/androidfw/BackupHelpers.cpp
@@ -0,0 +1,1591 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "file_backup_helper"
+
+#include <androidfw/BackupHelpers.h>
+
+#include <utils/KeyedVector.h>
+#include <utils/ByteOrder.h>
+#include <utils/String8.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <sys/time.h> // for utimes
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <zlib.h>
+
+#include <cutils/log.h>
+
+namespace android {
+
+#define MAGIC0 0x70616e53 // Snap
+#define MAGIC1 0x656c6946 // File
+
+/*
+ * File entity data format (v1):
+ *
+ * - 4-byte version number of the metadata, little endian (0x00000001 for v1)
+ * - 12 bytes of metadata
+ * - the file data itself
+ *
+ * i.e. a 16-byte metadata header followed by the raw file data. If the
+ * restore code does not recognize the metadata version, it can still
+ * interpret the file data itself correctly.
+ *
+ * file_metadata_v1:
+ *
+ * - 4 byte version number === 0x00000001 (little endian)
+ * - 4-byte access mode (little-endian)
+ * - undefined (8 bytes)
+ */
+
+struct file_metadata_v1 {
+ int version;
+ int mode;
+ int undefined_1;
+ int undefined_2;
+};
+
+const static int CURRENT_METADATA_VERSION = 1;
+
+#if 1
+#define LOGP(f, x...)
+#else
+#if TEST_BACKUP_HELPERS
+#define LOGP(f, x...) printf(f "\n", x)
+#else
+#define LOGP(x...) ALOGD(x)
+#endif
+#endif
+
+const static int ROUND_UP[4] = { 0, 3, 2, 1 };
+
+static inline int
+round_up(int n)
+{
+ return n + ROUND_UP[n % 4];
+}
+
+static int
+read_snapshot_file(int fd, KeyedVector<String8,FileState>* snapshot)
+{
+ int bytesRead = 0;
+ int amt;
+ SnapshotHeader header;
+
+ amt = read(fd, &header, sizeof(header));
+ if (amt != sizeof(header)) {
+ return errno;
+ }
+ bytesRead += amt;
+
+ if (header.magic0 != MAGIC0 || header.magic1 != MAGIC1) {
+ ALOGW("read_snapshot_file header.magic0=0x%08x magic1=0x%08x", header.magic0, header.magic1);
+ return 1;
+ }
+
+ for (int i=0; i<header.fileCount; i++) {
+ FileState file;
+ char filenameBuf[128];
+
+ amt = read(fd, &file, sizeof(FileState));
+ if (amt != sizeof(FileState)) {
+ ALOGW("read_snapshot_file FileState truncated/error with read at %d bytes\n", bytesRead);
+ return 1;
+ }
+ bytesRead += amt;
+
+ // filename is not NULL terminated, but it is padded
+ int nameBufSize = round_up(file.nameLen);
+ char* filename = nameBufSize <= (int)sizeof(filenameBuf)
+ ? filenameBuf
+ : (char*)malloc(nameBufSize);
+ amt = read(fd, filename, nameBufSize);
+ if (amt == nameBufSize) {
+ snapshot->add(String8(filename, file.nameLen), file);
+ }
+ bytesRead += amt;
+ if (filename != filenameBuf) {
+ free(filename);
+ }
+ if (amt != nameBufSize) {
+ ALOGW("read_snapshot_file filename truncated/error with read at %d bytes\n", bytesRead);
+ return 1;
+ }
+ }
+
+ if (header.totalSize != bytesRead) {
+ ALOGW("read_snapshot_file length mismatch: header.totalSize=%d bytesRead=%d\n",
+ header.totalSize, bytesRead);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+write_snapshot_file(int fd, const KeyedVector<String8,FileRec>& snapshot)
+{
+ int fileCount = 0;
+ int bytesWritten = sizeof(SnapshotHeader);
+ // preflight size
+ const int N = snapshot.size();
+ for (int i=0; i<N; i++) {
+ const FileRec& g = snapshot.valueAt(i);
+ if (!g.deleted) {
+ const String8& name = snapshot.keyAt(i);
+ bytesWritten += sizeof(FileState) + round_up(name.length());
+ fileCount++;
+ }
+ }
+
+ LOGP("write_snapshot_file fd=%d\n", fd);
+
+ int amt;
+ SnapshotHeader header = { MAGIC0, fileCount, MAGIC1, bytesWritten };
+
+ amt = write(fd, &header, sizeof(header));
+ if (amt != sizeof(header)) {
+ ALOGW("write_snapshot_file error writing header %s", strerror(errno));
+ return errno;
+ }
+
+ for (int i=0; i<N; i++) {
+ FileRec r = snapshot.valueAt(i);
+ if (!r.deleted) {
+ const String8& name = snapshot.keyAt(i);
+ int nameLen = r.s.nameLen = name.length();
+
+ amt = write(fd, &r.s, sizeof(FileState));
+ if (amt != sizeof(FileState)) {
+ ALOGW("write_snapshot_file error writing header %s", strerror(errno));
+ return 1;
+ }
+
+ // filename is not NULL terminated, but it is padded
+ amt = write(fd, name.string(), nameLen);
+ if (amt != nameLen) {
+ ALOGW("write_snapshot_file error writing filename %s", strerror(errno));
+ return 1;
+ }
+ int paddingLen = ROUND_UP[nameLen % 4];
+ if (paddingLen != 0) {
+ int padding = 0xabababab;
+ amt = write(fd, &padding, paddingLen);
+ if (amt != paddingLen) {
+ ALOGW("write_snapshot_file error writing %d bytes of filename padding %s",
+ paddingLen, strerror(errno));
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int
+write_delete_file(BackupDataWriter* dataStream, const String8& key)
+{
+ LOGP("write_delete_file %s\n", key.string());
+ return dataStream->WriteEntityHeader(key, -1);
+}
+
+static int
+write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8& key,
+ char const* realFilename)
+{
+ LOGP("write_update_file %s (%s) : mode 0%o\n", realFilename, key.string(), mode);
+
+ const int bufsize = 4*1024;
+ int err;
+ int amt;
+ int fileSize;
+ int bytesLeft;
+ file_metadata_v1 metadata;
+
+ char* buf = (char*)malloc(bufsize);
+ int crc = crc32(0L, Z_NULL, 0);
+
+
+ fileSize = lseek(fd, 0, SEEK_END);
+ lseek(fd, 0, SEEK_SET);
+
+ if (sizeof(metadata) != 16) {
+ ALOGE("ERROR: metadata block is the wrong size!");
+ }
+
+ bytesLeft = fileSize + sizeof(metadata);
+ err = dataStream->WriteEntityHeader(key, bytesLeft);
+ if (err != 0) {
+ free(buf);
+ return err;
+ }
+
+ // store the file metadata first
+ metadata.version = tolel(CURRENT_METADATA_VERSION);
+ metadata.mode = tolel(mode);
+ metadata.undefined_1 = metadata.undefined_2 = 0;
+ err = dataStream->WriteEntityData(&metadata, sizeof(metadata));
+ if (err != 0) {
+ free(buf);
+ return err;
+ }
+ bytesLeft -= sizeof(metadata); // bytesLeft should == fileSize now
+
+ // now store the file content
+ while ((amt = read(fd, buf, bufsize)) != 0 && bytesLeft > 0) {
+ bytesLeft -= amt;
+ if (bytesLeft < 0) {
+ amt += bytesLeft; // Plus a negative is minus. Don't write more than we promised.
+ }
+ err = dataStream->WriteEntityData(buf, amt);
+ if (err != 0) {
+ free(buf);
+ return err;
+ }
+ }
+ if (bytesLeft != 0) {
+ if (bytesLeft > 0) {
+ // Pad out the space we promised in the buffer. We can't corrupt the buffer,
+ // even though the data we're sending is probably bad.
+ memset(buf, 0, bufsize);
+ while (bytesLeft > 0) {
+ amt = bytesLeft < bufsize ? bytesLeft : bufsize;
+ bytesLeft -= amt;
+ err = dataStream->WriteEntityData(buf, amt);
+ if (err != 0) {
+ free(buf);
+ return err;
+ }
+ }
+ }
+ ALOGE("write_update_file size mismatch for %s. expected=%d actual=%d."
+ " You aren't doing proper locking!", realFilename, fileSize, fileSize-bytesLeft);
+ }
+
+ free(buf);
+ return NO_ERROR;
+}
+
+static int
+write_update_file(BackupDataWriter* dataStream, const String8& key, char const* realFilename)
+{
+ int err;
+ struct stat st;
+
+ err = stat(realFilename, &st);
+ if (err < 0) {
+ return errno;
+ }
+
+ int fd = open(realFilename, O_RDONLY);
+ if (fd == -1) {
+ return errno;
+ }
+
+ err = write_update_file(dataStream, fd, st.st_mode, key, realFilename);
+ close(fd);
+ return err;
+}
+
+static int
+compute_crc32(int fd)
+{
+ const int bufsize = 4*1024;
+ int amt;
+
+ char* buf = (char*)malloc(bufsize);
+ int crc = crc32(0L, Z_NULL, 0);
+
+ lseek(fd, 0, SEEK_SET);
+
+ while ((amt = read(fd, buf, bufsize)) != 0) {
+ crc = crc32(crc, (Bytef*)buf, amt);
+ }
+
+ free(buf);
+ return crc;
+}
+
+int
+back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD,
+ char const* const* files, char const* const* keys, int fileCount)
+{
+ int err;
+ KeyedVector<String8,FileState> oldSnapshot;
+ KeyedVector<String8,FileRec> newSnapshot;
+
+ if (oldSnapshotFD != -1) {
+ err = read_snapshot_file(oldSnapshotFD, &oldSnapshot);
+ if (err != 0) {
+ // On an error, treat this as a full backup.
+ oldSnapshot.clear();
+ }
+ }
+
+ for (int i=0; i<fileCount; i++) {
+ String8 key(keys[i]);
+ FileRec r;
+ char const* file = files[i];
+ r.file = file;
+ struct stat st;
+
+ err = stat(file, &st);
+ if (err != 0) {
+ r.deleted = true;
+ } else {
+ r.deleted = false;
+ r.s.modTime_sec = st.st_mtime;
+ r.s.modTime_nsec = 0; // workaround sim breakage
+ //r.s.modTime_nsec = st.st_mtime_nsec;
+ r.s.mode = st.st_mode;
+ r.s.size = st.st_size;
+ // we compute the crc32 later down below, when we already have the file open.
+
+ if (newSnapshot.indexOfKey(key) >= 0) {
+ LOGP("back_up_files key already in use '%s'", key.string());
+ return -1;
+ }
+ }
+ newSnapshot.add(key, r);
+ }
+
+ int n = 0;
+ int N = oldSnapshot.size();
+ int m = 0;
+
+ while (n<N && m<fileCount) {
+ const String8& p = oldSnapshot.keyAt(n);
+ const String8& q = newSnapshot.keyAt(m);
+ FileRec& g = newSnapshot.editValueAt(m);
+ int cmp = p.compare(q);
+ if (g.deleted || cmp < 0) {
+ // file removed
+ LOGP("file removed: %s", p.string());
+ g.deleted = true; // They didn't mention the file, but we noticed that it's gone.
+ dataStream->WriteEntityHeader(p, -1);
+ n++;
+ }
+ else if (cmp > 0) {
+ // file added
+ LOGP("file added: %s", g.file.string());
+ write_update_file(dataStream, q, g.file.string());
+ m++;
+ }
+ else {
+ // both files exist, check them
+ const FileState& f = oldSnapshot.valueAt(n);
+
+ int fd = open(g.file.string(), O_RDONLY);
+ if (fd < 0) {
+ // We can't open the file. Don't report it as a delete either. Let the
+ // server keep the old version. Maybe they'll be able to deal with it
+ // on restore.
+ LOGP("Unable to open file %s - skipping", g.file.string());
+ } else {
+ g.s.crc32 = compute_crc32(fd);
+
+ LOGP("%s", q.string());
+ LOGP(" new: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x",
+ f.modTime_sec, f.modTime_nsec, f.mode, f.size, f.crc32);
+ LOGP(" old: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x",
+ g.s.modTime_sec, g.s.modTime_nsec, g.s.mode, g.s.size, g.s.crc32);
+ if (f.modTime_sec != g.s.modTime_sec || f.modTime_nsec != g.s.modTime_nsec
+ || f.mode != g.s.mode || f.size != g.s.size || f.crc32 != g.s.crc32) {
+ write_update_file(dataStream, fd, g.s.mode, p, g.file.string());
+ }
+
+ close(fd);
+ }
+ n++;
+ m++;
+ }
+ }
+
+ // these were deleted
+ while (n<N) {
+ dataStream->WriteEntityHeader(oldSnapshot.keyAt(n), -1);
+ n++;
+ }
+
+ // these were added
+ while (m<fileCount) {
+ const String8& q = newSnapshot.keyAt(m);
+ FileRec& g = newSnapshot.editValueAt(m);
+ write_update_file(dataStream, q, g.file.string());
+ m++;
+ }
+
+ err = write_snapshot_file(newSnapshotFD, newSnapshot);
+
+ return 0;
+}
+
+// Utility function, equivalent to stpcpy(): perform a strcpy, but instead of
+// returning the initial dest, return a pointer to the trailing NUL.
+static char* strcpy_ptr(char* dest, const char* str) {
+ if (dest && str) {
+ while ((*dest = *str) != 0) {
+ dest++;
+ str++;
+ }
+ }
+ return dest;
+}
+
+static void calc_tar_checksum(char* buf) {
+ // [ 148 : 8 ] checksum -- to be calculated with this field as space chars
+ memset(buf + 148, ' ', 8);
+
+ uint16_t sum = 0;
+ for (uint8_t* p = (uint8_t*) buf; p < ((uint8_t*)buf) + 512; p++) {
+ sum += *p;
+ }
+
+ // Now write the real checksum value:
+ // [ 148 : 8 ] checksum: 6 octal digits [leading zeroes], NUL, SPC
+ sprintf(buf + 148, "%06o", sum); // the trailing space is already in place
+}
+
+// Returns number of bytes written
+static int write_pax_header_entry(char* buf, const char* key, const char* value) {
+ // start with the size of "1 key=value\n"
+ int len = strlen(key) + strlen(value) + 4;
+ if (len > 9) len++;
+ if (len > 99) len++;
+ if (len > 999) len++;
+ // since PATH_MAX is 4096 we don't expect to have to generate any single
+ // header entry longer than 9999 characters
+
+ return sprintf(buf, "%d %s=%s\n", len, key, value);
+}
+
+// Wire format to the backup manager service is chunked: each chunk is prefixed by
+// a 4-byte count of its size. A chunk size of zero (four zero bytes) indicates EOD.
+void send_tarfile_chunk(BackupDataWriter* writer, const char* buffer, size_t size) {
+ uint32_t chunk_size_no = htonl(size);
+ writer->WriteEntityData(&chunk_size_no, 4);
+ if (size != 0) writer->WriteEntityData(buffer, size);
+}
+
+int write_tarfile(const String8& packageName, const String8& domain,
+ const String8& rootpath, const String8& filepath, BackupDataWriter* writer)
+{
+ // In the output stream everything is stored relative to the root
+ const char* relstart = filepath.string() + rootpath.length();
+ if (*relstart == '/') relstart++; // won't be true when path == rootpath
+ String8 relpath(relstart);
+
+ // If relpath is empty, it means this is the top of one of the standard named
+ // domain directories, so we should just skip it
+ if (relpath.length() == 0) {
+ return 0;
+ }
+
+ // Too long a name for the ustar format?
+ // "apps/" + packagename + '/' + domainpath < 155 chars
+ // relpath < 100 chars
+ bool needExtended = false;
+ if ((5 + packageName.length() + 1 + domain.length() >= 155) || (relpath.length() >= 100)) {
+ needExtended = true;
+ }
+
+ // Non-7bit-clean path also means needing pax extended format
+ if (!needExtended) {
+ for (size_t i = 0; i < filepath.length(); i++) {
+ if ((filepath[i] & 0x80) != 0) {
+ needExtended = true;
+ break;
+ }
+ }
+ }
+
+ int err = 0;
+ struct stat64 s;
+ if (lstat64(filepath.string(), &s) != 0) {
+ err = errno;
+ ALOGE("Error %d (%s) from lstat64(%s)", err, strerror(err), filepath.string());
+ return err;
+ }
+
+ String8 fullname; // for pax later on
+ String8 prefix;
+
+ const int isdir = S_ISDIR(s.st_mode);
+ if (isdir) s.st_size = 0; // directories get no actual data in the tar stream
+
+ // !!! TODO: use mmap when possible to avoid churning the buffer cache
+ // !!! TODO: this will break with symlinks; need to use readlink(2)
+ int fd = open(filepath.string(), O_RDONLY);
+ if (fd < 0) {
+ err = errno;
+ ALOGE("Error %d (%s) from open(%s)", err, strerror(err), filepath.string());
+ return err;
+ }
+
+ // read/write up to this much at a time.
+ const size_t BUFSIZE = 32 * 1024;
+ char* buf = (char *)calloc(1,BUFSIZE);
+ char* paxHeader = buf + 512; // use a different chunk of it as separate scratch
+ char* paxData = buf + 1024;
+
+ if (buf == NULL) {
+ ALOGE("Out of mem allocating transfer buffer");
+ err = ENOMEM;
+ goto done;
+ }
+
+ // Magic fields for the ustar file format
+ strcat(buf + 257, "ustar");
+ strcat(buf + 263, "00");
+
+ // [ 265 : 32 ] user name, ignored on restore
+ // [ 297 : 32 ] group name, ignored on restore
+
+ // [ 100 : 8 ] file mode
+ snprintf(buf + 100, 8, "%06o ", s.st_mode & ~S_IFMT);
+
+ // [ 108 : 8 ] uid -- ignored in Android format; uids are remapped at restore time
+ // [ 116 : 8 ] gid -- ignored in Android format
+ snprintf(buf + 108, 8, "0%lo", s.st_uid);
+ snprintf(buf + 116, 8, "0%lo", s.st_gid);
+
+ // [ 124 : 12 ] file size in bytes
+ if (s.st_size > 077777777777LL) {
+ // very large files need a pax extended size header
+ needExtended = true;
+ }
+ snprintf(buf + 124, 12, "%011llo", (isdir) ? 0LL : s.st_size);
+
+ // [ 136 : 12 ] last mod time as a UTC time_t
+ snprintf(buf + 136, 12, "%0lo", s.st_mtime);
+
+ // [ 156 : 1 ] link/file type
+ uint8_t type;
+ if (isdir) {
+ type = '5'; // tar magic: '5' == directory
+ } else if (S_ISREG(s.st_mode)) {
+ type = '0'; // tar magic: '0' == normal file
+ } else {
+ ALOGW("Error: unknown file mode 0%o [%s]", s.st_mode, filepath.string());
+ goto cleanup;
+ }
+ buf[156] = type;
+
+ // [ 157 : 100 ] name of linked file [not implemented]
+
+ {
+ // Prefix and main relative path. Path lengths have been preflighted.
+ if (packageName.length() > 0) {
+ prefix = "apps/";
+ prefix += packageName;
+ }
+ if (domain.length() > 0) {
+ prefix.appendPath(domain);
+ }
+
+ // pax extended means we don't put in a prefix field, and put a different
+ // string in the basic name field. We can also construct the full path name
+ // out of the substrings we've now built.
+ fullname = prefix;
+ fullname.appendPath(relpath);
+
+ // ustar:
+ // [ 0 : 100 ]; file name/path
+ // [ 345 : 155 ] filename path prefix
+ // We only use the prefix area if fullname won't fit in the path
+ if (fullname.length() > 100) {
+ strncpy(buf, relpath.string(), 100);
+ strncpy(buf + 345, prefix.string(), 155);
+ } else {
+ strncpy(buf, fullname.string(), 100);
+ }
+ }
+
+ // [ 329 : 8 ] and [ 337 : 8 ] devmajor/devminor, not used
+
+ ALOGI(" Name: %s", fullname.string());
+
+ // If we're using a pax extended header, build & write that here; lengths are
+ // already preflighted
+ if (needExtended) {
+ char sizeStr[32]; // big enough for a 64-bit unsigned value in decimal
+ char* p = paxData;
+
+ // construct the pax extended header data block
+ memset(paxData, 0, BUFSIZE - (paxData - buf));
+ int len;
+
+ // size header -- calc len in digits by actually rendering the number
+ // to a string - brute force but simple
+ snprintf(sizeStr, sizeof(sizeStr), "%lld", s.st_size);
+ p += write_pax_header_entry(p, "size", sizeStr);
+
+ // fullname was generated above with the ustar paths
+ p += write_pax_header_entry(p, "path", fullname.string());
+
+ // Now we know how big the pax data is
+ int paxLen = p - paxData;
+
+ // Now build the pax *header* templated on the ustar header
+ memcpy(paxHeader, buf, 512);
+
+ String8 leaf = fullname.getPathLeaf();
+ memset(paxHeader, 0, 100); // rewrite the name area
+ snprintf(paxHeader, 100, "PaxHeader/%s", leaf.string());
+ memset(paxHeader + 345, 0, 155); // rewrite the prefix area
+ strncpy(paxHeader + 345, prefix.string(), 155);
+
+ paxHeader[156] = 'x'; // mark it as a pax extended header
+
+ // [ 124 : 12 ] size of pax extended header data
+ memset(paxHeader + 124, 0, 12);
+ snprintf(paxHeader + 124, 12, "%011o", p - paxData);
+
+ // Checksum and write the pax block header
+ calc_tar_checksum(paxHeader);
+ send_tarfile_chunk(writer, paxHeader, 512);
+
+ // Now write the pax data itself
+ int paxblocks = (paxLen + 511) / 512;
+ send_tarfile_chunk(writer, paxData, 512 * paxblocks);
+ }
+
+ // Checksum and write the 512-byte ustar file header block to the output
+ calc_tar_checksum(buf);
+ send_tarfile_chunk(writer, buf, 512);
+
+ // Now write the file data itself, for real files. We honor tar's convention that
+ // only full 512-byte blocks are sent to write().
+ if (!isdir) {
+ off64_t toWrite = s.st_size;
+ while (toWrite > 0) {
+ size_t toRead = (toWrite < BUFSIZE) ? toWrite : BUFSIZE;
+ ssize_t nRead = read(fd, buf, toRead);
+ if (nRead < 0) {
+ err = errno;
+ ALOGE("Unable to read file [%s], err=%d (%s)", filepath.string(),
+ err, strerror(err));
+ break;
+ } else if (nRead == 0) {
+ ALOGE("EOF but expect %lld more bytes in [%s]", (long long) toWrite,
+ filepath.string());
+ err = EIO;
+ break;
+ }
+
+ // At EOF we might have a short block; NUL-pad that to a 512-byte multiple. This
+ // depends on the OS guarantee that for ordinary files, read() will never return
+ // less than the number of bytes requested.
+ ssize_t partial = (nRead+512) % 512;
+ if (partial > 0) {
+ ssize_t remainder = 512 - partial;
+ memset(buf + nRead, 0, remainder);
+ nRead += remainder;
+ }
+ send_tarfile_chunk(writer, buf, nRead);
+ toWrite -= nRead;
+ }
+ }
+
+cleanup:
+ free(buf);
+done:
+ close(fd);
+ return err;
+}
+// end tarfile
+
+
+
+#define RESTORE_BUF_SIZE (8*1024)
+
+RestoreHelperBase::RestoreHelperBase()
+{
+ m_buf = malloc(RESTORE_BUF_SIZE);
+ m_loggedUnknownMetadata = false;
+}
+
+RestoreHelperBase::~RestoreHelperBase()
+{
+ free(m_buf);
+}
+
+status_t
+RestoreHelperBase::WriteFile(const String8& filename, BackupDataReader* in)
+{
+ ssize_t err;
+ size_t dataSize;
+ String8 key;
+ int fd;
+ void* buf = m_buf;
+ ssize_t amt;
+ int mode;
+ int crc;
+ struct stat st;
+ FileRec r;
+
+ err = in->ReadEntityHeader(&key, &dataSize);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ // Get the metadata block off the head of the file entity and use that to
+ // set up the output file
+ file_metadata_v1 metadata;
+ amt = in->ReadEntityData(&metadata, sizeof(metadata));
+ if (amt != sizeof(metadata)) {
+ ALOGW("Could not read metadata for %s -- %ld / %s", filename.string(),
+ (long)amt, strerror(errno));
+ return EIO;
+ }
+ metadata.version = fromlel(metadata.version);
+ metadata.mode = fromlel(metadata.mode);
+ if (metadata.version > CURRENT_METADATA_VERSION) {
+ if (!m_loggedUnknownMetadata) {
+ m_loggedUnknownMetadata = true;
+ ALOGW("Restoring file with unsupported metadata version %d (currently %d)",
+ metadata.version, CURRENT_METADATA_VERSION);
+ }
+ }
+ mode = metadata.mode;
+
+ // Write the file and compute the crc
+ crc = crc32(0L, Z_NULL, 0);
+ fd = open(filename.string(), O_CREAT|O_RDWR|O_TRUNC, mode);
+ if (fd == -1) {
+ ALOGW("Could not open file %s -- %s", filename.string(), strerror(errno));
+ return errno;
+ }
+
+ while ((amt = in->ReadEntityData(buf, RESTORE_BUF_SIZE)) > 0) {
+ err = write(fd, buf, amt);
+ if (err != amt) {
+ close(fd);
+ ALOGW("Error '%s' writing '%s'", strerror(errno), filename.string());
+ return errno;
+ }
+ crc = crc32(crc, (Bytef*)buf, amt);
+ }
+
+ close(fd);
+
+ // Record for the snapshot
+ err = stat(filename.string(), &st);
+ if (err != 0) {
+ ALOGW("Error stating file that we just created %s", filename.string());
+ return errno;
+ }
+
+ r.file = filename;
+ r.deleted = false;
+ r.s.modTime_sec = st.st_mtime;
+ r.s.modTime_nsec = 0; // workaround sim breakage
+ //r.s.modTime_nsec = st.st_mtime_nsec;
+ r.s.mode = st.st_mode;
+ r.s.size = st.st_size;
+ r.s.crc32 = crc;
+
+ m_files.add(key, r);
+
+ return NO_ERROR;
+}
+
+status_t
+RestoreHelperBase::WriteSnapshot(int fd)
+{
+ return write_snapshot_file(fd, m_files);;
+}
+
+#if TEST_BACKUP_HELPERS
+
+#define SCRATCH_DIR "/data/backup_helper_test/"
+
+static int
+write_text_file(const char* path, const char* data)
+{
+ int amt;
+ int fd;
+ int len;
+
+ fd = creat(path, 0666);
+ if (fd == -1) {
+ fprintf(stderr, "creat %s failed\n", path);
+ return errno;
+ }
+
+ len = strlen(data);
+ amt = write(fd, data, len);
+ if (amt != len) {
+ fprintf(stderr, "error (%s) writing to file %s\n", strerror(errno), path);
+ return errno;
+ }
+
+ close(fd);
+
+ return 0;
+}
+
+static int
+compare_file(const char* path, const unsigned char* data, int len)
+{
+ int fd;
+ int amt;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ fprintf(stderr, "compare_file error (%s) opening %s\n", strerror(errno), path);
+ return errno;
+ }
+
+ unsigned char* contents = (unsigned char*)malloc(len);
+ if (contents == NULL) {
+ fprintf(stderr, "malloc(%d) failed\n", len);
+ return ENOMEM;
+ }
+
+ bool sizesMatch = true;
+ amt = lseek(fd, 0, SEEK_END);
+ if (amt != len) {
+ fprintf(stderr, "compare_file file length should be %d, was %d\n", len, amt);
+ sizesMatch = false;
+ }
+ lseek(fd, 0, SEEK_SET);
+
+ int readLen = amt < len ? amt : len;
+ amt = read(fd, contents, readLen);
+ if (amt != readLen) {
+ fprintf(stderr, "compare_file read expected %d bytes but got %d\n", len, amt);
+ }
+
+ bool contentsMatch = true;
+ for (int i=0; i<readLen; i++) {
+ if (data[i] != contents[i]) {
+ if (contentsMatch) {
+ fprintf(stderr, "compare_file contents are different: (index, expected, actual)\n");
+ contentsMatch = false;
+ }
+ fprintf(stderr, " [%-2d] %02x %02x\n", i, data[i], contents[i]);
+ }
+ }
+
+ free(contents);
+ return contentsMatch && sizesMatch ? 0 : 1;
+}
+
+int
+backup_helper_test_empty()
+{
+ int err;
+ int fd;
+ KeyedVector<String8,FileRec> snapshot;
+ const char* filename = SCRATCH_DIR "backup_helper_test_empty.snap";
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+
+ // write
+ fd = creat(filename, 0666);
+ if (fd == -1) {
+ fprintf(stderr, "error creating %s\n", filename);
+ return 1;
+ }
+
+ err = write_snapshot_file(fd, snapshot);
+
+ close(fd);
+
+ if (err != 0) {
+ fprintf(stderr, "write_snapshot_file reported error %d (%s)\n", err, strerror(err));
+ return err;
+ }
+
+ static const unsigned char correct_data[] = {
+ 0x53, 0x6e, 0x61, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x46, 0x69, 0x6c, 0x65, 0x10, 0x00, 0x00, 0x00
+ };
+
+ err = compare_file(filename, correct_data, sizeof(correct_data));
+ if (err != 0) {
+ return err;
+ }
+
+ // read
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ fprintf(stderr, "error opening for read %s\n", filename);
+ return 1;
+ }
+
+ KeyedVector<String8,FileState> readSnapshot;
+ err = read_snapshot_file(fd, &readSnapshot);
+ if (err != 0) {
+ fprintf(stderr, "read_snapshot_file failed %d\n", err);
+ return err;
+ }
+
+ if (readSnapshot.size() != 0) {
+ fprintf(stderr, "readSnapshot should be length 0\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+backup_helper_test_four()
+{
+ int err;
+ int fd;
+ KeyedVector<String8,FileRec> snapshot;
+ const char* filename = SCRATCH_DIR "backup_helper_test_four.snap";
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+
+ // write
+ fd = creat(filename, 0666);
+ if (fd == -1) {
+ fprintf(stderr, "error opening %s\n", filename);
+ return 1;
+ }
+
+ String8 filenames[4];
+ FileState states[4];
+ FileRec r;
+ r.deleted = false;
+
+ states[0].modTime_sec = 0xfedcba98;
+ states[0].modTime_nsec = 0xdeadbeef;
+ states[0].mode = 0777; // decimal 511, hex 0x000001ff
+ states[0].size = 0xababbcbc;
+ states[0].crc32 = 0x12345678;
+ states[0].nameLen = -12;
+ r.s = states[0];
+ filenames[0] = String8("bytes_of_padding");
+ snapshot.add(filenames[0], r);
+
+ states[1].modTime_sec = 0x93400031;
+ states[1].modTime_nsec = 0xdeadbeef;
+ states[1].mode = 0666; // decimal 438, hex 0x000001b6
+ states[1].size = 0x88557766;
+ states[1].crc32 = 0x22334422;
+ states[1].nameLen = -1;
+ r.s = states[1];
+ filenames[1] = String8("bytes_of_padding3");
+ snapshot.add(filenames[1], r);
+
+ states[2].modTime_sec = 0x33221144;
+ states[2].modTime_nsec = 0xdeadbeef;
+ states[2].mode = 0744; // decimal 484, hex 0x000001e4
+ states[2].size = 0x11223344;
+ states[2].crc32 = 0x01122334;
+ states[2].nameLen = 0;
+ r.s = states[2];
+ filenames[2] = String8("bytes_of_padding_2");
+ snapshot.add(filenames[2], r);
+
+ states[3].modTime_sec = 0x33221144;
+ states[3].modTime_nsec = 0xdeadbeef;
+ states[3].mode = 0755; // decimal 493, hex 0x000001ed
+ states[3].size = 0x11223344;
+ states[3].crc32 = 0x01122334;
+ states[3].nameLen = 0;
+ r.s = states[3];
+ filenames[3] = String8("bytes_of_padding__1");
+ snapshot.add(filenames[3], r);
+
+ err = write_snapshot_file(fd, snapshot);
+
+ close(fd);
+
+ if (err != 0) {
+ fprintf(stderr, "write_snapshot_file reported error %d (%s)\n", err, strerror(err));
+ return err;
+ }
+
+ static const unsigned char correct_data[] = {
+ // header
+ 0x53, 0x6e, 0x61, 0x70, 0x04, 0x00, 0x00, 0x00,
+ 0x46, 0x69, 0x6c, 0x65, 0xbc, 0x00, 0x00, 0x00,
+
+ // bytes_of_padding
+ 0x98, 0xba, 0xdc, 0xfe, 0xef, 0xbe, 0xad, 0xde,
+ 0xff, 0x01, 0x00, 0x00, 0xbc, 0xbc, 0xab, 0xab,
+ 0x78, 0x56, 0x34, 0x12, 0x10, 0x00, 0x00, 0x00,
+ 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66,
+ 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
+
+ // bytes_of_padding3
+ 0x31, 0x00, 0x40, 0x93, 0xef, 0xbe, 0xad, 0xde,
+ 0xb6, 0x01, 0x00, 0x00, 0x66, 0x77, 0x55, 0x88,
+ 0x22, 0x44, 0x33, 0x22, 0x11, 0x00, 0x00, 0x00,
+ 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66,
+ 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
+ 0x33, 0xab, 0xab, 0xab,
+
+ // bytes of padding2
+ 0x44, 0x11, 0x22, 0x33, 0xef, 0xbe, 0xad, 0xde,
+ 0xe4, 0x01, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11,
+ 0x34, 0x23, 0x12, 0x01, 0x12, 0x00, 0x00, 0x00,
+ 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66,
+ 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
+ 0x5f, 0x32, 0xab, 0xab,
+
+ // bytes of padding3
+ 0x44, 0x11, 0x22, 0x33, 0xef, 0xbe, 0xad, 0xde,
+ 0xed, 0x01, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11,
+ 0x34, 0x23, 0x12, 0x01, 0x13, 0x00, 0x00, 0x00,
+ 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66,
+ 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
+ 0x5f, 0x5f, 0x31, 0xab
+ };
+
+ err = compare_file(filename, correct_data, sizeof(correct_data));
+ if (err != 0) {
+ return err;
+ }
+
+ // read
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ fprintf(stderr, "error opening for read %s\n", filename);
+ return 1;
+ }
+
+
+ KeyedVector<String8,FileState> readSnapshot;
+ err = read_snapshot_file(fd, &readSnapshot);
+ if (err != 0) {
+ fprintf(stderr, "read_snapshot_file failed %d\n", err);
+ return err;
+ }
+
+ if (readSnapshot.size() != 4) {
+ fprintf(stderr, "readSnapshot should be length 4 is %d\n", readSnapshot.size());
+ return 1;
+ }
+
+ bool matched = true;
+ for (size_t i=0; i<readSnapshot.size(); i++) {
+ const String8& name = readSnapshot.keyAt(i);
+ const FileState state = readSnapshot.valueAt(i);
+
+ if (name != filenames[i] || states[i].modTime_sec != state.modTime_sec
+ || states[i].modTime_nsec != state.modTime_nsec || states[i].mode != state.mode
+ || states[i].size != state.size || states[i].crc32 != states[i].crc32) {
+ fprintf(stderr, "state %d expected={%d/%d, 0x%08x, %04o, 0x%08x, %3d} '%s'\n"
+ " actual={%d/%d, 0x%08x, %04o, 0x%08x, %3d} '%s'\n", i,
+ states[i].modTime_sec, states[i].modTime_nsec, states[i].mode, states[i].size,
+ states[i].crc32, name.length(), filenames[i].string(),
+ state.modTime_sec, state.modTime_nsec, state.mode, state.size, state.crc32,
+ state.nameLen, name.string());
+ matched = false;
+ }
+ }
+
+ return matched ? 0 : 1;
+}
+
+// hexdump -v -e '" " 8/1 " 0x%02x," "\n"' data_writer.data
+const unsigned char DATA_GOLDEN_FILE[] = {
+ 0x44, 0x61, 0x74, 0x61, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x6e, 0x6f, 0x5f, 0x70,
+ 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x00,
+ 0x6e, 0x6f, 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69,
+ 0x6e, 0x67, 0x5f, 0x00, 0x44, 0x61, 0x74, 0x61,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x70, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x74,
+ 0x6f, 0x5f, 0x5f, 0x33, 0x00, 0xbc, 0xbc, 0xbc,
+ 0x70, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x74,
+ 0x6f, 0x5f, 0x5f, 0x33, 0x00, 0xbc, 0xbc, 0xbc,
+ 0x44, 0x61, 0x74, 0x61, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x64,
+ 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x32, 0x5f,
+ 0x5f, 0x00, 0xbc, 0xbc, 0x70, 0x61, 0x64, 0x64,
+ 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x32, 0x5f,
+ 0x5f, 0x00, 0xbc, 0xbc, 0x44, 0x61, 0x74, 0x61,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x70, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x74,
+ 0x6f, 0x31, 0x00, 0xbc, 0x70, 0x61, 0x64, 0x64,
+ 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x31, 0x00
+
+};
+const int DATA_GOLDEN_FILE_SIZE = sizeof(DATA_GOLDEN_FILE);
+
+static int
+test_write_header_and_entity(BackupDataWriter& writer, const char* str)
+{
+ int err;
+ String8 text(str);
+
+ err = writer.WriteEntityHeader(text, text.length()+1);
+ if (err != 0) {
+ fprintf(stderr, "WriteEntityHeader failed with %s\n", strerror(err));
+ return err;
+ }
+
+ err = writer.WriteEntityData(text.string(), text.length()+1);
+ if (err != 0) {
+ fprintf(stderr, "write failed for data '%s'\n", text.string());
+ return errno;
+ }
+
+ return err;
+}
+
+int
+backup_helper_test_data_writer()
+{
+ int err;
+ int fd;
+ const char* filename = SCRATCH_DIR "data_writer.data";
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+ mkdir(SCRATCH_DIR "data", 0777);
+
+ fd = creat(filename, 0666);
+ if (fd == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ BackupDataWriter writer(fd);
+
+ err = 0;
+ err |= test_write_header_and_entity(writer, "no_padding_");
+ err |= test_write_header_and_entity(writer, "padded_to__3");
+ err |= test_write_header_and_entity(writer, "padded_to_2__");
+ err |= test_write_header_and_entity(writer, "padded_to1");
+
+ close(fd);
+
+ err = compare_file(filename, DATA_GOLDEN_FILE, DATA_GOLDEN_FILE_SIZE);
+ if (err != 0) {
+ return err;
+ }
+
+ return err;
+}
+
+int
+test_read_header_and_entity(BackupDataReader& reader, const char* str)
+{
+ int err;
+ int bufSize = strlen(str)+1;
+ char* buf = (char*)malloc(bufSize);
+ String8 string;
+ int cookie = 0x11111111;
+ size_t actualSize;
+ bool done;
+ int type;
+ ssize_t nRead;
+
+ // printf("\n\n---------- test_read_header_and_entity -- %s\n\n", str);
+
+ err = reader.ReadNextHeader(&done, &type);
+ if (done) {
+ fprintf(stderr, "should not be done yet\n");
+ goto finished;
+ }
+ if (err != 0) {
+ fprintf(stderr, "ReadNextHeader (for app header) failed with %s\n", strerror(err));
+ goto finished;
+ }
+ if (type != BACKUP_HEADER_ENTITY_V1) {
+ err = EINVAL;
+ fprintf(stderr, "type=0x%08x expected 0x%08x\n", type, BACKUP_HEADER_ENTITY_V1);
+ }
+
+ err = reader.ReadEntityHeader(&string, &actualSize);
+ if (err != 0) {
+ fprintf(stderr, "ReadEntityHeader failed with %s\n", strerror(err));
+ goto finished;
+ }
+ if (string != str) {
+ fprintf(stderr, "ReadEntityHeader expected key '%s' got '%s'\n", str, string.string());
+ err = EINVAL;
+ goto finished;
+ }
+ if ((int)actualSize != bufSize) {
+ fprintf(stderr, "ReadEntityHeader expected dataSize 0x%08x got 0x%08x\n", bufSize,
+ actualSize);
+ err = EINVAL;
+ goto finished;
+ }
+
+ nRead = reader.ReadEntityData(buf, bufSize);
+ if (nRead < 0) {
+ err = reader.Status();
+ fprintf(stderr, "ReadEntityData failed with %s\n", strerror(err));
+ goto finished;
+ }
+
+ if (0 != memcmp(buf, str, bufSize)) {
+ fprintf(stderr, "ReadEntityData expected '%s' but got something starting with "
+ "%02x %02x %02x %02x '%c%c%c%c'\n", str, buf[0], buf[1], buf[2], buf[3],
+ buf[0], buf[1], buf[2], buf[3]);
+ err = EINVAL;
+ goto finished;
+ }
+
+ // The next read will confirm whether it got the right amount of data.
+
+finished:
+ if (err != NO_ERROR) {
+ fprintf(stderr, "test_read_header_and_entity failed with %s\n", strerror(err));
+ }
+ free(buf);
+ return err;
+}
+
+int
+backup_helper_test_data_reader()
+{
+ int err;
+ int fd;
+ const char* filename = SCRATCH_DIR "data_reader.data";
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+ mkdir(SCRATCH_DIR "data", 0777);
+
+ fd = creat(filename, 0666);
+ if (fd == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ err = write(fd, DATA_GOLDEN_FILE, DATA_GOLDEN_FILE_SIZE);
+ if (err != DATA_GOLDEN_FILE_SIZE) {
+ fprintf(stderr, "Error \"%s\" writing golden file %s\n", strerror(errno), filename);
+ return errno;
+ }
+
+ close(fd);
+
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ fprintf(stderr, "Error \"%s\" opening golden file %s for read\n", strerror(errno),
+ filename);
+ return errno;
+ }
+
+ {
+ BackupDataReader reader(fd);
+
+ err = 0;
+
+ if (err == NO_ERROR) {
+ err = test_read_header_and_entity(reader, "no_padding_");
+ }
+
+ if (err == NO_ERROR) {
+ err = test_read_header_and_entity(reader, "padded_to__3");
+ }
+
+ if (err == NO_ERROR) {
+ err = test_read_header_and_entity(reader, "padded_to_2__");
+ }
+
+ if (err == NO_ERROR) {
+ err = test_read_header_and_entity(reader, "padded_to1");
+ }
+ }
+
+ close(fd);
+
+ return err;
+}
+
+static int
+get_mod_time(const char* filename, struct timeval times[2])
+{
+ int err;
+ struct stat64 st;
+ err = stat64(filename, &st);
+ if (err != 0) {
+ fprintf(stderr, "stat '%s' failed: %s\n", filename, strerror(errno));
+ return errno;
+ }
+ times[0].tv_sec = st.st_atime;
+ times[1].tv_sec = st.st_mtime;
+
+ // If st_atime is a macro then struct stat64 uses struct timespec
+ // to store the access and modif time values and typically
+ // st_*time_nsec is not defined. In glibc, this is controlled by
+ // __USE_MISC.
+#ifdef __USE_MISC
+#if !defined(st_atime) || defined(st_atime_nsec)
+#error "Check if this __USE_MISC conditional is still needed."
+#endif
+ times[0].tv_usec = st.st_atim.tv_nsec / 1000;
+ times[1].tv_usec = st.st_mtim.tv_nsec / 1000;
+#else
+ times[0].tv_usec = st.st_atime_nsec / 1000;
+ times[1].tv_usec = st.st_mtime_nsec / 1000;
+#endif
+
+ return 0;
+}
+
+int
+backup_helper_test_files()
+{
+ int err;
+ int oldSnapshotFD;
+ int dataStreamFD;
+ int newSnapshotFD;
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+ mkdir(SCRATCH_DIR "data", 0777);
+
+ write_text_file(SCRATCH_DIR "data/b", "b\nbb\n");
+ write_text_file(SCRATCH_DIR "data/c", "c\ncc\n");
+ write_text_file(SCRATCH_DIR "data/d", "d\ndd\n");
+ write_text_file(SCRATCH_DIR "data/e", "e\nee\n");
+ write_text_file(SCRATCH_DIR "data/f", "f\nff\n");
+ write_text_file(SCRATCH_DIR "data/h", "h\nhh\n");
+
+ char const* files_before[] = {
+ SCRATCH_DIR "data/b",
+ SCRATCH_DIR "data/c",
+ SCRATCH_DIR "data/d",
+ SCRATCH_DIR "data/e",
+ SCRATCH_DIR "data/f"
+ };
+
+ char const* keys_before[] = {
+ "data/b",
+ "data/c",
+ "data/d",
+ "data/e",
+ "data/f"
+ };
+
+ dataStreamFD = creat(SCRATCH_DIR "1.data", 0666);
+ if (dataStreamFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ newSnapshotFD = creat(SCRATCH_DIR "before.snap", 0666);
+ if (newSnapshotFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ {
+ BackupDataWriter dataStream(dataStreamFD);
+
+ err = back_up_files(-1, &dataStream, newSnapshotFD, files_before, keys_before, 5);
+ if (err != 0) {
+ return err;
+ }
+ }
+
+ close(dataStreamFD);
+ close(newSnapshotFD);
+
+ sleep(3);
+
+ struct timeval d_times[2];
+ struct timeval e_times[2];
+
+ err = get_mod_time(SCRATCH_DIR "data/d", d_times);
+ err |= get_mod_time(SCRATCH_DIR "data/e", e_times);
+ if (err != 0) {
+ return err;
+ }
+
+ write_text_file(SCRATCH_DIR "data/a", "a\naa\n");
+ unlink(SCRATCH_DIR "data/c");
+ write_text_file(SCRATCH_DIR "data/c", "c\ncc\n");
+ write_text_file(SCRATCH_DIR "data/d", "dd\ndd\n");
+ utimes(SCRATCH_DIR "data/d", d_times);
+ write_text_file(SCRATCH_DIR "data/e", "z\nzz\n");
+ utimes(SCRATCH_DIR "data/e", e_times);
+ write_text_file(SCRATCH_DIR "data/g", "g\ngg\n");
+ unlink(SCRATCH_DIR "data/f");
+
+ char const* files_after[] = {
+ SCRATCH_DIR "data/a", // added
+ SCRATCH_DIR "data/b", // same
+ SCRATCH_DIR "data/c", // different mod time
+ SCRATCH_DIR "data/d", // different size (same mod time)
+ SCRATCH_DIR "data/e", // different contents (same mod time, same size)
+ SCRATCH_DIR "data/g" // added
+ };
+
+ char const* keys_after[] = {
+ "data/a", // added
+ "data/b", // same
+ "data/c", // different mod time
+ "data/d", // different size (same mod time)
+ "data/e", // different contents (same mod time, same size)
+ "data/g" // added
+ };
+
+ oldSnapshotFD = open(SCRATCH_DIR "before.snap", O_RDONLY);
+ if (oldSnapshotFD == -1) {
+ fprintf(stderr, "error opening: %s\n", strerror(errno));
+ return errno;
+ }
+
+ dataStreamFD = creat(SCRATCH_DIR "2.data", 0666);
+ if (dataStreamFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ newSnapshotFD = creat(SCRATCH_DIR "after.snap", 0666);
+ if (newSnapshotFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ {
+ BackupDataWriter dataStream(dataStreamFD);
+
+ err = back_up_files(oldSnapshotFD, &dataStream, newSnapshotFD, files_after, keys_after, 6);
+ if (err != 0) {
+ return err;
+ }
+}
+
+ close(oldSnapshotFD);
+ close(dataStreamFD);
+ close(newSnapshotFD);
+
+ return 0;
+}
+
+int
+backup_helper_test_null_base()
+{
+ int err;
+ int oldSnapshotFD;
+ int dataStreamFD;
+ int newSnapshotFD;
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+ mkdir(SCRATCH_DIR "data", 0777);
+
+ write_text_file(SCRATCH_DIR "data/a", "a\naa\n");
+
+ char const* files[] = {
+ SCRATCH_DIR "data/a",
+ };
+
+ char const* keys[] = {
+ "a",
+ };
+
+ dataStreamFD = creat(SCRATCH_DIR "null_base.data", 0666);
+ if (dataStreamFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ newSnapshotFD = creat(SCRATCH_DIR "null_base.snap", 0666);
+ if (newSnapshotFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ {
+ BackupDataWriter dataStream(dataStreamFD);
+
+ err = back_up_files(-1, &dataStream, newSnapshotFD, files, keys, 1);
+ if (err != 0) {
+ return err;
+ }
+ }
+
+ close(dataStreamFD);
+ close(newSnapshotFD);
+
+ return 0;
+}
+
+int
+backup_helper_test_missing_file()
+{
+ int err;
+ int oldSnapshotFD;
+ int dataStreamFD;
+ int newSnapshotFD;
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+ mkdir(SCRATCH_DIR "data", 0777);
+
+ write_text_file(SCRATCH_DIR "data/b", "b\nbb\n");
+
+ char const* files[] = {
+ SCRATCH_DIR "data/a",
+ SCRATCH_DIR "data/b",
+ SCRATCH_DIR "data/c",
+ };
+
+ char const* keys[] = {
+ "a",
+ "b",
+ "c",
+ };
+
+ dataStreamFD = creat(SCRATCH_DIR "null_base.data", 0666);
+ if (dataStreamFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ newSnapshotFD = creat(SCRATCH_DIR "null_base.snap", 0666);
+ if (newSnapshotFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ {
+ BackupDataWriter dataStream(dataStreamFD);
+
+ err = back_up_files(-1, &dataStream, newSnapshotFD, files, keys, 1);
+ if (err != 0) {
+ return err;
+ }
+ }
+
+ close(dataStreamFD);
+ close(newSnapshotFD);
+
+ return 0;
+}
+
+
+#endif // TEST_BACKUP_HELPERS
+
+}
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
new file mode 100644
index 0000000..0f54edb
--- /dev/null
+++ b/libs/androidfw/CursorWindow.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2006-2007 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "CursorWindow"
+
+#include <androidfw/CursorWindow.h>
+#include <binder/Parcel.h>
+#include <utils/Log.h>
+
+#include <cutils/ashmem.h>
+#include <sys/mman.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+namespace android {
+
+CursorWindow::CursorWindow(const String8& name, int ashmemFd,
+ void* data, size_t size, bool readOnly) :
+ mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size), mReadOnly(readOnly) {
+ mHeader = static_cast<Header*>(mData);
+}
+
+CursorWindow::~CursorWindow() {
+ ::munmap(mData, mSize);
+ ::close(mAshmemFd);
+}
+
+status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** outCursorWindow) {
+ String8 ashmemName("CursorWindow: ");
+ ashmemName.append(name);
+
+ status_t result;
+ int ashmemFd = ashmem_create_region(ashmemName.string(), size);
+ if (ashmemFd < 0) {
+ result = -errno;
+ } else {
+ result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE);
+ if (result >= 0) {
+ void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
+ if (data == MAP_FAILED) {
+ result = -errno;
+ } else {
+ result = ashmem_set_prot_region(ashmemFd, PROT_READ);
+ if (result >= 0) {
+ CursorWindow* window = new CursorWindow(name, ashmemFd,
+ data, size, false /*readOnly*/);
+ result = window->clear();
+ if (!result) {
+ LOG_WINDOW("Created new CursorWindow: freeOffset=%d, "
+ "numRows=%d, numColumns=%d, mSize=%d, mData=%p",
+ window->mHeader->freeOffset,
+ window->mHeader->numRows,
+ window->mHeader->numColumns,
+ window->mSize, window->mData);
+ *outCursorWindow = window;
+ return OK;
+ }
+ delete window;
+ }
+ }
+ ::munmap(data, size);
+ }
+ ::close(ashmemFd);
+ }
+ *outCursorWindow = NULL;
+ return result;
+}
+
+status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow) {
+ String8 name = parcel->readString8();
+
+ status_t result;
+ int ashmemFd = parcel->readFileDescriptor();
+ if (ashmemFd == int(BAD_TYPE)) {
+ result = BAD_TYPE;
+ } else {
+ ssize_t size = ashmem_get_size_region(ashmemFd);
+ if (size < 0) {
+ result = UNKNOWN_ERROR;
+ } else {
+ int dupAshmemFd = ::dup(ashmemFd);
+ if (dupAshmemFd < 0) {
+ result = -errno;
+ } else {
+ void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0);
+ if (data == MAP_FAILED) {
+ result = -errno;
+ } else {
+ CursorWindow* window = new CursorWindow(name, dupAshmemFd,
+ data, size, true /*readOnly*/);
+ LOG_WINDOW("Created CursorWindow from parcel: freeOffset=%d, "
+ "numRows=%d, numColumns=%d, mSize=%d, mData=%p",
+ window->mHeader->freeOffset,
+ window->mHeader->numRows,
+ window->mHeader->numColumns,
+ window->mSize, window->mData);
+ *outCursorWindow = window;
+ return OK;
+ }
+ ::close(dupAshmemFd);
+ }
+ }
+ }
+ *outCursorWindow = NULL;
+ return result;
+}
+
+status_t CursorWindow::writeToParcel(Parcel* parcel) {
+ status_t status = parcel->writeString8(mName);
+ if (!status) {
+ status = parcel->writeDupFileDescriptor(mAshmemFd);
+ }
+ return status;
+}
+
+status_t CursorWindow::clear() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ mHeader->freeOffset = sizeof(Header) + sizeof(RowSlotChunk);
+ mHeader->firstChunkOffset = sizeof(Header);
+ mHeader->numRows = 0;
+ mHeader->numColumns = 0;
+
+ RowSlotChunk* firstChunk = static_cast<RowSlotChunk*>(offsetToPtr(mHeader->firstChunkOffset));
+ firstChunk->nextChunkOffset = 0;
+ return OK;
+}
+
+status_t CursorWindow::setNumColumns(uint32_t numColumns) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ uint32_t cur = mHeader->numColumns;
+ if ((cur > 0 || mHeader->numRows > 0) && cur != numColumns) {
+ ALOGE("Trying to go from %d columns to %d", cur, numColumns);
+ return INVALID_OPERATION;
+ }
+ mHeader->numColumns = numColumns;
+ return OK;
+}
+
+status_t CursorWindow::allocRow() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ // Fill in the row slot
+ RowSlot* rowSlot = allocRowSlot();
+ if (rowSlot == NULL) {
+ return NO_MEMORY;
+ }
+
+ // Allocate the slots for the field directory
+ size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot);
+ uint32_t fieldDirOffset = alloc(fieldDirSize, true /*aligned*/);
+ if (!fieldDirOffset) {
+ mHeader->numRows--;
+ LOG_WINDOW("The row failed, so back out the new row accounting "
+ "from allocRowSlot %d", mHeader->numRows);
+ return NO_MEMORY;
+ }
+ FieldSlot* fieldDir = static_cast<FieldSlot*>(offsetToPtr(fieldDirOffset));
+ memset(fieldDir, 0, fieldDirSize);
+
+ LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at offset %u\n",
+ mHeader->numRows - 1, offsetFromPtr(rowSlot), fieldDirSize, fieldDirOffset);
+ rowSlot->offset = fieldDirOffset;
+ return OK;
+}
+
+status_t CursorWindow::freeLastRow() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ if (mHeader->numRows > 0) {
+ mHeader->numRows--;
+ }
+ return OK;
+}
+
+uint32_t CursorWindow::alloc(size_t size, bool aligned) {
+ uint32_t padding;
+ if (aligned) {
+ // 4 byte alignment
+ padding = (~mHeader->freeOffset + 1) & 3;
+ } else {
+ padding = 0;
+ }
+
+ uint32_t offset = mHeader->freeOffset + padding;
+ uint32_t nextFreeOffset = offset + size;
+ if (nextFreeOffset > mSize) {
+ ALOGW("Window is full: requested allocation %d bytes, "
+ "free space %d bytes, window size %d bytes",
+ size, freeSpace(), mSize);
+ return 0;
+ }
+
+ mHeader->freeOffset = nextFreeOffset;
+ return offset;
+}
+
+CursorWindow::RowSlot* CursorWindow::getRowSlot(uint32_t row) {
+ uint32_t chunkPos = row;
+ RowSlotChunk* chunk = static_cast<RowSlotChunk*>(
+ offsetToPtr(mHeader->firstChunkOffset));
+ while (chunkPos >= ROW_SLOT_CHUNK_NUM_ROWS) {
+ chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
+ chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS;
+ }
+ return &chunk->slots[chunkPos];
+}
+
+CursorWindow::RowSlot* CursorWindow::allocRowSlot() {
+ uint32_t chunkPos = mHeader->numRows;
+ RowSlotChunk* chunk = static_cast<RowSlotChunk*>(
+ offsetToPtr(mHeader->firstChunkOffset));
+ while (chunkPos > ROW_SLOT_CHUNK_NUM_ROWS) {
+ chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
+ chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS;
+ }
+ if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) {
+ if (!chunk->nextChunkOffset) {
+ chunk->nextChunkOffset = alloc(sizeof(RowSlotChunk), true /*aligned*/);
+ if (!chunk->nextChunkOffset) {
+ return NULL;
+ }
+ }
+ chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
+ chunk->nextChunkOffset = 0;
+ chunkPos = 0;
+ }
+ mHeader->numRows += 1;
+ return &chunk->slots[chunkPos];
+}
+
+CursorWindow::FieldSlot* CursorWindow::getFieldSlot(uint32_t row, uint32_t column) {
+ if (row >= mHeader->numRows || column >= mHeader->numColumns) {
+ ALOGE("Failed to read row %d, column %d from a CursorWindow which "
+ "has %d rows, %d columns.",
+ row, column, mHeader->numRows, mHeader->numColumns);
+ return NULL;
+ }
+ RowSlot* rowSlot = getRowSlot(row);
+ if (!rowSlot) {
+ ALOGE("Failed to find rowSlot for row %d.", row);
+ return NULL;
+ }
+ FieldSlot* fieldDir = static_cast<FieldSlot*>(offsetToPtr(rowSlot->offset));
+ return &fieldDir[column];
+}
+
+status_t CursorWindow::putBlob(uint32_t row, uint32_t column, const void* value, size_t size) {
+ return putBlobOrString(row, column, value, size, FIELD_TYPE_BLOB);
+}
+
+status_t CursorWindow::putString(uint32_t row, uint32_t column, const char* value,
+ size_t sizeIncludingNull) {
+ return putBlobOrString(row, column, value, sizeIncludingNull, FIELD_TYPE_STRING);
+}
+
+status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column,
+ const void* value, size_t size, int32_t type) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ uint32_t offset = alloc(size);
+ if (!offset) {
+ return NO_MEMORY;
+ }
+
+ memcpy(offsetToPtr(offset), value, size);
+
+ fieldSlot->type = type;
+ fieldSlot->data.buffer.offset = offset;
+ fieldSlot->data.buffer.size = size;
+ return OK;
+}
+
+status_t CursorWindow::putLong(uint32_t row, uint32_t column, int64_t value) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ fieldSlot->type = FIELD_TYPE_INTEGER;
+ fieldSlot->data.l = value;
+ return OK;
+}
+
+status_t CursorWindow::putDouble(uint32_t row, uint32_t column, double value) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ fieldSlot->type = FIELD_TYPE_FLOAT;
+ fieldSlot->data.d = value;
+ return OK;
+}
+
+status_t CursorWindow::putNull(uint32_t row, uint32_t column) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ fieldSlot->type = FIELD_TYPE_NULL;
+ fieldSlot->data.buffer.offset = 0;
+ fieldSlot->data.buffer.size = 0;
+ return OK;
+}
+
+}; // namespace android
diff --git a/libs/androidfw/MODULE_LICENSE_APACHE2 b/libs/androidfw/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libs/androidfw/MODULE_LICENSE_APACHE2
diff --git a/libs/androidfw/NOTICE b/libs/androidfw/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/libs/androidfw/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/libs/androidfw/ObbFile.cpp b/libs/androidfw/ObbFile.cpp
new file mode 100644
index 0000000..ec59f06
--- /dev/null
+++ b/libs/androidfw/ObbFile.cpp
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define LOG_TAG "ObbFile"
+
+#include <androidfw/ObbFile.h>
+#include <utils/Compat.h>
+#include <utils/Log.h>
+
+//#define DEBUG 1
+
+#define kFooterTagSize 8 /* last two 32-bit integers */
+
+#define kFooterMinSize 33 /* 32-bit signature version (4 bytes)
+ * 32-bit package version (4 bytes)
+ * 32-bit flags (4 bytes)
+ * 64-bit salt (8 bytes)
+ * 32-bit package name size (4 bytes)
+ * >=1-character package name (1 byte)
+ * 32-bit footer size (4 bytes)
+ * 32-bit footer marker (4 bytes)
+ */
+
+#define kMaxBufSize 32768 /* Maximum file read buffer */
+
+#define kSignature 0x01059983U /* ObbFile signature */
+
+#define kSigVersion 1 /* We only know about signature version 1 */
+
+/* offsets in version 1 of the header */
+#define kPackageVersionOffset 4
+#define kFlagsOffset 8
+#define kSaltOffset 12
+#define kPackageNameLenOffset 20
+#define kPackageNameOffset 24
+
+/*
+ * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
+ * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
+ * not already defined, then define it here.
+ */
+#ifndef TEMP_FAILURE_RETRY
+/* Used to retry syscalls that can return EINTR. */
+#define TEMP_FAILURE_RETRY(exp) ({ \
+ typeof (exp) _rc; \
+ do { \
+ _rc = (exp); \
+ } while (_rc == -1 && errno == EINTR); \
+ _rc; })
+#endif
+
+
+namespace android {
+
+ObbFile::ObbFile()
+ : mPackageName("")
+ , mVersion(-1)
+ , mFlags(0)
+{
+ memset(mSalt, 0, sizeof(mSalt));
+}
+
+ObbFile::~ObbFile() {
+}
+
+bool ObbFile::readFrom(const char* filename)
+{
+ int fd;
+ bool success = false;
+
+ fd = ::open(filename, O_RDONLY);
+ if (fd < 0) {
+ ALOGW("couldn't open file %s: %s", filename, strerror(errno));
+ goto out;
+ }
+ success = readFrom(fd);
+ close(fd);
+
+ if (!success) {
+ ALOGW("failed to read from %s (fd=%d)\n", filename, fd);
+ }
+
+out:
+ return success;
+}
+
+bool ObbFile::readFrom(int fd)
+{
+ if (fd < 0) {
+ ALOGW("attempt to read from invalid fd\n");
+ return false;
+ }
+
+ return parseObbFile(fd);
+}
+
+bool ObbFile::parseObbFile(int fd)
+{
+ off64_t fileLength = lseek64(fd, 0, SEEK_END);
+
+ if (fileLength < kFooterMinSize) {
+ if (fileLength < 0) {
+ ALOGW("error seeking in ObbFile: %s\n", strerror(errno));
+ } else {
+ ALOGW("file is only %lld (less than %d minimum)\n", fileLength, kFooterMinSize);
+ }
+ return false;
+ }
+
+ ssize_t actual;
+ size_t footerSize;
+
+ {
+ lseek64(fd, fileLength - kFooterTagSize, SEEK_SET);
+
+ char footer[kFooterTagSize];
+ actual = TEMP_FAILURE_RETRY(read(fd, footer, kFooterTagSize));
+ if (actual != kFooterTagSize) {
+ ALOGW("couldn't read footer signature: %s\n", strerror(errno));
+ return false;
+ }
+
+ unsigned int fileSig = get4LE((unsigned char*)footer + sizeof(int32_t));
+ if (fileSig != kSignature) {
+ ALOGW("footer didn't match magic string (expected 0x%08x; got 0x%08x)\n",
+ kSignature, fileSig);
+ return false;
+ }
+
+ footerSize = get4LE((unsigned char*)footer);
+ if (footerSize > (size_t)fileLength - kFooterTagSize
+ || footerSize > kMaxBufSize) {
+ ALOGW("claimed footer size is too large (0x%08zx; file size is 0x%08llx)\n",
+ footerSize, fileLength);
+ return false;
+ }
+
+ if (footerSize < (kFooterMinSize - kFooterTagSize)) {
+ ALOGW("claimed footer size is too small (0x%zx; minimum size is 0x%x)\n",
+ footerSize, kFooterMinSize - kFooterTagSize);
+ return false;
+ }
+ }
+
+ off64_t fileOffset = fileLength - footerSize - kFooterTagSize;
+ if (lseek64(fd, fileOffset, SEEK_SET) != fileOffset) {
+ ALOGW("seek %lld failed: %s\n", fileOffset, strerror(errno));
+ return false;
+ }
+
+ mFooterStart = fileOffset;
+
+ char* scanBuf = (char*)malloc(footerSize);
+ if (scanBuf == NULL) {
+ ALOGW("couldn't allocate scanBuf: %s\n", strerror(errno));
+ return false;
+ }
+
+ actual = TEMP_FAILURE_RETRY(read(fd, scanBuf, footerSize));
+ // readAmount is guaranteed to be less than kMaxBufSize
+ if (actual != (ssize_t)footerSize) {
+ ALOGI("couldn't read ObbFile footer: %s\n", strerror(errno));
+ free(scanBuf);
+ return false;
+ }
+
+#ifdef DEBUG
+ for (int i = 0; i < footerSize; ++i) {
+ ALOGI("char: 0x%02x\n", scanBuf[i]);
+ }
+#endif
+
+ uint32_t sigVersion = get4LE((unsigned char*)scanBuf);
+ if (sigVersion != kSigVersion) {
+ ALOGW("Unsupported ObbFile version %d\n", sigVersion);
+ free(scanBuf);
+ return false;
+ }
+
+ mVersion = (int32_t) get4LE((unsigned char*)scanBuf + kPackageVersionOffset);
+ mFlags = (int32_t) get4LE((unsigned char*)scanBuf + kFlagsOffset);
+
+ memcpy(&mSalt, (unsigned char*)scanBuf + kSaltOffset, sizeof(mSalt));
+
+ size_t packageNameLen = get4LE((unsigned char*)scanBuf + kPackageNameLenOffset);
+ if (packageNameLen == 0
+ || packageNameLen > (footerSize - kPackageNameOffset)) {
+ ALOGW("bad ObbFile package name length (0x%04zx; 0x%04zx possible)\n",
+ packageNameLen, footerSize - kPackageNameOffset);
+ free(scanBuf);
+ return false;
+ }
+
+ char* packageName = reinterpret_cast<char*>(scanBuf + kPackageNameOffset);
+ mPackageName = String8(const_cast<char*>(packageName), packageNameLen);
+
+ free(scanBuf);
+
+#ifdef DEBUG
+ ALOGI("Obb scan succeeded: packageName=%s, version=%d\n", mPackageName.string(), mVersion);
+#endif
+
+ return true;
+}
+
+bool ObbFile::writeTo(const char* filename)
+{
+ int fd;
+ bool success = false;
+
+ fd = ::open(filename, O_WRONLY);
+ if (fd < 0) {
+ goto out;
+ }
+ success = writeTo(fd);
+ close(fd);
+
+out:
+ if (!success) {
+ ALOGW("failed to write to %s: %s\n", filename, strerror(errno));
+ }
+ return success;
+}
+
+bool ObbFile::writeTo(int fd)
+{
+ if (fd < 0) {
+ return false;
+ }
+
+ lseek64(fd, 0, SEEK_END);
+
+ if (mPackageName.size() == 0 || mVersion == -1) {
+ ALOGW("tried to write uninitialized ObbFile data\n");
+ return false;
+ }
+
+ unsigned char intBuf[sizeof(uint32_t)+1];
+ memset(&intBuf, 0, sizeof(intBuf));
+
+ put4LE(intBuf, kSigVersion);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write signature version: %s\n", strerror(errno));
+ return false;
+ }
+
+ put4LE(intBuf, mVersion);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write package version\n");
+ return false;
+ }
+
+ put4LE(intBuf, mFlags);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write package version\n");
+ return false;
+ }
+
+ if (write(fd, mSalt, sizeof(mSalt)) != (ssize_t)sizeof(mSalt)) {
+ ALOGW("couldn't write salt: %s\n", strerror(errno));
+ return false;
+ }
+
+ size_t packageNameLen = mPackageName.size();
+ put4LE(intBuf, packageNameLen);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write package name length: %s\n", strerror(errno));
+ return false;
+ }
+
+ if (write(fd, mPackageName.string(), packageNameLen) != (ssize_t)packageNameLen) {
+ ALOGW("couldn't write package name: %s\n", strerror(errno));
+ return false;
+ }
+
+ put4LE(intBuf, kPackageNameOffset + packageNameLen);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write footer size: %s\n", strerror(errno));
+ return false;
+ }
+
+ put4LE(intBuf, kSignature);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write footer magic signature: %s\n", strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+bool ObbFile::removeFrom(const char* filename)
+{
+ int fd;
+ bool success = false;
+
+ fd = ::open(filename, O_RDWR);
+ if (fd < 0) {
+ goto out;
+ }
+ success = removeFrom(fd);
+ close(fd);
+
+out:
+ if (!success) {
+ ALOGW("failed to remove signature from %s: %s\n", filename, strerror(errno));
+ }
+ return success;
+}
+
+bool ObbFile::removeFrom(int fd)
+{
+ if (fd < 0) {
+ return false;
+ }
+
+ if (!readFrom(fd)) {
+ return false;
+ }
+
+ ftruncate(fd, mFooterStart);
+
+ return true;
+}
+
+}
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
new file mode 100644
index 0000000..1cc3563
--- /dev/null
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -0,0 +1,5796 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "ResourceType"
+//#define LOG_NDEBUG 0
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/Atomic.h>
+#include <utils/ByteOrder.h>
+#include <utils/Debug.h>
+#include <utils/Log.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <memory.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#ifndef INT32_MAX
+#define INT32_MAX ((int32_t)(2147483647))
+#endif
+
+#define STRING_POOL_NOISY(x) //x
+#define XML_NOISY(x) //x
+#define TABLE_NOISY(x) //x
+#define TABLE_GETENTRY(x) //x
+#define TABLE_SUPER_NOISY(x) //x
+#define LOAD_TABLE_NOISY(x) //x
+#define TABLE_THEME(x) //x
+
+namespace android {
+
+#ifdef HAVE_WINSOCK
+#undef nhtol
+#undef htonl
+
+#ifdef HAVE_LITTLE_ENDIAN
+#define ntohl(x) ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) )
+#define htonl(x) ntohl(x)
+#define ntohs(x) ( (((x) << 8) & 0xff00) | (((x) >> 8) & 255) )
+#define htons(x) ntohs(x)
+#else
+#define ntohl(x) (x)
+#define htonl(x) (x)
+#define ntohs(x) (x)
+#define htons(x) (x)
+#endif
+#endif
+
+#define IDMAP_MAGIC 0x706d6469
+// size measured in sizeof(uint32_t)
+#define IDMAP_HEADER_SIZE (ResTable::IDMAP_HEADER_SIZE_BYTES / sizeof(uint32_t))
+
+static void printToLogFunc(void* cookie, const char* txt)
+{
+ ALOGV("%s", txt);
+}
+
+// Standard C isspace() is only required to look at the low byte of its input, so
+// produces incorrect results for UTF-16 characters. For safety's sake, assume that
+// any high-byte UTF-16 code point is not whitespace.
+inline int isspace16(char16_t c) {
+ return (c < 0x0080 && isspace(c));
+}
+
+// range checked; guaranteed to NUL-terminate within the stated number of available slots
+// NOTE: if this truncates the dst string due to running out of space, no attempt is
+// made to avoid splitting surrogate pairs.
+static void strcpy16_dtoh(uint16_t* dst, const uint16_t* src, size_t avail)
+{
+ uint16_t* last = dst + avail - 1;
+ while (*src && (dst < last)) {
+ char16_t s = dtohs(*src);
+ *dst++ = s;
+ src++;
+ }
+ *dst = 0;
+}
+
+static status_t validate_chunk(const ResChunk_header* chunk,
+ size_t minSize,
+ const uint8_t* dataEnd,
+ const char* name)
+{
+ const uint16_t headerSize = dtohs(chunk->headerSize);
+ const uint32_t size = dtohl(chunk->size);
+
+ if (headerSize >= minSize) {
+ if (headerSize <= size) {
+ if (((headerSize|size)&0x3) == 0) {
+ if ((ssize_t)size <= (dataEnd-((const uint8_t*)chunk))) {
+ return NO_ERROR;
+ }
+ ALOGW("%s data size %p extends beyond resource end %p.",
+ name, (void*)size,
+ (void*)(dataEnd-((const uint8_t*)chunk)));
+ return BAD_TYPE;
+ }
+ ALOGW("%s size 0x%x or headerSize 0x%x is not on an integer boundary.",
+ name, (int)size, (int)headerSize);
+ return BAD_TYPE;
+ }
+ ALOGW("%s size %p is smaller than header size %p.",
+ name, (void*)size, (void*)(int)headerSize);
+ return BAD_TYPE;
+ }
+ ALOGW("%s header size %p is too small.",
+ name, (void*)(int)headerSize);
+ return BAD_TYPE;
+}
+
+inline void Res_value::copyFrom_dtoh(const Res_value& src)
+{
+ size = dtohs(src.size);
+ res0 = src.res0;
+ dataType = src.dataType;
+ data = dtohl(src.data);
+}
+
+void Res_png_9patch::deviceToFile()
+{
+ for (int i = 0; i < numXDivs; i++) {
+ xDivs[i] = htonl(xDivs[i]);
+ }
+ for (int i = 0; i < numYDivs; i++) {
+ yDivs[i] = htonl(yDivs[i]);
+ }
+ paddingLeft = htonl(paddingLeft);
+ paddingRight = htonl(paddingRight);
+ paddingTop = htonl(paddingTop);
+ paddingBottom = htonl(paddingBottom);
+ for (int i=0; i<numColors; i++) {
+ colors[i] = htonl(colors[i]);
+ }
+}
+
+void Res_png_9patch::fileToDevice()
+{
+ for (int i = 0; i < numXDivs; i++) {
+ xDivs[i] = ntohl(xDivs[i]);
+ }
+ for (int i = 0; i < numYDivs; i++) {
+ yDivs[i] = ntohl(yDivs[i]);
+ }
+ paddingLeft = ntohl(paddingLeft);
+ paddingRight = ntohl(paddingRight);
+ paddingTop = ntohl(paddingTop);
+ paddingBottom = ntohl(paddingBottom);
+ for (int i=0; i<numColors; i++) {
+ colors[i] = ntohl(colors[i]);
+ }
+}
+
+size_t Res_png_9patch::serializedSize()
+{
+ // The size of this struct is 32 bytes on the 32-bit target system
+ // 4 * int8_t
+ // 4 * int32_t
+ // 3 * pointer
+ return 32
+ + numXDivs * sizeof(int32_t)
+ + numYDivs * sizeof(int32_t)
+ + numColors * sizeof(uint32_t);
+}
+
+void* Res_png_9patch::serialize()
+{
+ // Use calloc since we're going to leave a few holes in the data
+ // and want this to run cleanly under valgrind
+ void* newData = calloc(1, serializedSize());
+ serialize(newData);
+ return newData;
+}
+
+void Res_png_9patch::serialize(void * outData)
+{
+ char* data = (char*) outData;
+ memmove(data, &wasDeserialized, 4); // copy wasDeserialized, numXDivs, numYDivs, numColors
+ memmove(data + 12, &paddingLeft, 16); // copy paddingXXXX
+ data += 32;
+
+ memmove(data, this->xDivs, numXDivs * sizeof(int32_t));
+ data += numXDivs * sizeof(int32_t);
+ memmove(data, this->yDivs, numYDivs * sizeof(int32_t));
+ data += numYDivs * sizeof(int32_t);
+ memmove(data, this->colors, numColors * sizeof(uint32_t));
+}
+
+static void deserializeInternal(const void* inData, Res_png_9patch* outData) {
+ char* patch = (char*) inData;
+ if (inData != outData) {
+ memmove(&outData->wasDeserialized, patch, 4); // copy wasDeserialized, numXDivs, numYDivs, numColors
+ memmove(&outData->paddingLeft, patch + 12, 4); // copy wasDeserialized, numXDivs, numYDivs, numColors
+ }
+ outData->wasDeserialized = true;
+ char* data = (char*)outData;
+ data += sizeof(Res_png_9patch);
+ outData->xDivs = (int32_t*) data;
+ data += outData->numXDivs * sizeof(int32_t);
+ outData->yDivs = (int32_t*) data;
+ data += outData->numYDivs * sizeof(int32_t);
+ outData->colors = (uint32_t*) data;
+}
+
+static bool assertIdmapHeader(const uint32_t* map, size_t sizeBytes)
+{
+ if (sizeBytes < ResTable::IDMAP_HEADER_SIZE_BYTES) {
+ ALOGW("idmap assertion failed: size=%d bytes\n", (int)sizeBytes);
+ return false;
+ }
+ if (*map != htodl(IDMAP_MAGIC)) { // htodl: map data expected to be in correct endianess
+ ALOGW("idmap assertion failed: invalid magic found (is 0x%08x, expected 0x%08x)\n",
+ *map, htodl(IDMAP_MAGIC));
+ return false;
+ }
+ return true;
+}
+
+static status_t idmapLookup(const uint32_t* map, size_t sizeBytes, uint32_t key, uint32_t* outValue)
+{
+ // see README for details on the format of map
+ if (!assertIdmapHeader(map, sizeBytes)) {
+ return UNKNOWN_ERROR;
+ }
+ map = map + IDMAP_HEADER_SIZE; // skip ahead to data segment
+ // size of data block, in uint32_t
+ const size_t size = (sizeBytes - ResTable::IDMAP_HEADER_SIZE_BYTES) / sizeof(uint32_t);
+ const uint32_t type = Res_GETTYPE(key) + 1; // add one, idmap stores "public" type id
+ const uint32_t entry = Res_GETENTRY(key);
+ const uint32_t typeCount = *map;
+
+ if (type > typeCount) {
+ ALOGW("Resource ID map: type=%d exceeds number of types=%d\n", type, typeCount);
+ return UNKNOWN_ERROR;
+ }
+ if (typeCount > size) {
+ ALOGW("Resource ID map: number of types=%d exceeds size of map=%d\n", typeCount, (int)size);
+ return UNKNOWN_ERROR;
+ }
+ const uint32_t typeOffset = map[type];
+ if (typeOffset == 0) {
+ *outValue = 0;
+ return NO_ERROR;
+ }
+ if (typeOffset + 1 > size) {
+ ALOGW("Resource ID map: type offset=%d exceeds reasonable value, size of map=%d\n",
+ typeOffset, (int)size);
+ return UNKNOWN_ERROR;
+ }
+ const uint32_t entryCount = map[typeOffset];
+ const uint32_t entryOffset = map[typeOffset + 1];
+ if (entryCount == 0 || entry < entryOffset || entry - entryOffset > entryCount - 1) {
+ *outValue = 0;
+ return NO_ERROR;
+ }
+ const uint32_t index = typeOffset + 2 + entry - entryOffset;
+ if (index > size) {
+ ALOGW("Resource ID map: entry index=%d exceeds size of map=%d\n", index, (int)size);
+ *outValue = 0;
+ return NO_ERROR;
+ }
+ *outValue = map[index];
+
+ return NO_ERROR;
+}
+
+static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t *outId)
+{
+ if (!assertIdmapHeader(map, mapSize)) {
+ return UNKNOWN_ERROR;
+ }
+ const uint32_t* p = map + IDMAP_HEADER_SIZE + 1;
+ while (*p == 0) {
+ ++p;
+ }
+ *outId = (map[*p + IDMAP_HEADER_SIZE + 2] >> 24) & 0x000000ff;
+ return NO_ERROR;
+}
+
+Res_png_9patch* Res_png_9patch::deserialize(const void* inData)
+{
+ if (sizeof(void*) != sizeof(int32_t)) {
+ ALOGE("Cannot deserialize on non 32-bit system\n");
+ return NULL;
+ }
+ deserializeInternal(inData, (Res_png_9patch*) inData);
+ return (Res_png_9patch*) inData;
+}
+
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+
+ResStringPool::ResStringPool()
+ : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
+{
+}
+
+ResStringPool::ResStringPool(const void* data, size_t size, bool copyData)
+ : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
+{
+ setTo(data, size, copyData);
+}
+
+ResStringPool::~ResStringPool()
+{
+ uninit();
+}
+
+status_t ResStringPool::setTo(const void* data, size_t size, bool copyData)
+{
+ if (!data || !size) {
+ return (mError=BAD_TYPE);
+ }
+
+ uninit();
+
+ const bool notDeviceEndian = htods(0xf0) != 0xf0;
+
+ if (copyData || notDeviceEndian) {
+ mOwnedData = malloc(size);
+ if (mOwnedData == NULL) {
+ return (mError=NO_MEMORY);
+ }
+ memcpy(mOwnedData, data, size);
+ data = mOwnedData;
+ }
+
+ mHeader = (const ResStringPool_header*)data;
+
+ if (notDeviceEndian) {
+ ResStringPool_header* h = const_cast<ResStringPool_header*>(mHeader);
+ h->header.headerSize = dtohs(mHeader->header.headerSize);
+ h->header.type = dtohs(mHeader->header.type);
+ h->header.size = dtohl(mHeader->header.size);
+ h->stringCount = dtohl(mHeader->stringCount);
+ h->styleCount = dtohl(mHeader->styleCount);
+ h->flags = dtohl(mHeader->flags);
+ h->stringsStart = dtohl(mHeader->stringsStart);
+ h->stylesStart = dtohl(mHeader->stylesStart);
+ }
+
+ if (mHeader->header.headerSize > mHeader->header.size
+ || mHeader->header.size > size) {
+ ALOGW("Bad string block: header size %d or total size %d is larger than data size %d\n",
+ (int)mHeader->header.headerSize, (int)mHeader->header.size, (int)size);
+ return (mError=BAD_TYPE);
+ }
+ mSize = mHeader->header.size;
+ mEntries = (const uint32_t*)
+ (((const uint8_t*)data)+mHeader->header.headerSize);
+
+ if (mHeader->stringCount > 0) {
+ if ((mHeader->stringCount*sizeof(uint32_t) < mHeader->stringCount) // uint32 overflow?
+ || (mHeader->header.headerSize+(mHeader->stringCount*sizeof(uint32_t)))
+ > size) {
+ ALOGW("Bad string block: entry of %d items extends past data size %d\n",
+ (int)(mHeader->header.headerSize+(mHeader->stringCount*sizeof(uint32_t))),
+ (int)size);
+ return (mError=BAD_TYPE);
+ }
+
+ size_t charSize;
+ if (mHeader->flags&ResStringPool_header::UTF8_FLAG) {
+ charSize = sizeof(uint8_t);
+ } else {
+ charSize = sizeof(char16_t);
+ }
+
+ mStrings = (const void*)
+ (((const uint8_t*)data)+mHeader->stringsStart);
+ if (mHeader->stringsStart >= (mHeader->header.size-sizeof(uint16_t))) {
+ ALOGW("Bad string block: string pool starts at %d, after total size %d\n",
+ (int)mHeader->stringsStart, (int)mHeader->header.size);
+ return (mError=BAD_TYPE);
+ }
+ if (mHeader->styleCount == 0) {
+ mStringPoolSize =
+ (mHeader->header.size-mHeader->stringsStart)/charSize;
+ } else {
+ // check invariant: styles starts before end of data
+ if (mHeader->stylesStart >= (mHeader->header.size-sizeof(uint16_t))) {
+ ALOGW("Bad style block: style block starts at %d past data size of %d\n",
+ (int)mHeader->stylesStart, (int)mHeader->header.size);
+ return (mError=BAD_TYPE);
+ }
+ // check invariant: styles follow the strings
+ if (mHeader->stylesStart <= mHeader->stringsStart) {
+ ALOGW("Bad style block: style block starts at %d, before strings at %d\n",
+ (int)mHeader->stylesStart, (int)mHeader->stringsStart);
+ return (mError=BAD_TYPE);
+ }
+ mStringPoolSize =
+ (mHeader->stylesStart-mHeader->stringsStart)/charSize;
+ }
+
+ // check invariant: stringCount > 0 requires a string pool to exist
+ if (mStringPoolSize == 0) {
+ ALOGW("Bad string block: stringCount is %d but pool size is 0\n", (int)mHeader->stringCount);
+ return (mError=BAD_TYPE);
+ }
+
+ if (notDeviceEndian) {
+ size_t i;
+ uint32_t* e = const_cast<uint32_t*>(mEntries);
+ for (i=0; i<mHeader->stringCount; i++) {
+ e[i] = dtohl(mEntries[i]);
+ }
+ if (!(mHeader->flags&ResStringPool_header::UTF8_FLAG)) {
+ const char16_t* strings = (const char16_t*)mStrings;
+ char16_t* s = const_cast<char16_t*>(strings);
+ for (i=0; i<mStringPoolSize; i++) {
+ s[i] = dtohs(strings[i]);
+ }
+ }
+ }
+
+ if ((mHeader->flags&ResStringPool_header::UTF8_FLAG &&
+ ((uint8_t*)mStrings)[mStringPoolSize-1] != 0) ||
+ (!mHeader->flags&ResStringPool_header::UTF8_FLAG &&
+ ((char16_t*)mStrings)[mStringPoolSize-1] != 0)) {
+ ALOGW("Bad string block: last string is not 0-terminated\n");
+ return (mError=BAD_TYPE);
+ }
+ } else {
+ mStrings = NULL;
+ mStringPoolSize = 0;
+ }
+
+ if (mHeader->styleCount > 0) {
+ mEntryStyles = mEntries + mHeader->stringCount;
+ // invariant: integer overflow in calculating mEntryStyles
+ if (mEntryStyles < mEntries) {
+ ALOGW("Bad string block: integer overflow finding styles\n");
+ return (mError=BAD_TYPE);
+ }
+
+ if (((const uint8_t*)mEntryStyles-(const uint8_t*)mHeader) > (int)size) {
+ ALOGW("Bad string block: entry of %d styles extends past data size %d\n",
+ (int)((const uint8_t*)mEntryStyles-(const uint8_t*)mHeader),
+ (int)size);
+ return (mError=BAD_TYPE);
+ }
+ mStyles = (const uint32_t*)
+ (((const uint8_t*)data)+mHeader->stylesStart);
+ if (mHeader->stylesStart >= mHeader->header.size) {
+ ALOGW("Bad string block: style pool starts %d, after total size %d\n",
+ (int)mHeader->stylesStart, (int)mHeader->header.size);
+ return (mError=BAD_TYPE);
+ }
+ mStylePoolSize =
+ (mHeader->header.size-mHeader->stylesStart)/sizeof(uint32_t);
+
+ if (notDeviceEndian) {
+ size_t i;
+ uint32_t* e = const_cast<uint32_t*>(mEntryStyles);
+ for (i=0; i<mHeader->styleCount; i++) {
+ e[i] = dtohl(mEntryStyles[i]);
+ }
+ uint32_t* s = const_cast<uint32_t*>(mStyles);
+ for (i=0; i<mStylePoolSize; i++) {
+ s[i] = dtohl(mStyles[i]);
+ }
+ }
+
+ const ResStringPool_span endSpan = {
+ { htodl(ResStringPool_span::END) },
+ htodl(ResStringPool_span::END), htodl(ResStringPool_span::END)
+ };
+ if (memcmp(&mStyles[mStylePoolSize-(sizeof(endSpan)/sizeof(uint32_t))],
+ &endSpan, sizeof(endSpan)) != 0) {
+ ALOGW("Bad string block: last style is not 0xFFFFFFFF-terminated\n");
+ return (mError=BAD_TYPE);
+ }
+ } else {
+ mEntryStyles = NULL;
+ mStyles = NULL;
+ mStylePoolSize = 0;
+ }
+
+ return (mError=NO_ERROR);
+}
+
+status_t ResStringPool::getError() const
+{
+ return mError;
+}
+
+void ResStringPool::uninit()
+{
+ mError = NO_INIT;
+ if (mHeader != NULL && mCache != NULL) {
+ for (size_t x = 0; x < mHeader->stringCount; x++) {
+ if (mCache[x] != NULL) {
+ free(mCache[x]);
+ mCache[x] = NULL;
+ }
+ }
+ free(mCache);
+ mCache = NULL;
+ }
+ if (mOwnedData) {
+ free(mOwnedData);
+ mOwnedData = NULL;
+ }
+}
+
+/**
+ * Strings in UTF-16 format have length indicated by a length encoded in the
+ * stored data. It is either 1 or 2 characters of length data. This allows a
+ * maximum length of 0x7FFFFFF (2147483647 bytes), but if you're storing that
+ * much data in a string, you're abusing them.
+ *
+ * If the high bit is set, then there are two characters or 4 bytes of length
+ * data encoded. In that case, drop the high bit of the first character and
+ * add it together with the next character.
+ */
+static inline size_t
+decodeLength(const char16_t** str)
+{
+ size_t len = **str;
+ if ((len & 0x8000) != 0) {
+ (*str)++;
+ len = ((len & 0x7FFF) << 16) | **str;
+ }
+ (*str)++;
+ return len;
+}
+
+/**
+ * Strings in UTF-8 format have length indicated by a length encoded in the
+ * stored data. It is either 1 or 2 characters of length data. This allows a
+ * maximum length of 0x7FFF (32767 bytes), but you should consider storing
+ * text in another way if you're using that much data in a single string.
+ *
+ * If the high bit is set, then there are two characters or 2 bytes of length
+ * data encoded. In that case, drop the high bit of the first character and
+ * add it together with the next character.
+ */
+static inline size_t
+decodeLength(const uint8_t** str)
+{
+ size_t len = **str;
+ if ((len & 0x80) != 0) {
+ (*str)++;
+ len = ((len & 0x7F) << 8) | **str;
+ }
+ (*str)++;
+ return len;
+}
+
+const uint16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const
+{
+ if (mError == NO_ERROR && idx < mHeader->stringCount) {
+ const bool isUTF8 = (mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0;
+ const uint32_t off = mEntries[idx]/(isUTF8?sizeof(char):sizeof(char16_t));
+ if (off < (mStringPoolSize-1)) {
+ if (!isUTF8) {
+ const char16_t* strings = (char16_t*)mStrings;
+ const char16_t* str = strings+off;
+
+ *u16len = decodeLength(&str);
+ if ((uint32_t)(str+*u16len-strings) < mStringPoolSize) {
+ return str;
+ } else {
+ ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
+ (int)idx, (int)(str+*u16len-strings), (int)mStringPoolSize);
+ }
+ } else {
+ const uint8_t* strings = (uint8_t*)mStrings;
+ const uint8_t* u8str = strings+off;
+
+ *u16len = decodeLength(&u8str);
+ size_t u8len = decodeLength(&u8str);
+
+ // encLen must be less than 0x7FFF due to encoding.
+ if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) {
+ AutoMutex lock(mDecodeLock);
+
+ if (mCache == NULL) {
+#ifndef HAVE_ANDROID_OS
+ STRING_POOL_NOISY(ALOGI("CREATING STRING CACHE OF %d bytes",
+ mHeader->stringCount*sizeof(char16_t**)));
+#else
+ // We do not want to be in this case when actually running Android.
+ ALOGW("CREATING STRING CACHE OF %d bytes",
+ mHeader->stringCount*sizeof(char16_t**));
+#endif
+ mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t**));
+ if (mCache == NULL) {
+ ALOGW("No memory trying to allocate decode cache table of %d bytes\n",
+ (int)(mHeader->stringCount*sizeof(char16_t**)));
+ return NULL;
+ }
+ }
+
+ if (mCache[idx] != NULL) {
+ return mCache[idx];
+ }
+
+ ssize_t actualLen = utf8_to_utf16_length(u8str, u8len);
+ if (actualLen < 0 || (size_t)actualLen != *u16len) {
+ ALOGW("Bad string block: string #%lld decoded length is not correct "
+ "%lld vs %llu\n",
+ (long long)idx, (long long)actualLen, (long long)*u16len);
+ return NULL;
+ }
+
+ char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t));
+ if (!u16str) {
+ ALOGW("No memory when trying to allocate decode cache for string #%d\n",
+ (int)idx);
+ return NULL;
+ }
+
+ STRING_POOL_NOISY(ALOGI("Caching UTF8 string: %s", u8str));
+ utf8_to_utf16(u8str, u8len, u16str);
+ mCache[idx] = u16str;
+ return u16str;
+ } else {
+ ALOGW("Bad string block: string #%lld extends to %lld, past end at %lld\n",
+ (long long)idx, (long long)(u8str+u8len-strings),
+ (long long)mStringPoolSize);
+ }
+ }
+ } else {
+ ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
+ (int)idx, (int)(off*sizeof(uint16_t)),
+ (int)(mStringPoolSize*sizeof(uint16_t)));
+ }
+ }
+ return NULL;
+}
+
+const char* ResStringPool::string8At(size_t idx, size_t* outLen) const
+{
+ if (mError == NO_ERROR && idx < mHeader->stringCount) {
+ if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) == 0) {
+ return NULL;
+ }
+ const uint32_t off = mEntries[idx]/sizeof(char);
+ if (off < (mStringPoolSize-1)) {
+ const uint8_t* strings = (uint8_t*)mStrings;
+ const uint8_t* str = strings+off;
+ *outLen = decodeLength(&str);
+ size_t encLen = decodeLength(&str);
+ if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
+ return (const char*)str;
+ } else {
+ ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
+ (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
+ }
+ } else {
+ ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
+ (int)idx, (int)(off*sizeof(uint16_t)),
+ (int)(mStringPoolSize*sizeof(uint16_t)));
+ }
+ }
+ return NULL;
+}
+
+const String8 ResStringPool::string8ObjectAt(size_t idx) const
+{
+ size_t len;
+ const char *str = (const char*)string8At(idx, &len);
+ if (str != NULL) {
+ return String8(str);
+ }
+ return String8(stringAt(idx, &len));
+}
+
+const ResStringPool_span* ResStringPool::styleAt(const ResStringPool_ref& ref) const
+{
+ return styleAt(ref.index);
+}
+
+const ResStringPool_span* ResStringPool::styleAt(size_t idx) const
+{
+ if (mError == NO_ERROR && idx < mHeader->styleCount) {
+ const uint32_t off = (mEntryStyles[idx]/sizeof(uint32_t));
+ if (off < mStylePoolSize) {
+ return (const ResStringPool_span*)(mStyles+off);
+ } else {
+ ALOGW("Bad string block: style #%d entry is at %d, past end at %d\n",
+ (int)idx, (int)(off*sizeof(uint32_t)),
+ (int)(mStylePoolSize*sizeof(uint32_t)));
+ }
+ }
+ return NULL;
+}
+
+ssize_t ResStringPool::indexOfString(const char16_t* str, size_t strLen) const
+{
+ if (mError != NO_ERROR) {
+ return mError;
+ }
+
+ size_t len;
+
+ if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0) {
+ STRING_POOL_NOISY(ALOGI("indexOfString UTF-8: %s", String8(str, strLen).string()));
+
+ // The string pool contains UTF 8 strings; we don't want to cause
+ // temporary UTF-16 strings to be created as we search.
+ if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
+ // Do a binary search for the string... this is a little tricky,
+ // because the strings are sorted with strzcmp16(). So to match
+ // the ordering, we need to convert strings in the pool to UTF-16.
+ // But we don't want to hit the cache, so instead we will have a
+ // local temporary allocation for the conversions.
+ char16_t* convBuffer = (char16_t*)malloc(strLen+4);
+ ssize_t l = 0;
+ ssize_t h = mHeader->stringCount-1;
+
+ ssize_t mid;
+ while (l <= h) {
+ mid = l + (h - l)/2;
+ const uint8_t* s = (const uint8_t*)string8At(mid, &len);
+ int c;
+ if (s != NULL) {
+ char16_t* end = utf8_to_utf16_n(s, len, convBuffer, strLen+3);
+ *end = 0;
+ c = strzcmp16(convBuffer, end-convBuffer, str, strLen);
+ } else {
+ c = -1;
+ }
+ STRING_POOL_NOISY(ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
+ (const char*)s, c, (int)l, (int)mid, (int)h));
+ if (c == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ free(convBuffer);
+ return mid;
+ } else if (c < 0) {
+ l = mid + 1;
+ } else {
+ h = mid - 1;
+ }
+ }
+ free(convBuffer);
+ } else {
+ // It is unusual to get the ID from an unsorted string block...
+ // most often this happens because we want to get IDs for style
+ // span tags; since those always appear at the end of the string
+ // block, start searching at the back.
+ String8 str8(str, strLen);
+ const size_t str8Len = str8.size();
+ for (int i=mHeader->stringCount-1; i>=0; i--) {
+ const char* s = string8At(i, &len);
+ STRING_POOL_NOISY(ALOGI("Looking at %s, i=%d\n",
+ String8(s).string(),
+ i));
+ if (s && str8Len == len && memcmp(s, str8.string(), str8Len) == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ return i;
+ }
+ }
+ }
+
+ } else {
+ STRING_POOL_NOISY(ALOGI("indexOfString UTF-16: %s", String8(str, strLen).string()));
+
+ if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
+ // Do a binary search for the string...
+ ssize_t l = 0;
+ ssize_t h = mHeader->stringCount-1;
+
+ ssize_t mid;
+ while (l <= h) {
+ mid = l + (h - l)/2;
+ const char16_t* s = stringAt(mid, &len);
+ int c = s ? strzcmp16(s, len, str, strLen) : -1;
+ STRING_POOL_NOISY(ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
+ String8(s).string(),
+ c, (int)l, (int)mid, (int)h));
+ if (c == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ return mid;
+ } else if (c < 0) {
+ l = mid + 1;
+ } else {
+ h = mid - 1;
+ }
+ }
+ } else {
+ // It is unusual to get the ID from an unsorted string block...
+ // most often this happens because we want to get IDs for style
+ // span tags; since those always appear at the end of the string
+ // block, start searching at the back.
+ for (int i=mHeader->stringCount-1; i>=0; i--) {
+ const char16_t* s = stringAt(i, &len);
+ STRING_POOL_NOISY(ALOGI("Looking at %s, i=%d\n",
+ String8(s).string(),
+ i));
+ if (s && strLen == len && strzcmp16(s, len, str, strLen) == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ return i;
+ }
+ }
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
+size_t ResStringPool::size() const
+{
+ return (mError == NO_ERROR) ? mHeader->stringCount : 0;
+}
+
+size_t ResStringPool::styleCount() const
+{
+ return (mError == NO_ERROR) ? mHeader->styleCount : 0;
+}
+
+size_t ResStringPool::bytes() const
+{
+ return (mError == NO_ERROR) ? mHeader->header.size : 0;
+}
+
+bool ResStringPool::isSorted() const
+{
+ return (mHeader->flags&ResStringPool_header::SORTED_FLAG)!=0;
+}
+
+bool ResStringPool::isUTF8() const
+{
+ return (mHeader->flags&ResStringPool_header::UTF8_FLAG)!=0;
+}
+
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+
+ResXMLParser::ResXMLParser(const ResXMLTree& tree)
+ : mTree(tree), mEventCode(BAD_DOCUMENT)
+{
+}
+
+void ResXMLParser::restart()
+{
+ mCurNode = NULL;
+ mEventCode = mTree.mError == NO_ERROR ? START_DOCUMENT : BAD_DOCUMENT;
+}
+const ResStringPool& ResXMLParser::getStrings() const
+{
+ return mTree.mStrings;
+}
+
+ResXMLParser::event_code_t ResXMLParser::getEventType() const
+{
+ return mEventCode;
+}
+
+ResXMLParser::event_code_t ResXMLParser::next()
+{
+ if (mEventCode == START_DOCUMENT) {
+ mCurNode = mTree.mRootNode;
+ mCurExt = mTree.mRootExt;
+ return (mEventCode=mTree.mRootCode);
+ } else if (mEventCode >= FIRST_CHUNK_CODE) {
+ return nextNode();
+ }
+ return mEventCode;
+}
+
+int32_t ResXMLParser::getCommentID() const
+{
+ return mCurNode != NULL ? dtohl(mCurNode->comment.index) : -1;
+}
+
+const uint16_t* ResXMLParser::getComment(size_t* outLen) const
+{
+ int32_t id = getCommentID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+uint32_t ResXMLParser::getLineNumber() const
+{
+ return mCurNode != NULL ? dtohl(mCurNode->lineNumber) : -1;
+}
+
+int32_t ResXMLParser::getTextID() const
+{
+ if (mEventCode == TEXT) {
+ return dtohl(((const ResXMLTree_cdataExt*)mCurExt)->data.index);
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getText(size_t* outLen) const
+{
+ int32_t id = getTextID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+ssize_t ResXMLParser::getTextValue(Res_value* outValue) const
+{
+ if (mEventCode == TEXT) {
+ outValue->copyFrom_dtoh(((const ResXMLTree_cdataExt*)mCurExt)->typedData);
+ return sizeof(Res_value);
+ }
+ return BAD_TYPE;
+}
+
+int32_t ResXMLParser::getNamespacePrefixID() const
+{
+ if (mEventCode == START_NAMESPACE || mEventCode == END_NAMESPACE) {
+ return dtohl(((const ResXMLTree_namespaceExt*)mCurExt)->prefix.index);
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getNamespacePrefix(size_t* outLen) const
+{
+ int32_t id = getNamespacePrefixID();
+ //printf("prefix=%d event=%p\n", id, mEventCode);
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+int32_t ResXMLParser::getNamespaceUriID() const
+{
+ if (mEventCode == START_NAMESPACE || mEventCode == END_NAMESPACE) {
+ return dtohl(((const ResXMLTree_namespaceExt*)mCurExt)->uri.index);
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getNamespaceUri(size_t* outLen) const
+{
+ int32_t id = getNamespaceUriID();
+ //printf("uri=%d event=%p\n", id, mEventCode);
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+int32_t ResXMLParser::getElementNamespaceID() const
+{
+ if (mEventCode == START_TAG) {
+ return dtohl(((const ResXMLTree_attrExt*)mCurExt)->ns.index);
+ }
+ if (mEventCode == END_TAG) {
+ return dtohl(((const ResXMLTree_endElementExt*)mCurExt)->ns.index);
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getElementNamespace(size_t* outLen) const
+{
+ int32_t id = getElementNamespaceID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+int32_t ResXMLParser::getElementNameID() const
+{
+ if (mEventCode == START_TAG) {
+ return dtohl(((const ResXMLTree_attrExt*)mCurExt)->name.index);
+ }
+ if (mEventCode == END_TAG) {
+ return dtohl(((const ResXMLTree_endElementExt*)mCurExt)->name.index);
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getElementName(size_t* outLen) const
+{
+ int32_t id = getElementNameID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+size_t ResXMLParser::getAttributeCount() const
+{
+ if (mEventCode == START_TAG) {
+ return dtohs(((const ResXMLTree_attrExt*)mCurExt)->attributeCount);
+ }
+ return 0;
+}
+
+int32_t ResXMLParser::getAttributeNamespaceID(size_t idx) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ return dtohl(attr->ns.index);
+ }
+ }
+ return -2;
+}
+
+const uint16_t* ResXMLParser::getAttributeNamespace(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeNamespaceID(idx);
+ //printf("attribute namespace=%d idx=%d event=%p\n", id, idx, mEventCode);
+ //XML_NOISY(printf("getAttributeNamespace 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+const char* ResXMLParser::getAttributeNamespace8(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeNamespaceID(idx);
+ //printf("attribute namespace=%d idx=%d event=%p\n", id, idx, mEventCode);
+ //XML_NOISY(printf("getAttributeNamespace 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.string8At(id, outLen) : NULL;
+}
+
+int32_t ResXMLParser::getAttributeNameID(size_t idx) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ return dtohl(attr->name.index);
+ }
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getAttributeName(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeNameID(idx);
+ //printf("attribute name=%d idx=%d event=%p\n", id, idx, mEventCode);
+ //XML_NOISY(printf("getAttributeName 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+const char* ResXMLParser::getAttributeName8(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeNameID(idx);
+ //printf("attribute name=%d idx=%d event=%p\n", id, idx, mEventCode);
+ //XML_NOISY(printf("getAttributeName 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.string8At(id, outLen) : NULL;
+}
+
+uint32_t ResXMLParser::getAttributeNameResID(size_t idx) const
+{
+ int32_t id = getAttributeNameID(idx);
+ if (id >= 0 && (size_t)id < mTree.mNumResIds) {
+ return dtohl(mTree.mResIds[id]);
+ }
+ return 0;
+}
+
+int32_t ResXMLParser::getAttributeValueStringID(size_t idx) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ return dtohl(attr->rawValue.index);
+ }
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getAttributeStringValue(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeValueStringID(idx);
+ //XML_NOISY(printf("getAttributeValue 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+int32_t ResXMLParser::getAttributeDataType(size_t idx) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ return attr->typedValue.dataType;
+ }
+ }
+ return Res_value::TYPE_NULL;
+}
+
+int32_t ResXMLParser::getAttributeData(size_t idx) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ return dtohl(attr->typedValue.data);
+ }
+ }
+ return 0;
+}
+
+ssize_t ResXMLParser::getAttributeValue(size_t idx, Res_value* outValue) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ outValue->copyFrom_dtoh(attr->typedValue);
+ return sizeof(Res_value);
+ }
+ }
+ return BAD_TYPE;
+}
+
+ssize_t ResXMLParser::indexOfAttribute(const char* ns, const char* attr) const
+{
+ String16 nsStr(ns != NULL ? ns : "");
+ String16 attrStr(attr);
+ return indexOfAttribute(ns ? nsStr.string() : NULL, ns ? nsStr.size() : 0,
+ attrStr.string(), attrStr.size());
+}
+
+ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen,
+ const char16_t* attr, size_t attrLen) const
+{
+ if (mEventCode == START_TAG) {
+ if (attr == NULL) {
+ return NAME_NOT_FOUND;
+ }
+ const size_t N = getAttributeCount();
+ if (mTree.mStrings.isUTF8()) {
+ String8 ns8, attr8;
+ if (ns != NULL) {
+ ns8 = String8(ns, nsLen);
+ }
+ attr8 = String8(attr, attrLen);
+ STRING_POOL_NOISY(ALOGI("indexOfAttribute UTF8 %s (%d) / %s (%d)", ns8.string(), nsLen,
+ attr8.string(), attrLen));
+ for (size_t i=0; i<N; i++) {
+ size_t curNsLen = 0, curAttrLen = 0;
+ const char* curNs = getAttributeNamespace8(i, &curNsLen);
+ const char* curAttr = getAttributeName8(i, &curAttrLen);
+ STRING_POOL_NOISY(ALOGI(" curNs=%s (%d), curAttr=%s (%d)", curNs, curNsLen,
+ curAttr, curAttrLen));
+ if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen
+ && memcmp(attr8.string(), curAttr, attrLen) == 0) {
+ if (ns == NULL) {
+ if (curNs == NULL) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ } else if (curNs != NULL) {
+ //printf(" --> ns=%s, curNs=%s\n",
+ // String8(ns).string(), String8(curNs).string());
+ if (memcmp(ns8.string(), curNs, nsLen) == 0) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ }
+ }
+ }
+ } else {
+ STRING_POOL_NOISY(ALOGI("indexOfAttribute UTF16 %s (%d) / %s (%d)",
+ String8(ns, nsLen).string(), nsLen,
+ String8(attr, attrLen).string(), attrLen));
+ for (size_t i=0; i<N; i++) {
+ size_t curNsLen = 0, curAttrLen = 0;
+ const char16_t* curNs = getAttributeNamespace(i, &curNsLen);
+ const char16_t* curAttr = getAttributeName(i, &curAttrLen);
+ STRING_POOL_NOISY(ALOGI(" curNs=%s (%d), curAttr=%s (%d)",
+ String8(curNs, curNsLen).string(), curNsLen,
+ String8(curAttr, curAttrLen).string(), curAttrLen));
+ if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen
+ && (memcmp(attr, curAttr, attrLen*sizeof(char16_t)) == 0)) {
+ if (ns == NULL) {
+ if (curNs == NULL) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ } else if (curNs != NULL) {
+ //printf(" --> ns=%s, curNs=%s\n",
+ // String8(ns).string(), String8(curNs).string());
+ if (memcmp(ns, curNs, nsLen*sizeof(char16_t)) == 0) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
+ssize_t ResXMLParser::indexOfID() const
+{
+ if (mEventCode == START_TAG) {
+ const ssize_t idx = dtohs(((const ResXMLTree_attrExt*)mCurExt)->idIndex);
+ if (idx > 0) return (idx-1);
+ }
+ return NAME_NOT_FOUND;
+}
+
+ssize_t ResXMLParser::indexOfClass() const
+{
+ if (mEventCode == START_TAG) {
+ const ssize_t idx = dtohs(((const ResXMLTree_attrExt*)mCurExt)->classIndex);
+ if (idx > 0) return (idx-1);
+ }
+ return NAME_NOT_FOUND;
+}
+
+ssize_t ResXMLParser::indexOfStyle() const
+{
+ if (mEventCode == START_TAG) {
+ const ssize_t idx = dtohs(((const ResXMLTree_attrExt*)mCurExt)->styleIndex);
+ if (idx > 0) return (idx-1);
+ }
+ return NAME_NOT_FOUND;
+}
+
+ResXMLParser::event_code_t ResXMLParser::nextNode()
+{
+ if (mEventCode < 0) {
+ return mEventCode;
+ }
+
+ do {
+ const ResXMLTree_node* next = (const ResXMLTree_node*)
+ (((const uint8_t*)mCurNode) + dtohl(mCurNode->header.size));
+ //ALOGW("Next node: prev=%p, next=%p\n", mCurNode, next);
+
+ if (((const uint8_t*)next) >= mTree.mDataEnd) {
+ mCurNode = NULL;
+ return (mEventCode=END_DOCUMENT);
+ }
+
+ if (mTree.validateNode(next) != NO_ERROR) {
+ mCurNode = NULL;
+ return (mEventCode=BAD_DOCUMENT);
+ }
+
+ mCurNode = next;
+ const uint16_t headerSize = dtohs(next->header.headerSize);
+ const uint32_t totalSize = dtohl(next->header.size);
+ mCurExt = ((const uint8_t*)next) + headerSize;
+ size_t minExtSize = 0;
+ event_code_t eventCode = (event_code_t)dtohs(next->header.type);
+ switch ((mEventCode=eventCode)) {
+ case RES_XML_START_NAMESPACE_TYPE:
+ case RES_XML_END_NAMESPACE_TYPE:
+ minExtSize = sizeof(ResXMLTree_namespaceExt);
+ break;
+ case RES_XML_START_ELEMENT_TYPE:
+ minExtSize = sizeof(ResXMLTree_attrExt);
+ break;
+ case RES_XML_END_ELEMENT_TYPE:
+ minExtSize = sizeof(ResXMLTree_endElementExt);
+ break;
+ case RES_XML_CDATA_TYPE:
+ minExtSize = sizeof(ResXMLTree_cdataExt);
+ break;
+ default:
+ ALOGW("Unknown XML block: header type %d in node at %d\n",
+ (int)dtohs(next->header.type),
+ (int)(((const uint8_t*)next)-((const uint8_t*)mTree.mHeader)));
+ continue;
+ }
+
+ if ((totalSize-headerSize) < minExtSize) {
+ ALOGW("Bad XML block: header type 0x%x in node at 0x%x has size %d, need %d\n",
+ (int)dtohs(next->header.type),
+ (int)(((const uint8_t*)next)-((const uint8_t*)mTree.mHeader)),
+ (int)(totalSize-headerSize), (int)minExtSize);
+ return (mEventCode=BAD_DOCUMENT);
+ }
+
+ //printf("CurNode=%p, CurExt=%p, headerSize=%d, minExtSize=%d\n",
+ // mCurNode, mCurExt, headerSize, minExtSize);
+
+ return eventCode;
+ } while (true);
+}
+
+void ResXMLParser::getPosition(ResXMLParser::ResXMLPosition* pos) const
+{
+ pos->eventCode = mEventCode;
+ pos->curNode = mCurNode;
+ pos->curExt = mCurExt;
+}
+
+void ResXMLParser::setPosition(const ResXMLParser::ResXMLPosition& pos)
+{
+ mEventCode = pos.eventCode;
+ mCurNode = pos.curNode;
+ mCurExt = pos.curExt;
+}
+
+
+// --------------------------------------------------------------------
+
+static volatile int32_t gCount = 0;
+
+ResXMLTree::ResXMLTree()
+ : ResXMLParser(*this)
+ , mError(NO_INIT), mOwnedData(NULL)
+{
+ //ALOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1);
+ restart();
+}
+
+ResXMLTree::ResXMLTree(const void* data, size_t size, bool copyData)
+ : ResXMLParser(*this)
+ , mError(NO_INIT), mOwnedData(NULL)
+{
+ //ALOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1);
+ setTo(data, size, copyData);
+}
+
+ResXMLTree::~ResXMLTree()
+{
+ //ALOGI("Destroying ResXMLTree in %p #%d\n", this, android_atomic_dec(&gCount)-1);
+ uninit();
+}
+
+status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData)
+{
+ uninit();
+ mEventCode = START_DOCUMENT;
+
+ if (!data || !size) {
+ return (mError=BAD_TYPE);
+ }
+
+ if (copyData) {
+ mOwnedData = malloc(size);
+ if (mOwnedData == NULL) {
+ return (mError=NO_MEMORY);
+ }
+ memcpy(mOwnedData, data, size);
+ data = mOwnedData;
+ }
+
+ mHeader = (const ResXMLTree_header*)data;
+ mSize = dtohl(mHeader->header.size);
+ if (dtohs(mHeader->header.headerSize) > mSize || mSize > size) {
+ ALOGW("Bad XML block: header size %d or total size %d is larger than data size %d\n",
+ (int)dtohs(mHeader->header.headerSize),
+ (int)dtohl(mHeader->header.size), (int)size);
+ mError = BAD_TYPE;
+ restart();
+ return mError;
+ }
+ mDataEnd = ((const uint8_t*)mHeader) + mSize;
+
+ mStrings.uninit();
+ mRootNode = NULL;
+ mResIds = NULL;
+ mNumResIds = 0;
+
+ // First look for a couple interesting chunks: the string block
+ // and first XML node.
+ const ResChunk_header* chunk =
+ (const ResChunk_header*)(((const uint8_t*)mHeader) + dtohs(mHeader->header.headerSize));
+ const ResChunk_header* lastChunk = chunk;
+ while (((const uint8_t*)chunk) < (mDataEnd-sizeof(ResChunk_header)) &&
+ ((const uint8_t*)chunk) < (mDataEnd-dtohl(chunk->size))) {
+ status_t err = validate_chunk(chunk, sizeof(ResChunk_header), mDataEnd, "XML");
+ if (err != NO_ERROR) {
+ mError = err;
+ goto done;
+ }
+ const uint16_t type = dtohs(chunk->type);
+ const size_t size = dtohl(chunk->size);
+ XML_NOISY(printf("Scanning @ %p: type=0x%x, size=0x%x\n",
+ (void*)(((uint32_t)chunk)-((uint32_t)mHeader)), type, size));
+ if (type == RES_STRING_POOL_TYPE) {
+ mStrings.setTo(chunk, size);
+ } else if (type == RES_XML_RESOURCE_MAP_TYPE) {
+ mResIds = (const uint32_t*)
+ (((const uint8_t*)chunk)+dtohs(chunk->headerSize));
+ mNumResIds = (dtohl(chunk->size)-dtohs(chunk->headerSize))/sizeof(uint32_t);
+ } else if (type >= RES_XML_FIRST_CHUNK_TYPE
+ && type <= RES_XML_LAST_CHUNK_TYPE) {
+ if (validateNode((const ResXMLTree_node*)chunk) != NO_ERROR) {
+ mError = BAD_TYPE;
+ goto done;
+ }
+ mCurNode = (const ResXMLTree_node*)lastChunk;
+ if (nextNode() == BAD_DOCUMENT) {
+ mError = BAD_TYPE;
+ goto done;
+ }
+ mRootNode = mCurNode;
+ mRootExt = mCurExt;
+ mRootCode = mEventCode;
+ break;
+ } else {
+ XML_NOISY(printf("Skipping unknown chunk!\n"));
+ }
+ lastChunk = chunk;
+ chunk = (const ResChunk_header*)
+ (((const uint8_t*)chunk) + size);
+ }
+
+ if (mRootNode == NULL) {
+ ALOGW("Bad XML block: no root element node found\n");
+ mError = BAD_TYPE;
+ goto done;
+ }
+
+ mError = mStrings.getError();
+
+done:
+ restart();
+ return mError;
+}
+
+status_t ResXMLTree::getError() const
+{
+ return mError;
+}
+
+void ResXMLTree::uninit()
+{
+ mError = NO_INIT;
+ mStrings.uninit();
+ if (mOwnedData) {
+ free(mOwnedData);
+ mOwnedData = NULL;
+ }
+ restart();
+}
+
+status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const
+{
+ const uint16_t eventCode = dtohs(node->header.type);
+
+ status_t err = validate_chunk(
+ &node->header, sizeof(ResXMLTree_node),
+ mDataEnd, "ResXMLTree_node");
+
+ if (err >= NO_ERROR) {
+ // Only perform additional validation on START nodes
+ if (eventCode != RES_XML_START_ELEMENT_TYPE) {
+ return NO_ERROR;
+ }
+
+ const uint16_t headerSize = dtohs(node->header.headerSize);
+ const uint32_t size = dtohl(node->header.size);
+ const ResXMLTree_attrExt* attrExt = (const ResXMLTree_attrExt*)
+ (((const uint8_t*)node) + headerSize);
+ // check for sensical values pulled out of the stream so far...
+ if ((size >= headerSize + sizeof(ResXMLTree_attrExt))
+ && ((void*)attrExt > (void*)node)) {
+ const size_t attrSize = ((size_t)dtohs(attrExt->attributeSize))
+ * dtohs(attrExt->attributeCount);
+ if ((dtohs(attrExt->attributeStart)+attrSize) <= (size-headerSize)) {
+ return NO_ERROR;
+ }
+ ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n",
+ (unsigned int)(dtohs(attrExt->attributeStart)+attrSize),
+ (unsigned int)(size-headerSize));
+ }
+ else {
+ ALOGW("Bad XML start block: node header size 0x%x, size 0x%x\n",
+ (unsigned int)headerSize, (unsigned int)size);
+ }
+ return BAD_TYPE;
+ }
+
+ return err;
+
+#if 0
+ const bool isStart = dtohs(node->header.type) == RES_XML_START_ELEMENT_TYPE;
+
+ const uint16_t headerSize = dtohs(node->header.headerSize);
+ const uint32_t size = dtohl(node->header.size);
+
+ if (headerSize >= (isStart ? sizeof(ResXMLTree_attrNode) : sizeof(ResXMLTree_node))) {
+ if (size >= headerSize) {
+ if (((const uint8_t*)node) <= (mDataEnd-size)) {
+ if (!isStart) {
+ return NO_ERROR;
+ }
+ if ((((size_t)dtohs(node->attributeSize))*dtohs(node->attributeCount))
+ <= (size-headerSize)) {
+ return NO_ERROR;
+ }
+ ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n",
+ ((int)dtohs(node->attributeSize))*dtohs(node->attributeCount),
+ (int)(size-headerSize));
+ return BAD_TYPE;
+ }
+ ALOGW("Bad XML block: node at 0x%x extends beyond data end 0x%x\n",
+ (int)(((const uint8_t*)node)-((const uint8_t*)mHeader)), (int)mSize);
+ return BAD_TYPE;
+ }
+ ALOGW("Bad XML block: node at 0x%x header size 0x%x smaller than total size 0x%x\n",
+ (int)(((const uint8_t*)node)-((const uint8_t*)mHeader)),
+ (int)headerSize, (int)size);
+ return BAD_TYPE;
+ }
+ ALOGW("Bad XML block: node at 0x%x header size 0x%x too small\n",
+ (int)(((const uint8_t*)node)-((const uint8_t*)mHeader)),
+ (int)headerSize);
+ return BAD_TYPE;
+#endif
+}
+
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+
+void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) {
+ const size_t size = dtohl(o.size);
+ if (size >= sizeof(ResTable_config)) {
+ *this = o;
+ } else {
+ memcpy(this, &o, size);
+ memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size);
+ }
+}
+
+void ResTable_config::copyFromDtoH(const ResTable_config& o) {
+ copyFromDeviceNoSwap(o);
+ size = sizeof(ResTable_config);
+ mcc = dtohs(mcc);
+ mnc = dtohs(mnc);
+ density = dtohs(density);
+ screenWidth = dtohs(screenWidth);
+ screenHeight = dtohs(screenHeight);
+ sdkVersion = dtohs(sdkVersion);
+ minorVersion = dtohs(minorVersion);
+ smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
+ screenWidthDp = dtohs(screenWidthDp);
+ screenHeightDp = dtohs(screenHeightDp);
+}
+
+void ResTable_config::swapHtoD() {
+ size = htodl(size);
+ mcc = htods(mcc);
+ mnc = htods(mnc);
+ density = htods(density);
+ screenWidth = htods(screenWidth);
+ screenHeight = htods(screenHeight);
+ sdkVersion = htods(sdkVersion);
+ minorVersion = htods(minorVersion);
+ smallestScreenWidthDp = htods(smallestScreenWidthDp);
+ screenWidthDp = htods(screenWidthDp);
+ screenHeightDp = htods(screenHeightDp);
+}
+
+int ResTable_config::compare(const ResTable_config& o) const {
+ int32_t diff = (int32_t)(imsi - o.imsi);
+ if (diff != 0) return diff;
+ diff = (int32_t)(locale - o.locale);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenType - o.screenType);
+ if (diff != 0) return diff;
+ diff = (int32_t)(input - o.input);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenSize - o.screenSize);
+ if (diff != 0) return diff;
+ diff = (int32_t)(version - o.version);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenLayout - o.screenLayout);
+ if (diff != 0) return diff;
+ diff = (int32_t)(uiMode - o.uiMode);
+ if (diff != 0) return diff;
+ diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenSizeDp - o.screenSizeDp);
+ return (int)diff;
+}
+
+int ResTable_config::compareLogical(const ResTable_config& o) const {
+ if (mcc != o.mcc) {
+ return mcc < o.mcc ? -1 : 1;
+ }
+ if (mnc != o.mnc) {
+ return mnc < o.mnc ? -1 : 1;
+ }
+ if (language[0] != o.language[0]) {
+ return language[0] < o.language[0] ? -1 : 1;
+ }
+ if (language[1] != o.language[1]) {
+ return language[1] < o.language[1] ? -1 : 1;
+ }
+ if (country[0] != o.country[0]) {
+ return country[0] < o.country[0] ? -1 : 1;
+ }
+ if (country[1] != o.country[1]) {
+ return country[1] < o.country[1] ? -1 : 1;
+ }
+ if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) {
+ return (screenLayout & MASK_LAYOUTDIR) < (o.screenLayout & MASK_LAYOUTDIR) ? -1 : 1;
+ }
+ if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
+ return smallestScreenWidthDp < o.smallestScreenWidthDp ? -1 : 1;
+ }
+ if (screenWidthDp != o.screenWidthDp) {
+ return screenWidthDp < o.screenWidthDp ? -1 : 1;
+ }
+ if (screenHeightDp != o.screenHeightDp) {
+ return screenHeightDp < o.screenHeightDp ? -1 : 1;
+ }
+ if (screenWidth != o.screenWidth) {
+ return screenWidth < o.screenWidth ? -1 : 1;
+ }
+ if (screenHeight != o.screenHeight) {
+ return screenHeight < o.screenHeight ? -1 : 1;
+ }
+ if (density != o.density) {
+ return density < o.density ? -1 : 1;
+ }
+ if (orientation != o.orientation) {
+ return orientation < o.orientation ? -1 : 1;
+ }
+ if (touchscreen != o.touchscreen) {
+ return touchscreen < o.touchscreen ? -1 : 1;
+ }
+ if (input != o.input) {
+ return input < o.input ? -1 : 1;
+ }
+ if (screenLayout != o.screenLayout) {
+ return screenLayout < o.screenLayout ? -1 : 1;
+ }
+ if (uiMode != o.uiMode) {
+ return uiMode < o.uiMode ? -1 : 1;
+ }
+ if (version != o.version) {
+ return version < o.version ? -1 : 1;
+ }
+ return 0;
+}
+
+int ResTable_config::diff(const ResTable_config& o) const {
+ int diffs = 0;
+ if (mcc != o.mcc) diffs |= CONFIG_MCC;
+ if (mnc != o.mnc) diffs |= CONFIG_MNC;
+ if (locale != o.locale) diffs |= CONFIG_LOCALE;
+ if (orientation != o.orientation) diffs |= CONFIG_ORIENTATION;
+ if (density != o.density) diffs |= CONFIG_DENSITY;
+ if (touchscreen != o.touchscreen) diffs |= CONFIG_TOUCHSCREEN;
+ if (((inputFlags^o.inputFlags)&(MASK_KEYSHIDDEN|MASK_NAVHIDDEN)) != 0)
+ diffs |= CONFIG_KEYBOARD_HIDDEN;
+ if (keyboard != o.keyboard) diffs |= CONFIG_KEYBOARD;
+ if (navigation != o.navigation) diffs |= CONFIG_NAVIGATION;
+ if (screenSize != o.screenSize) diffs |= CONFIG_SCREEN_SIZE;
+ if (version != o.version) diffs |= CONFIG_VERSION;
+ if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) diffs |= CONFIG_LAYOUTDIR;
+ if ((screenLayout & ~MASK_LAYOUTDIR) != (o.screenLayout & ~MASK_LAYOUTDIR)) diffs |= CONFIG_SCREEN_LAYOUT;
+ if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
+ if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
+ if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE;
+ return diffs;
+}
+
+bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const {
+ // The order of the following tests defines the importance of one
+ // configuration parameter over another. Those tests first are more
+ // important, trumping any values in those following them.
+ if (imsi || o.imsi) {
+ if (mcc != o.mcc) {
+ if (!mcc) return false;
+ if (!o.mcc) return true;
+ }
+
+ if (mnc != o.mnc) {
+ if (!mnc) return false;
+ if (!o.mnc) return true;
+ }
+ }
+
+ if (locale || o.locale) {
+ if (language[0] != o.language[0]) {
+ if (!language[0]) return false;
+ if (!o.language[0]) return true;
+ }
+
+ if (country[0] != o.country[0]) {
+ if (!country[0]) return false;
+ if (!o.country[0]) return true;
+ }
+ }
+
+ if (screenLayout || o.screenLayout) {
+ if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0) {
+ if (!(screenLayout & MASK_LAYOUTDIR)) return false;
+ if (!(o.screenLayout & MASK_LAYOUTDIR)) return true;
+ }
+ }
+
+ if (smallestScreenWidthDp || o.smallestScreenWidthDp) {
+ if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
+ if (!smallestScreenWidthDp) return false;
+ if (!o.smallestScreenWidthDp) return true;
+ }
+ }
+
+ if (screenSizeDp || o.screenSizeDp) {
+ if (screenWidthDp != o.screenWidthDp) {
+ if (!screenWidthDp) return false;
+ if (!o.screenWidthDp) return true;
+ }
+
+ if (screenHeightDp != o.screenHeightDp) {
+ if (!screenHeightDp) return false;
+ if (!o.screenHeightDp) return true;
+ }
+ }
+
+ if (screenLayout || o.screenLayout) {
+ if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0) {
+ if (!(screenLayout & MASK_SCREENSIZE)) return false;
+ if (!(o.screenLayout & MASK_SCREENSIZE)) return true;
+ }
+ if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0) {
+ if (!(screenLayout & MASK_SCREENLONG)) return false;
+ if (!(o.screenLayout & MASK_SCREENLONG)) return true;
+ }
+ }
+
+ if (orientation != o.orientation) {
+ if (!orientation) return false;
+ if (!o.orientation) return true;
+ }
+
+ if (uiMode || o.uiMode) {
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0) {
+ if (!(uiMode & MASK_UI_MODE_TYPE)) return false;
+ if (!(o.uiMode & MASK_UI_MODE_TYPE)) return true;
+ }
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0) {
+ if (!(uiMode & MASK_UI_MODE_NIGHT)) return false;
+ if (!(o.uiMode & MASK_UI_MODE_NIGHT)) return true;
+ }
+ }
+
+ // density is never 'more specific'
+ // as the default just equals 160
+
+ if (touchscreen != o.touchscreen) {
+ if (!touchscreen) return false;
+ if (!o.touchscreen) return true;
+ }
+
+ if (input || o.input) {
+ if (((inputFlags^o.inputFlags) & MASK_KEYSHIDDEN) != 0) {
+ if (!(inputFlags & MASK_KEYSHIDDEN)) return false;
+ if (!(o.inputFlags & MASK_KEYSHIDDEN)) return true;
+ }
+
+ if (((inputFlags^o.inputFlags) & MASK_NAVHIDDEN) != 0) {
+ if (!(inputFlags & MASK_NAVHIDDEN)) return false;
+ if (!(o.inputFlags & MASK_NAVHIDDEN)) return true;
+ }
+
+ if (keyboard != o.keyboard) {
+ if (!keyboard) return false;
+ if (!o.keyboard) return true;
+ }
+
+ if (navigation != o.navigation) {
+ if (!navigation) return false;
+ if (!o.navigation) return true;
+ }
+ }
+
+ if (screenSize || o.screenSize) {
+ if (screenWidth != o.screenWidth) {
+ if (!screenWidth) return false;
+ if (!o.screenWidth) return true;
+ }
+
+ if (screenHeight != o.screenHeight) {
+ if (!screenHeight) return false;
+ if (!o.screenHeight) return true;
+ }
+ }
+
+ if (version || o.version) {
+ if (sdkVersion != o.sdkVersion) {
+ if (!sdkVersion) return false;
+ if (!o.sdkVersion) return true;
+ }
+
+ if (minorVersion != o.minorVersion) {
+ if (!minorVersion) return false;
+ if (!o.minorVersion) return true;
+ }
+ }
+ return false;
+}
+
+bool ResTable_config::isBetterThan(const ResTable_config& o,
+ const ResTable_config* requested) const {
+ if (requested) {
+ if (imsi || o.imsi) {
+ if ((mcc != o.mcc) && requested->mcc) {
+ return (mcc);
+ }
+
+ if ((mnc != o.mnc) && requested->mnc) {
+ return (mnc);
+ }
+ }
+
+ if (locale || o.locale) {
+ if ((language[0] != o.language[0]) && requested->language[0]) {
+ return (language[0]);
+ }
+
+ if ((country[0] != o.country[0]) && requested->country[0]) {
+ return (country[0]);
+ }
+ }
+
+ if (screenLayout || o.screenLayout) {
+ if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0
+ && (requested->screenLayout & MASK_LAYOUTDIR)) {
+ int myLayoutDir = screenLayout & MASK_LAYOUTDIR;
+ int oLayoutDir = o.screenLayout & MASK_LAYOUTDIR;
+ return (myLayoutDir > oLayoutDir);
+ }
+ }
+
+ if (smallestScreenWidthDp || o.smallestScreenWidthDp) {
+ // The configuration closest to the actual size is best.
+ // We assume that larger configs have already been filtered
+ // out at this point. That means we just want the largest one.
+ if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
+ return smallestScreenWidthDp > o.smallestScreenWidthDp;
+ }
+ }
+
+ if (screenSizeDp || o.screenSizeDp) {
+ // "Better" is based on the sum of the difference between both
+ // width and height from the requested dimensions. We are
+ // assuming the invalid configs (with smaller dimens) have
+ // already been filtered. Note that if a particular dimension
+ // is unspecified, we will end up with a large value (the
+ // difference between 0 and the requested dimension), which is
+ // good since we will prefer a config that has specified a
+ // dimension value.
+ int myDelta = 0, otherDelta = 0;
+ if (requested->screenWidthDp) {
+ myDelta += requested->screenWidthDp - screenWidthDp;
+ otherDelta += requested->screenWidthDp - o.screenWidthDp;
+ }
+ if (requested->screenHeightDp) {
+ myDelta += requested->screenHeightDp - screenHeightDp;
+ otherDelta += requested->screenHeightDp - o.screenHeightDp;
+ }
+ //ALOGI("Comparing this %dx%d to other %dx%d in %dx%d: myDelta=%d otherDelta=%d",
+ // screenWidthDp, screenHeightDp, o.screenWidthDp, o.screenHeightDp,
+ // requested->screenWidthDp, requested->screenHeightDp, myDelta, otherDelta);
+ if (myDelta != otherDelta) {
+ return myDelta < otherDelta;
+ }
+ }
+
+ if (screenLayout || o.screenLayout) {
+ if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0
+ && (requested->screenLayout & MASK_SCREENSIZE)) {
+ // A little backwards compatibility here: undefined is
+ // considered equivalent to normal. But only if the
+ // requested size is at least normal; otherwise, small
+ // is better than the default.
+ int mySL = (screenLayout & MASK_SCREENSIZE);
+ int oSL = (o.screenLayout & MASK_SCREENSIZE);
+ int fixedMySL = mySL;
+ int fixedOSL = oSL;
+ if ((requested->screenLayout & MASK_SCREENSIZE) >= SCREENSIZE_NORMAL) {
+ if (fixedMySL == 0) fixedMySL = SCREENSIZE_NORMAL;
+ if (fixedOSL == 0) fixedOSL = SCREENSIZE_NORMAL;
+ }
+ // For screen size, the best match is the one that is
+ // closest to the requested screen size, but not over
+ // (the not over part is dealt with in match() below).
+ if (fixedMySL == fixedOSL) {
+ // If the two are the same, but 'this' is actually
+ // undefined, then the other is really a better match.
+ if (mySL == 0) return false;
+ return true;
+ }
+ if (fixedMySL != fixedOSL) {
+ return fixedMySL > fixedOSL;
+ }
+ }
+ if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0
+ && (requested->screenLayout & MASK_SCREENLONG)) {
+ return (screenLayout & MASK_SCREENLONG);
+ }
+ }
+
+ if ((orientation != o.orientation) && requested->orientation) {
+ return (orientation);
+ }
+
+ if (uiMode || o.uiMode) {
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0
+ && (requested->uiMode & MASK_UI_MODE_TYPE)) {
+ return (uiMode & MASK_UI_MODE_TYPE);
+ }
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0
+ && (requested->uiMode & MASK_UI_MODE_NIGHT)) {
+ return (uiMode & MASK_UI_MODE_NIGHT);
+ }
+ }
+
+ if (screenType || o.screenType) {
+ if (density != o.density) {
+ // density is tough. Any density is potentially useful
+ // because the system will scale it. Scaling down
+ // is generally better than scaling up.
+ // Default density counts as 160dpi (the system default)
+ // TODO - remove 160 constants
+ int h = (density?density:160);
+ int l = (o.density?o.density:160);
+ bool bImBigger = true;
+ if (l > h) {
+ int t = h;
+ h = l;
+ l = t;
+ bImBigger = false;
+ }
+
+ int reqValue = (requested->density?requested->density:160);
+ if (reqValue >= h) {
+ // requested value higher than both l and h, give h
+ return bImBigger;
+ }
+ if (l >= reqValue) {
+ // requested value lower than both l and h, give l
+ return !bImBigger;
+ }
+ // saying that scaling down is 2x better than up
+ if (((2 * l) - reqValue) * h > reqValue * reqValue) {
+ return !bImBigger;
+ } else {
+ return bImBigger;
+ }
+ }
+
+ if ((touchscreen != o.touchscreen) && requested->touchscreen) {
+ return (touchscreen);
+ }
+ }
+
+ if (input || o.input) {
+ const int keysHidden = inputFlags & MASK_KEYSHIDDEN;
+ const int oKeysHidden = o.inputFlags & MASK_KEYSHIDDEN;
+ if (keysHidden != oKeysHidden) {
+ const int reqKeysHidden =
+ requested->inputFlags & MASK_KEYSHIDDEN;
+ if (reqKeysHidden) {
+
+ if (!keysHidden) return false;
+ if (!oKeysHidden) return true;
+ // For compatibility, we count KEYSHIDDEN_NO as being
+ // the same as KEYSHIDDEN_SOFT. Here we disambiguate
+ // these by making an exact match more specific.
+ if (reqKeysHidden == keysHidden) return true;
+ if (reqKeysHidden == oKeysHidden) return false;
+ }
+ }
+
+ const int navHidden = inputFlags & MASK_NAVHIDDEN;
+ const int oNavHidden = o.inputFlags & MASK_NAVHIDDEN;
+ if (navHidden != oNavHidden) {
+ const int reqNavHidden =
+ requested->inputFlags & MASK_NAVHIDDEN;
+ if (reqNavHidden) {
+
+ if (!navHidden) return false;
+ if (!oNavHidden) return true;
+ }
+ }
+
+ if ((keyboard != o.keyboard) && requested->keyboard) {
+ return (keyboard);
+ }
+
+ if ((navigation != o.navigation) && requested->navigation) {
+ return (navigation);
+ }
+ }
+
+ if (screenSize || o.screenSize) {
+ // "Better" is based on the sum of the difference between both
+ // width and height from the requested dimensions. We are
+ // assuming the invalid configs (with smaller sizes) have
+ // already been filtered. Note that if a particular dimension
+ // is unspecified, we will end up with a large value (the
+ // difference between 0 and the requested dimension), which is
+ // good since we will prefer a config that has specified a
+ // size value.
+ int myDelta = 0, otherDelta = 0;
+ if (requested->screenWidth) {
+ myDelta += requested->screenWidth - screenWidth;
+ otherDelta += requested->screenWidth - o.screenWidth;
+ }
+ if (requested->screenHeight) {
+ myDelta += requested->screenHeight - screenHeight;
+ otherDelta += requested->screenHeight - o.screenHeight;
+ }
+ if (myDelta != otherDelta) {
+ return myDelta < otherDelta;
+ }
+ }
+
+ if (version || o.version) {
+ if ((sdkVersion != o.sdkVersion) && requested->sdkVersion) {
+ return (sdkVersion > o.sdkVersion);
+ }
+
+ if ((minorVersion != o.minorVersion) &&
+ requested->minorVersion) {
+ return (minorVersion);
+ }
+ }
+
+ return false;
+ }
+ return isMoreSpecificThan(o);
+}
+
+bool ResTable_config::match(const ResTable_config& settings) const {
+ if (imsi != 0) {
+ if (mcc != 0 && mcc != settings.mcc) {
+ return false;
+ }
+ if (mnc != 0 && mnc != settings.mnc) {
+ return false;
+ }
+ }
+ if (locale != 0) {
+ if (language[0] != 0
+ && (language[0] != settings.language[0]
+ || language[1] != settings.language[1])) {
+ return false;
+ }
+ if (country[0] != 0
+ && (country[0] != settings.country[0]
+ || country[1] != settings.country[1])) {
+ return false;
+ }
+ }
+ if (screenConfig != 0) {
+ const int layoutDir = screenLayout&MASK_LAYOUTDIR;
+ const int setLayoutDir = settings.screenLayout&MASK_LAYOUTDIR;
+ if (layoutDir != 0 && layoutDir != setLayoutDir) {
+ return false;
+ }
+
+ const int screenSize = screenLayout&MASK_SCREENSIZE;
+ const int setScreenSize = settings.screenLayout&MASK_SCREENSIZE;
+ // Any screen sizes for larger screens than the setting do not
+ // match.
+ if (screenSize != 0 && screenSize > setScreenSize) {
+ return false;
+ }
+
+ const int screenLong = screenLayout&MASK_SCREENLONG;
+ const int setScreenLong = settings.screenLayout&MASK_SCREENLONG;
+ if (screenLong != 0 && screenLong != setScreenLong) {
+ return false;
+ }
+
+ const int uiModeType = uiMode&MASK_UI_MODE_TYPE;
+ const int setUiModeType = settings.uiMode&MASK_UI_MODE_TYPE;
+ if (uiModeType != 0 && uiModeType != setUiModeType) {
+ return false;
+ }
+
+ const int uiModeNight = uiMode&MASK_UI_MODE_NIGHT;
+ const int setUiModeNight = settings.uiMode&MASK_UI_MODE_NIGHT;
+ if (uiModeNight != 0 && uiModeNight != setUiModeNight) {
+ return false;
+ }
+
+ if (smallestScreenWidthDp != 0
+ && smallestScreenWidthDp > settings.smallestScreenWidthDp) {
+ return false;
+ }
+ }
+ if (screenSizeDp != 0) {
+ if (screenWidthDp != 0 && screenWidthDp > settings.screenWidthDp) {
+ //ALOGI("Filtering out width %d in requested %d", screenWidthDp, settings.screenWidthDp);
+ return false;
+ }
+ if (screenHeightDp != 0 && screenHeightDp > settings.screenHeightDp) {
+ //ALOGI("Filtering out height %d in requested %d", screenHeightDp, settings.screenHeightDp);
+ return false;
+ }
+ }
+ if (screenType != 0) {
+ if (orientation != 0 && orientation != settings.orientation) {
+ return false;
+ }
+ // density always matches - we can scale it. See isBetterThan
+ if (touchscreen != 0 && touchscreen != settings.touchscreen) {
+ return false;
+ }
+ }
+ if (input != 0) {
+ const int keysHidden = inputFlags&MASK_KEYSHIDDEN;
+ const int setKeysHidden = settings.inputFlags&MASK_KEYSHIDDEN;
+ if (keysHidden != 0 && keysHidden != setKeysHidden) {
+ // For compatibility, we count a request for KEYSHIDDEN_NO as also
+ // matching the more recent KEYSHIDDEN_SOFT. Basically
+ // KEYSHIDDEN_NO means there is some kind of keyboard available.
+ //ALOGI("Matching keysHidden: have=%d, config=%d\n", keysHidden, setKeysHidden);
+ if (keysHidden != KEYSHIDDEN_NO || setKeysHidden != KEYSHIDDEN_SOFT) {
+ //ALOGI("No match!");
+ return false;
+ }
+ }
+ const int navHidden = inputFlags&MASK_NAVHIDDEN;
+ const int setNavHidden = settings.inputFlags&MASK_NAVHIDDEN;
+ if (navHidden != 0 && navHidden != setNavHidden) {
+ return false;
+ }
+ if (keyboard != 0 && keyboard != settings.keyboard) {
+ return false;
+ }
+ if (navigation != 0 && navigation != settings.navigation) {
+ return false;
+ }
+ }
+ if (screenSize != 0) {
+ if (screenWidth != 0 && screenWidth > settings.screenWidth) {
+ return false;
+ }
+ if (screenHeight != 0 && screenHeight > settings.screenHeight) {
+ return false;
+ }
+ }
+ if (version != 0) {
+ if (sdkVersion != 0 && sdkVersion > settings.sdkVersion) {
+ return false;
+ }
+ if (minorVersion != 0 && minorVersion != settings.minorVersion) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void ResTable_config::getLocale(char str[6]) const {
+ memset(str, 0, 6);
+ if (language[0]) {
+ str[0] = language[0];
+ str[1] = language[1];
+ if (country[0]) {
+ str[2] = '_';
+ str[3] = country[0];
+ str[4] = country[1];
+ }
+ }
+}
+
+String8 ResTable_config::toString() const {
+ String8 res;
+
+ if (mcc != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("%dmcc", dtohs(mcc));
+ }
+ if (mnc != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("%dmnc", dtohs(mnc));
+ }
+ if (language[0] != 0) {
+ if (res.size() > 0) res.append("-");
+ res.append(language, 2);
+ }
+ if (country[0] != 0) {
+ if (res.size() > 0) res.append("-");
+ res.append(country, 2);
+ }
+ if ((screenLayout&MASK_LAYOUTDIR) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (screenLayout&ResTable_config::MASK_LAYOUTDIR) {
+ case ResTable_config::LAYOUTDIR_LTR:
+ res.append("ldltr");
+ break;
+ case ResTable_config::LAYOUTDIR_RTL:
+ res.append("ldrtl");
+ break;
+ default:
+ res.appendFormat("layoutDir=%d",
+ dtohs(screenLayout&ResTable_config::MASK_LAYOUTDIR));
+ break;
+ }
+ }
+ if (smallestScreenWidthDp != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("sw%ddp", dtohs(smallestScreenWidthDp));
+ }
+ if (screenWidthDp != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("w%ddp", dtohs(screenWidthDp));
+ }
+ if (screenHeightDp != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("h%ddp", dtohs(screenHeightDp));
+ }
+ if ((screenLayout&MASK_SCREENSIZE) != SCREENSIZE_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (screenLayout&ResTable_config::MASK_SCREENSIZE) {
+ case ResTable_config::SCREENSIZE_SMALL:
+ res.append("small");
+ break;
+ case ResTable_config::SCREENSIZE_NORMAL:
+ res.append("normal");
+ break;
+ case ResTable_config::SCREENSIZE_LARGE:
+ res.append("large");
+ break;
+ case ResTable_config::SCREENSIZE_XLARGE:
+ res.append("xlarge");
+ break;
+ default:
+ res.appendFormat("screenLayoutSize=%d",
+ dtohs(screenLayout&ResTable_config::MASK_SCREENSIZE));
+ break;
+ }
+ }
+ if ((screenLayout&MASK_SCREENLONG) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (screenLayout&ResTable_config::MASK_SCREENLONG) {
+ case ResTable_config::SCREENLONG_NO:
+ res.append("notlong");
+ break;
+ case ResTable_config::SCREENLONG_YES:
+ res.append("long");
+ break;
+ default:
+ res.appendFormat("screenLayoutLong=%d",
+ dtohs(screenLayout&ResTable_config::MASK_SCREENLONG));
+ break;
+ }
+ }
+ if (orientation != ORIENTATION_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (orientation) {
+ case ResTable_config::ORIENTATION_PORT:
+ res.append("port");
+ break;
+ case ResTable_config::ORIENTATION_LAND:
+ res.append("land");
+ break;
+ case ResTable_config::ORIENTATION_SQUARE:
+ res.append("square");
+ break;
+ default:
+ res.appendFormat("orientation=%d", dtohs(orientation));
+ break;
+ }
+ }
+ if ((uiMode&MASK_UI_MODE_TYPE) != UI_MODE_TYPE_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (uiMode&ResTable_config::MASK_UI_MODE_TYPE) {
+ case ResTable_config::UI_MODE_TYPE_DESK:
+ res.append("desk");
+ break;
+ case ResTable_config::UI_MODE_TYPE_CAR:
+ res.append("car");
+ break;
+ case ResTable_config::UI_MODE_TYPE_TELEVISION:
+ res.append("television");
+ break;
+ case ResTable_config::UI_MODE_TYPE_APPLIANCE:
+ res.append("appliance");
+ break;
+ default:
+ res.appendFormat("uiModeType=%d",
+ dtohs(screenLayout&ResTable_config::MASK_UI_MODE_TYPE));
+ break;
+ }
+ }
+ if ((uiMode&MASK_UI_MODE_NIGHT) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (uiMode&ResTable_config::MASK_UI_MODE_NIGHT) {
+ case ResTable_config::UI_MODE_NIGHT_NO:
+ res.append("notnight");
+ break;
+ case ResTable_config::UI_MODE_NIGHT_YES:
+ res.append("night");
+ break;
+ default:
+ res.appendFormat("uiModeNight=%d",
+ dtohs(uiMode&MASK_UI_MODE_NIGHT));
+ break;
+ }
+ }
+ if (density != DENSITY_DEFAULT) {
+ if (res.size() > 0) res.append("-");
+ switch (density) {
+ case ResTable_config::DENSITY_LOW:
+ res.append("ldpi");
+ break;
+ case ResTable_config::DENSITY_MEDIUM:
+ res.append("mdpi");
+ break;
+ case ResTable_config::DENSITY_TV:
+ res.append("tvdpi");
+ break;
+ case ResTable_config::DENSITY_HIGH:
+ res.append("hdpi");
+ break;
+ case ResTable_config::DENSITY_XHIGH:
+ res.append("xhdpi");
+ break;
+ case ResTable_config::DENSITY_XXHIGH:
+ res.append("xxhdpi");
+ break;
+ case ResTable_config::DENSITY_NONE:
+ res.append("nodpi");
+ break;
+ default:
+ res.appendFormat("%ddpi", dtohs(density));
+ break;
+ }
+ }
+ if (touchscreen != TOUCHSCREEN_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (touchscreen) {
+ case ResTable_config::TOUCHSCREEN_NOTOUCH:
+ res.append("notouch");
+ break;
+ case ResTable_config::TOUCHSCREEN_FINGER:
+ res.append("finger");
+ break;
+ case ResTable_config::TOUCHSCREEN_STYLUS:
+ res.append("stylus");
+ break;
+ default:
+ res.appendFormat("touchscreen=%d", dtohs(touchscreen));
+ break;
+ }
+ }
+ if (keyboard != KEYBOARD_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (keyboard) {
+ case ResTable_config::KEYBOARD_NOKEYS:
+ res.append("nokeys");
+ break;
+ case ResTable_config::KEYBOARD_QWERTY:
+ res.append("qwerty");
+ break;
+ case ResTable_config::KEYBOARD_12KEY:
+ res.append("12key");
+ break;
+ default:
+ res.appendFormat("keyboard=%d", dtohs(keyboard));
+ break;
+ }
+ }
+ if ((inputFlags&MASK_KEYSHIDDEN) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (inputFlags&MASK_KEYSHIDDEN) {
+ case ResTable_config::KEYSHIDDEN_NO:
+ res.append("keysexposed");
+ break;
+ case ResTable_config::KEYSHIDDEN_YES:
+ res.append("keyshidden");
+ break;
+ case ResTable_config::KEYSHIDDEN_SOFT:
+ res.append("keyssoft");
+ break;
+ }
+ }
+ if (navigation != NAVIGATION_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (navigation) {
+ case ResTable_config::NAVIGATION_NONAV:
+ res.append("nonav");
+ break;
+ case ResTable_config::NAVIGATION_DPAD:
+ res.append("dpad");
+ break;
+ case ResTable_config::NAVIGATION_TRACKBALL:
+ res.append("trackball");
+ break;
+ case ResTable_config::NAVIGATION_WHEEL:
+ res.append("wheel");
+ break;
+ default:
+ res.appendFormat("navigation=%d", dtohs(navigation));
+ break;
+ }
+ }
+ if ((inputFlags&MASK_NAVHIDDEN) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (inputFlags&MASK_NAVHIDDEN) {
+ case ResTable_config::NAVHIDDEN_NO:
+ res.append("navsexposed");
+ break;
+ case ResTable_config::NAVHIDDEN_YES:
+ res.append("navhidden");
+ break;
+ default:
+ res.appendFormat("inputFlagsNavHidden=%d",
+ dtohs(inputFlags&MASK_NAVHIDDEN));
+ break;
+ }
+ }
+ if (screenSize != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("%dx%d", dtohs(screenWidth), dtohs(screenHeight));
+ }
+ if (version != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("v%d", dtohs(sdkVersion));
+ if (minorVersion != 0) {
+ res.appendFormat(".%d", dtohs(minorVersion));
+ }
+ }
+
+ return res;
+}
+
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+
+struct ResTable::Header
+{
+ Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL),
+ resourceIDMap(NULL), resourceIDMapSize(0) { }
+
+ ~Header()
+ {
+ free(resourceIDMap);
+ }
+
+ ResTable* const owner;
+ void* ownedData;
+ const ResTable_header* header;
+ size_t size;
+ const uint8_t* dataEnd;
+ size_t index;
+ void* cookie;
+
+ ResStringPool values;
+ uint32_t* resourceIDMap;
+ size_t resourceIDMapSize;
+};
+
+struct ResTable::Type
+{
+ Type(const Header* _header, const Package* _package, size_t count)
+ : header(_header), package(_package), entryCount(count),
+ typeSpec(NULL), typeSpecFlags(NULL) { }
+ const Header* const header;
+ const Package* const package;
+ const size_t entryCount;
+ const ResTable_typeSpec* typeSpec;
+ const uint32_t* typeSpecFlags;
+ Vector<const ResTable_type*> configs;
+};
+
+struct ResTable::Package
+{
+ Package(ResTable* _owner, const Header* _header, const ResTable_package* _package)
+ : owner(_owner), header(_header), package(_package) { }
+ ~Package()
+ {
+ size_t i = types.size();
+ while (i > 0) {
+ i--;
+ delete types[i];
+ }
+ }
+
+ ResTable* const owner;
+ const Header* const header;
+ const ResTable_package* const package;
+ Vector<Type*> types;
+
+ ResStringPool typeStrings;
+ ResStringPool keyStrings;
+
+ const Type* getType(size_t idx) const {
+ return idx < types.size() ? types[idx] : NULL;
+ }
+};
+
+// A group of objects describing a particular resource package.
+// The first in 'package' is always the root object (from the resource
+// table that defined the package); the ones after are skins on top of it.
+struct ResTable::PackageGroup
+{
+ PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id)
+ : owner(_owner), name(_name), id(_id), typeCount(0), bags(NULL) { }
+ ~PackageGroup() {
+ clearBagCache();
+ const size_t N = packages.size();
+ for (size_t i=0; i<N; i++) {
+ Package* pkg = packages[i];
+ if (pkg->owner == owner) {
+ delete pkg;
+ }
+ }
+ }
+
+ void clearBagCache() {
+ if (bags) {
+ TABLE_NOISY(printf("bags=%p\n", bags));
+ Package* pkg = packages[0];
+ TABLE_NOISY(printf("typeCount=%x\n", typeCount));
+ for (size_t i=0; i<typeCount; i++) {
+ TABLE_NOISY(printf("type=%d\n", i));
+ const Type* type = pkg->getType(i);
+ if (type != NULL) {
+ bag_set** typeBags = bags[i];
+ TABLE_NOISY(printf("typeBags=%p\n", typeBags));
+ if (typeBags) {
+ TABLE_NOISY(printf("type->entryCount=%x\n", type->entryCount));
+ const size_t N = type->entryCount;
+ for (size_t j=0; j<N; j++) {
+ if (typeBags[j] && typeBags[j] != (bag_set*)0xFFFFFFFF)
+ free(typeBags[j]);
+ }
+ free(typeBags);
+ }
+ }
+ }
+ free(bags);
+ bags = NULL;
+ }
+ }
+
+ ResTable* const owner;
+ String16 const name;
+ uint32_t const id;
+ Vector<Package*> packages;
+
+ // This is for finding typeStrings and other common package stuff.
+ Package* basePackage;
+
+ // For quick access.
+ size_t typeCount;
+
+ // Computed attribute bags, first indexed by the type and second
+ // by the entry in that type.
+ bag_set*** bags;
+};
+
+struct ResTable::bag_set
+{
+ size_t numAttrs; // number in array
+ size_t availAttrs; // total space in array
+ uint32_t typeSpecFlags;
+ // Followed by 'numAttr' bag_entry structures.
+};
+
+ResTable::Theme::Theme(const ResTable& table)
+ : mTable(table)
+{
+ memset(mPackages, 0, sizeof(mPackages));
+}
+
+ResTable::Theme::~Theme()
+{
+ for (size_t i=0; i<Res_MAXPACKAGE; i++) {
+ package_info* pi = mPackages[i];
+ if (pi != NULL) {
+ free_package(pi);
+ }
+ }
+}
+
+void ResTable::Theme::free_package(package_info* pi)
+{
+ for (size_t j=0; j<pi->numTypes; j++) {
+ theme_entry* te = pi->types[j].entries;
+ if (te != NULL) {
+ free(te);
+ }
+ }
+ free(pi);
+}
+
+ResTable::Theme::package_info* ResTable::Theme::copy_package(package_info* pi)
+{
+ package_info* newpi = (package_info*)malloc(
+ sizeof(package_info) + (pi->numTypes*sizeof(type_info)));
+ newpi->numTypes = pi->numTypes;
+ for (size_t j=0; j<newpi->numTypes; j++) {
+ size_t cnt = pi->types[j].numEntries;
+ newpi->types[j].numEntries = cnt;
+ theme_entry* te = pi->types[j].entries;
+ if (te != NULL) {
+ theme_entry* newte = (theme_entry*)malloc(cnt*sizeof(theme_entry));
+ newpi->types[j].entries = newte;
+ memcpy(newte, te, cnt*sizeof(theme_entry));
+ } else {
+ newpi->types[j].entries = NULL;
+ }
+ }
+ return newpi;
+}
+
+status_t ResTable::Theme::applyStyle(uint32_t resID, bool force)
+{
+ const bag_entry* bag;
+ uint32_t bagTypeSpecFlags = 0;
+ mTable.lock();
+ const ssize_t N = mTable.getBagLocked(resID, &bag, &bagTypeSpecFlags);
+ TABLE_NOISY(ALOGV("Applying style 0x%08x to theme %p, count=%d", resID, this, N));
+ if (N < 0) {
+ mTable.unlock();
+ return N;
+ }
+
+ uint32_t curPackage = 0xffffffff;
+ ssize_t curPackageIndex = 0;
+ package_info* curPI = NULL;
+ uint32_t curType = 0xffffffff;
+ size_t numEntries = 0;
+ theme_entry* curEntries = NULL;
+
+ const bag_entry* end = bag + N;
+ while (bag < end) {
+ const uint32_t attrRes = bag->map.name.ident;
+ const uint32_t p = Res_GETPACKAGE(attrRes);
+ const uint32_t t = Res_GETTYPE(attrRes);
+ const uint32_t e = Res_GETENTRY(attrRes);
+
+ if (curPackage != p) {
+ const ssize_t pidx = mTable.getResourcePackageIndex(attrRes);
+ if (pidx < 0) {
+ ALOGE("Style contains key with bad package: 0x%08x\n", attrRes);
+ bag++;
+ continue;
+ }
+ curPackage = p;
+ curPackageIndex = pidx;
+ curPI = mPackages[pidx];
+ if (curPI == NULL) {
+ PackageGroup* const grp = mTable.mPackageGroups[pidx];
+ int cnt = grp->typeCount;
+ curPI = (package_info*)malloc(
+ sizeof(package_info) + (cnt*sizeof(type_info)));
+ curPI->numTypes = cnt;
+ memset(curPI->types, 0, cnt*sizeof(type_info));
+ mPackages[pidx] = curPI;
+ }
+ curType = 0xffffffff;
+ }
+ if (curType != t) {
+ if (t >= curPI->numTypes) {
+ ALOGE("Style contains key with bad type: 0x%08x\n", attrRes);
+ bag++;
+ continue;
+ }
+ curType = t;
+ curEntries = curPI->types[t].entries;
+ if (curEntries == NULL) {
+ PackageGroup* const grp = mTable.mPackageGroups[curPackageIndex];
+ const Type* type = grp->packages[0]->getType(t);
+ int cnt = type != NULL ? type->entryCount : 0;
+ curEntries = (theme_entry*)malloc(cnt*sizeof(theme_entry));
+ memset(curEntries, Res_value::TYPE_NULL, cnt*sizeof(theme_entry));
+ curPI->types[t].numEntries = cnt;
+ curPI->types[t].entries = curEntries;
+ }
+ numEntries = curPI->types[t].numEntries;
+ }
+ if (e >= numEntries) {
+ ALOGE("Style contains key with bad entry: 0x%08x\n", attrRes);
+ bag++;
+ continue;
+ }
+ theme_entry* curEntry = curEntries + e;
+ TABLE_NOISY(ALOGV("Attr 0x%08x: type=0x%x, data=0x%08x; curType=0x%x",
+ attrRes, bag->map.value.dataType, bag->map.value.data,
+ curEntry->value.dataType));
+ if (force || curEntry->value.dataType == Res_value::TYPE_NULL) {
+ curEntry->stringBlock = bag->stringBlock;
+ curEntry->typeSpecFlags |= bagTypeSpecFlags;
+ curEntry->value = bag->map.value;
+ }
+
+ bag++;
+ }
+
+ mTable.unlock();
+
+ //ALOGI("Applying style 0x%08x (force=%d) theme %p...\n", resID, force, this);
+ //dumpToLog();
+
+ return NO_ERROR;
+}
+
+status_t ResTable::Theme::setTo(const Theme& other)
+{
+ //ALOGI("Setting theme %p from theme %p...\n", this, &other);
+ //dumpToLog();
+ //other.dumpToLog();
+
+ if (&mTable == &other.mTable) {
+ for (size_t i=0; i<Res_MAXPACKAGE; i++) {
+ if (mPackages[i] != NULL) {
+ free_package(mPackages[i]);
+ }
+ if (other.mPackages[i] != NULL) {
+ mPackages[i] = copy_package(other.mPackages[i]);
+ } else {
+ mPackages[i] = NULL;
+ }
+ }
+ } else {
+ // @todo: need to really implement this, not just copy
+ // the system package (which is still wrong because it isn't
+ // fixing up resource references).
+ for (size_t i=0; i<Res_MAXPACKAGE; i++) {
+ if (mPackages[i] != NULL) {
+ free_package(mPackages[i]);
+ }
+ if (i == 0 && other.mPackages[i] != NULL) {
+ mPackages[i] = copy_package(other.mPackages[i]);
+ } else {
+ mPackages[i] = NULL;
+ }
+ }
+ }
+
+ //ALOGI("Final theme:");
+ //dumpToLog();
+
+ return NO_ERROR;
+}
+
+ssize_t ResTable::Theme::getAttribute(uint32_t resID, Res_value* outValue,
+ uint32_t* outTypeSpecFlags) const
+{
+ int cnt = 20;
+
+ if (outTypeSpecFlags != NULL) *outTypeSpecFlags = 0;
+
+ do {
+ const ssize_t p = mTable.getResourcePackageIndex(resID);
+ const uint32_t t = Res_GETTYPE(resID);
+ const uint32_t e = Res_GETENTRY(resID);
+
+ TABLE_THEME(ALOGI("Looking up attr 0x%08x in theme %p", resID, this));
+
+ if (p >= 0) {
+ const package_info* const pi = mPackages[p];
+ TABLE_THEME(ALOGI("Found package: %p", pi));
+ if (pi != NULL) {
+ TABLE_THEME(ALOGI("Desired type index is %ld in avail %d", t, pi->numTypes));
+ if (t < pi->numTypes) {
+ const type_info& ti = pi->types[t];
+ TABLE_THEME(ALOGI("Desired entry index is %ld in avail %d", e, ti.numEntries));
+ if (e < ti.numEntries) {
+ const theme_entry& te = ti.entries[e];
+ if (outTypeSpecFlags != NULL) {
+ *outTypeSpecFlags |= te.typeSpecFlags;
+ }
+ TABLE_THEME(ALOGI("Theme value: type=0x%x, data=0x%08x",
+ te.value.dataType, te.value.data));
+ const uint8_t type = te.value.dataType;
+ if (type == Res_value::TYPE_ATTRIBUTE) {
+ if (cnt > 0) {
+ cnt--;
+ resID = te.value.data;
+ continue;
+ }
+ ALOGW("Too many attribute references, stopped at: 0x%08x\n", resID);
+ return BAD_INDEX;
+ } else if (type != Res_value::TYPE_NULL) {
+ *outValue = te.value;
+ return te.stringBlock;
+ }
+ return BAD_INDEX;
+ }
+ }
+ }
+ }
+ break;
+
+ } while (true);
+
+ return BAD_INDEX;
+}
+
+ssize_t ResTable::Theme::resolveAttributeReference(Res_value* inOutValue,
+ ssize_t blockIndex, uint32_t* outLastRef,
+ uint32_t* inoutTypeSpecFlags, ResTable_config* inoutConfig) const
+{
+ //printf("Resolving type=0x%x\n", inOutValue->dataType);
+ if (inOutValue->dataType == Res_value::TYPE_ATTRIBUTE) {
+ uint32_t newTypeSpecFlags;
+ blockIndex = getAttribute(inOutValue->data, inOutValue, &newTypeSpecFlags);
+ TABLE_THEME(ALOGI("Resolving attr reference: blockIndex=%d, type=0x%x, data=%p\n",
+ (int)blockIndex, (int)inOutValue->dataType, (void*)inOutValue->data));
+ if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newTypeSpecFlags;
+ //printf("Retrieved attribute new type=0x%x\n", inOutValue->dataType);
+ if (blockIndex < 0) {
+ return blockIndex;
+ }
+ }
+ return mTable.resolveReference(inOutValue, blockIndex, outLastRef,
+ inoutTypeSpecFlags, inoutConfig);
+}
+
+void ResTable::Theme::dumpToLog() const
+{
+ ALOGI("Theme %p:\n", this);
+ for (size_t i=0; i<Res_MAXPACKAGE; i++) {
+ package_info* pi = mPackages[i];
+ if (pi == NULL) continue;
+
+ ALOGI(" Package #0x%02x:\n", (int)(i+1));
+ for (size_t j=0; j<pi->numTypes; j++) {
+ type_info& ti = pi->types[j];
+ if (ti.numEntries == 0) continue;
+
+ ALOGI(" Type #0x%02x:\n", (int)(j+1));
+ for (size_t k=0; k<ti.numEntries; k++) {
+ theme_entry& te = ti.entries[k];
+ if (te.value.dataType == Res_value::TYPE_NULL) continue;
+ ALOGI(" 0x%08x: t=0x%x, d=0x%08x (block=%d)\n",
+ (int)Res_MAKEID(i, j, k),
+ te.value.dataType, (int)te.value.data, (int)te.stringBlock);
+ }
+ }
+ }
+}
+
+ResTable::ResTable()
+ : mError(NO_INIT)
+{
+ memset(&mParams, 0, sizeof(mParams));
+ memset(mPackageMap, 0, sizeof(mPackageMap));
+ //ALOGI("Creating ResTable %p\n", this);
+}
+
+ResTable::ResTable(const void* data, size_t size, void* cookie, bool copyData)
+ : mError(NO_INIT)
+{
+ memset(&mParams, 0, sizeof(mParams));
+ memset(mPackageMap, 0, sizeof(mPackageMap));
+ add(data, size, cookie, copyData);
+ LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table");
+ //ALOGI("Creating ResTable %p\n", this);
+}
+
+ResTable::~ResTable()
+{
+ //ALOGI("Destroying ResTable in %p\n", this);
+ uninit();
+}
+
+inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const
+{
+ return ((ssize_t)mPackageMap[Res_GETPACKAGE(resID)+1])-1;
+}
+
+status_t ResTable::add(const void* data, size_t size, void* cookie, bool copyData,
+ const void* idmap)
+{
+ return add(data, size, cookie, NULL, copyData, reinterpret_cast<const Asset*>(idmap));
+}
+
+status_t ResTable::add(Asset* asset, void* cookie, bool copyData, const void* idmap)
+{
+ const void* data = asset->getBuffer(true);
+ if (data == NULL) {
+ ALOGW("Unable to get buffer of resource asset file");
+ return UNKNOWN_ERROR;
+ }
+ size_t size = (size_t)asset->getLength();
+ return add(data, size, cookie, asset, copyData, reinterpret_cast<const Asset*>(idmap));
+}
+
+status_t ResTable::add(ResTable* src)
+{
+ mError = src->mError;
+
+ for (size_t i=0; i<src->mHeaders.size(); i++) {
+ mHeaders.add(src->mHeaders[i]);
+ }
+
+ for (size_t i=0; i<src->mPackageGroups.size(); i++) {
+ PackageGroup* srcPg = src->mPackageGroups[i];
+ PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id);
+ for (size_t j=0; j<srcPg->packages.size(); j++) {
+ pg->packages.add(srcPg->packages[j]);
+ }
+ pg->basePackage = srcPg->basePackage;
+ pg->typeCount = srcPg->typeCount;
+ mPackageGroups.add(pg);
+ }
+
+ memcpy(mPackageMap, src->mPackageMap, sizeof(mPackageMap));
+
+ return mError;
+}
+
+status_t ResTable::add(const void* data, size_t size, void* cookie,
+ Asset* asset, bool copyData, const Asset* idmap)
+{
+ if (!data) return NO_ERROR;
+ Header* header = new Header(this);
+ header->index = mHeaders.size();
+ header->cookie = cookie;
+ if (idmap != NULL) {
+ const size_t idmap_size = idmap->getLength();
+ const void* idmap_data = const_cast<Asset*>(idmap)->getBuffer(true);
+ header->resourceIDMap = (uint32_t*)malloc(idmap_size);
+ if (header->resourceIDMap == NULL) {
+ delete header;
+ return (mError = NO_MEMORY);
+ }
+ memcpy((void*)header->resourceIDMap, idmap_data, idmap_size);
+ header->resourceIDMapSize = idmap_size;
+ }
+ mHeaders.add(header);
+
+ const bool notDeviceEndian = htods(0xf0) != 0xf0;
+
+ LOAD_TABLE_NOISY(
+ ALOGV("Adding resources to ResTable: data=%p, size=0x%x, cookie=%p, asset=%p, copy=%d "
+ "idmap=%p\n", data, size, cookie, asset, copyData, idmap));
+
+ if (copyData || notDeviceEndian) {
+ header->ownedData = malloc(size);
+ if (header->ownedData == NULL) {
+ return (mError=NO_MEMORY);
+ }
+ memcpy(header->ownedData, data, size);
+ data = header->ownedData;
+ }
+
+ header->header = (const ResTable_header*)data;
+ header->size = dtohl(header->header->header.size);
+ //ALOGI("Got size 0x%x, again size 0x%x, raw size 0x%x\n", header->size,
+ // dtohl(header->header->header.size), header->header->header.size);
+ LOAD_TABLE_NOISY(ALOGV("Loading ResTable @%p:\n", header->header));
+ LOAD_TABLE_NOISY(printHexData(2, header->header, header->size < 256 ? header->size : 256,
+ 16, 16, 0, false, printToLogFunc));
+ if (dtohs(header->header->header.headerSize) > header->size
+ || header->size > size) {
+ ALOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n",
+ (int)dtohs(header->header->header.headerSize),
+ (int)header->size, (int)size);
+ return (mError=BAD_TYPE);
+ }
+ if (((dtohs(header->header->header.headerSize)|header->size)&0x3) != 0) {
+ ALOGW("Bad resource table: header size 0x%x or total size 0x%x is not on an integer boundary\n",
+ (int)dtohs(header->header->header.headerSize),
+ (int)header->size);
+ return (mError=BAD_TYPE);
+ }
+ header->dataEnd = ((const uint8_t*)header->header) + header->size;
+
+ // Iterate through all chunks.
+ size_t curPackage = 0;
+
+ const ResChunk_header* chunk =
+ (const ResChunk_header*)(((const uint8_t*)header->header)
+ + dtohs(header->header->header.headerSize));
+ while (((const uint8_t*)chunk) <= (header->dataEnd-sizeof(ResChunk_header)) &&
+ ((const uint8_t*)chunk) <= (header->dataEnd-dtohl(chunk->size))) {
+ status_t err = validate_chunk(chunk, sizeof(ResChunk_header), header->dataEnd, "ResTable");
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+ TABLE_NOISY(ALOGV("Chunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%p\n",
+ dtohs(chunk->type), dtohs(chunk->headerSize), dtohl(chunk->size),
+ (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header))));
+ const size_t csize = dtohl(chunk->size);
+ const uint16_t ctype = dtohs(chunk->type);
+ if (ctype == RES_STRING_POOL_TYPE) {
+ if (header->values.getError() != NO_ERROR) {
+ // Only use the first string chunk; ignore any others that
+ // may appear.
+ status_t err = header->values.setTo(chunk, csize);
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+ } else {
+ ALOGW("Multiple string chunks found in resource table.");
+ }
+ } else if (ctype == RES_TABLE_PACKAGE_TYPE) {
+ if (curPackage >= dtohl(header->header->packageCount)) {
+ ALOGW("More package chunks were found than the %d declared in the header.",
+ dtohl(header->header->packageCount));
+ return (mError=BAD_TYPE);
+ }
+ uint32_t idmap_id = 0;
+ if (idmap != NULL) {
+ uint32_t tmp;
+ if (getIdmapPackageId(header->resourceIDMap,
+ header->resourceIDMapSize,
+ &tmp) == NO_ERROR) {
+ idmap_id = tmp;
+ }
+ }
+ if (parsePackage((ResTable_package*)chunk, header, idmap_id) != NO_ERROR) {
+ return mError;
+ }
+ curPackage++;
+ } else {
+ ALOGW("Unknown chunk type %p in table at %p.\n",
+ (void*)(int)(ctype),
+ (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)));
+ }
+ chunk = (const ResChunk_header*)
+ (((const uint8_t*)chunk) + csize);
+ }
+
+ if (curPackage < dtohl(header->header->packageCount)) {
+ ALOGW("Fewer package chunks (%d) were found than the %d declared in the header.",
+ (int)curPackage, dtohl(header->header->packageCount));
+ return (mError=BAD_TYPE);
+ }
+ mError = header->values.getError();
+ if (mError != NO_ERROR) {
+ ALOGW("No string values found in resource table!");
+ }
+
+ TABLE_NOISY(ALOGV("Returning from add with mError=%d\n", mError));
+ return mError;
+}
+
+status_t ResTable::getError() const
+{
+ return mError;
+}
+
+void ResTable::uninit()
+{
+ mError = NO_INIT;
+ size_t N = mPackageGroups.size();
+ for (size_t i=0; i<N; i++) {
+ PackageGroup* g = mPackageGroups[i];
+ delete g;
+ }
+ N = mHeaders.size();
+ for (size_t i=0; i<N; i++) {
+ Header* header = mHeaders[i];
+ if (header->owner == this) {
+ if (header->ownedData) {
+ free(header->ownedData);
+ }
+ delete header;
+ }
+ }
+
+ mPackageGroups.clear();
+ mHeaders.clear();
+}
+
+bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const
+{
+ if (mError != NO_ERROR) {
+ return false;
+ }
+
+ const ssize_t p = getResourcePackageIndex(resID);
+ const int t = Res_GETTYPE(resID);
+ const int e = Res_GETENTRY(resID);
+
+ if (p < 0) {
+ if (Res_GETPACKAGE(resID)+1 == 0) {
+ ALOGW("No package identifier when getting name for resource number 0x%08x", resID);
+ } else {
+ ALOGW("No known package when getting name for resource number 0x%08x", resID);
+ }
+ return false;
+ }
+ if (t < 0) {
+ ALOGW("No type identifier when getting name for resource number 0x%08x", resID);
+ return false;
+ }
+
+ const PackageGroup* const grp = mPackageGroups[p];
+ if (grp == NULL) {
+ ALOGW("Bad identifier when getting name for resource number 0x%08x", resID);
+ return false;
+ }
+ if (grp->packages.size() > 0) {
+ const Package* const package = grp->packages[0];
+
+ const ResTable_type* type;
+ const ResTable_entry* entry;
+ ssize_t offset = getEntry(package, t, e, NULL, &type, &entry, NULL);
+ if (offset <= 0) {
+ return false;
+ }
+
+ outName->package = grp->name.string();
+ outName->packageLen = grp->name.size();
+ if (allowUtf8) {
+ outName->type8 = grp->basePackage->typeStrings.string8At(t, &outName->typeLen);
+ outName->name8 = grp->basePackage->keyStrings.string8At(
+ dtohl(entry->key.index), &outName->nameLen);
+ } else {
+ outName->type8 = NULL;
+ outName->name8 = NULL;
+ }
+ if (outName->type8 == NULL) {
+ outName->type = grp->basePackage->typeStrings.stringAt(t, &outName->typeLen);
+ // If we have a bad index for some reason, we should abort.
+ if (outName->type == NULL) {
+ return false;
+ }
+ }
+ if (outName->name8 == NULL) {
+ outName->name = grp->basePackage->keyStrings.stringAt(
+ dtohl(entry->key.index), &outName->nameLen);
+ // If we have a bad index for some reason, we should abort.
+ if (outName->name == NULL) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density,
+ uint32_t* outSpecFlags, ResTable_config* outConfig) const
+{
+ if (mError != NO_ERROR) {
+ return mError;
+ }
+
+ const ssize_t p = getResourcePackageIndex(resID);
+ const int t = Res_GETTYPE(resID);
+ const int e = Res_GETENTRY(resID);
+
+ if (p < 0) {
+ if (Res_GETPACKAGE(resID)+1 == 0) {
+ ALOGW("No package identifier when getting value for resource number 0x%08x", resID);
+ } else {
+ ALOGW("No known package when getting value for resource number 0x%08x", resID);
+ }
+ return BAD_INDEX;
+ }
+ if (t < 0) {
+ ALOGW("No type identifier when getting value for resource number 0x%08x", resID);
+ return BAD_INDEX;
+ }
+
+ const Res_value* bestValue = NULL;
+ const Package* bestPackage = NULL;
+ ResTable_config bestItem;
+ memset(&bestItem, 0, sizeof(bestItem)); // make the compiler shut up
+
+ if (outSpecFlags != NULL) *outSpecFlags = 0;
+
+ // Look through all resource packages, starting with the most
+ // recently added.
+ const PackageGroup* const grp = mPackageGroups[p];
+ if (grp == NULL) {
+ ALOGW("Bad identifier when getting value for resource number 0x%08x", resID);
+ return BAD_INDEX;
+ }
+
+ // Allow overriding density
+ const ResTable_config* desiredConfig = &mParams;
+ ResTable_config* overrideConfig = NULL;
+ if (density > 0) {
+ overrideConfig = (ResTable_config*) malloc(sizeof(ResTable_config));
+ if (overrideConfig == NULL) {
+ ALOGE("Couldn't malloc ResTable_config for overrides: %s", strerror(errno));
+ return BAD_INDEX;
+ }
+ memcpy(overrideConfig, &mParams, sizeof(ResTable_config));
+ overrideConfig->density = density;
+ desiredConfig = overrideConfig;
+ }
+
+ ssize_t rc = BAD_VALUE;
+ size_t ip = grp->packages.size();
+ while (ip > 0) {
+ ip--;
+ int T = t;
+ int E = e;
+
+ const Package* const package = grp->packages[ip];
+ if (package->header->resourceIDMap) {
+ uint32_t overlayResID = 0x0;
+ status_t retval = idmapLookup(package->header->resourceIDMap,
+ package->header->resourceIDMapSize,
+ resID, &overlayResID);
+ if (retval == NO_ERROR && overlayResID != 0x0) {
+ // for this loop iteration, this is the type and entry we really want
+ ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
+ T = Res_GETTYPE(overlayResID);
+ E = Res_GETENTRY(overlayResID);
+ } else {
+ // resource not present in overlay package, continue with the next package
+ continue;
+ }
+ }
+
+ const ResTable_type* type;
+ const ResTable_entry* entry;
+ const Type* typeClass;
+ ssize_t offset = getEntry(package, T, E, desiredConfig, &type, &entry, &typeClass);
+ if (offset <= 0) {
+ // No {entry, appropriate config} pair found in package. If this
+ // package is an overlay package (ip != 0), this simply means the
+ // overlay package did not specify a default.
+ // Non-overlay packages are still required to provide a default.
+ if (offset < 0 && ip == 0) {
+ ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) in package %zd (error %d)\n",
+ resID, T, E, ip, (int)offset);
+ rc = offset;
+ goto out;
+ }
+ continue;
+ }
+
+ if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) != 0) {
+ if (!mayBeBag) {
+ ALOGW("Requesting resource %p failed because it is complex\n",
+ (void*)resID);
+ }
+ continue;
+ }
+
+ TABLE_NOISY(aout << "Resource type data: "
+ << HexDump(type, dtohl(type->header.size)) << endl);
+
+ if ((size_t)offset > (dtohl(type->header.size)-sizeof(Res_value))) {
+ ALOGW("ResTable_item at %d is beyond type chunk data %d",
+ (int)offset, dtohl(type->header.size));
+ rc = BAD_TYPE;
+ goto out;
+ }
+
+ const Res_value* item =
+ (const Res_value*)(((const uint8_t*)type) + offset);
+ ResTable_config thisConfig;
+ thisConfig.copyFromDtoH(type->config);
+
+ if (outSpecFlags != NULL) {
+ if (typeClass->typeSpecFlags != NULL) {
+ *outSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
+ } else {
+ *outSpecFlags = -1;
+ }
+ }
+
+ if (bestPackage != NULL &&
+ (bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) {
+ // Discard thisConfig not only if bestItem is more specific, but also if the two configs
+ // are identical (diff == 0), or overlay packages will not take effect.
+ continue;
+ }
+
+ bestItem = thisConfig;
+ bestValue = item;
+ bestPackage = package;
+ }
+
+ TABLE_NOISY(printf("Found result: package %p\n", bestPackage));
+
+ if (bestValue) {
+ outValue->size = dtohs(bestValue->size);
+ outValue->res0 = bestValue->res0;
+ outValue->dataType = bestValue->dataType;
+ outValue->data = dtohl(bestValue->data);
+ if (outConfig != NULL) {
+ *outConfig = bestItem;
+ }
+ TABLE_NOISY(size_t len;
+ printf("Found value: pkg=%d, type=%d, str=%s, int=%d\n",
+ bestPackage->header->index,
+ outValue->dataType,
+ outValue->dataType == bestValue->TYPE_STRING
+ ? String8(bestPackage->header->values.stringAt(
+ outValue->data, &len)).string()
+ : "",
+ outValue->data));
+ rc = bestPackage->header->index;
+ goto out;
+ }
+
+out:
+ if (overrideConfig != NULL) {
+ free(overrideConfig);
+ }
+
+ return rc;
+}
+
+ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
+ uint32_t* outLastRef, uint32_t* inoutTypeSpecFlags,
+ ResTable_config* outConfig) const
+{
+ int count=0;
+ while (blockIndex >= 0 && value->dataType == value->TYPE_REFERENCE
+ && value->data != 0 && count < 20) {
+ if (outLastRef) *outLastRef = value->data;
+ uint32_t lastRef = value->data;
+ uint32_t newFlags = 0;
+ const ssize_t newIndex = getResource(value->data, value, true, 0, &newFlags,
+ outConfig);
+ if (newIndex == BAD_INDEX) {
+ return BAD_INDEX;
+ }
+ TABLE_THEME(ALOGI("Resolving reference %p: newIndex=%d, type=0x%x, data=%p\n",
+ (void*)lastRef, (int)newIndex, (int)value->dataType, (void*)value->data));
+ //printf("Getting reference 0x%08x: newIndex=%d\n", value->data, newIndex);
+ if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newFlags;
+ if (newIndex < 0) {
+ // This can fail if the resource being referenced is a style...
+ // in this case, just return the reference, and expect the
+ // caller to deal with.
+ return blockIndex;
+ }
+ blockIndex = newIndex;
+ count++;
+ }
+ return blockIndex;
+}
+
+const char16_t* ResTable::valueToString(
+ const Res_value* value, size_t stringBlock,
+ char16_t tmpBuffer[TMP_BUFFER_SIZE], size_t* outLen)
+{
+ if (!value) {
+ return NULL;
+ }
+ if (value->dataType == value->TYPE_STRING) {
+ return getTableStringBlock(stringBlock)->stringAt(value->data, outLen);
+ }
+ // XXX do int to string conversions.
+ return NULL;
+}
+
+ssize_t ResTable::lockBag(uint32_t resID, const bag_entry** outBag) const
+{
+ mLock.lock();
+ ssize_t err = getBagLocked(resID, outBag);
+ if (err < NO_ERROR) {
+ //printf("*** get failed! unlocking\n");
+ mLock.unlock();
+ }
+ return err;
+}
+
+void ResTable::unlockBag(const bag_entry* bag) const
+{
+ //printf("<<< unlockBag %p\n", this);
+ mLock.unlock();
+}
+
+void ResTable::lock() const
+{
+ mLock.lock();
+}
+
+void ResTable::unlock() const
+{
+ mLock.unlock();
+}
+
+ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
+ uint32_t* outTypeSpecFlags) const
+{
+ if (mError != NO_ERROR) {
+ return mError;
+ }
+
+ const ssize_t p = getResourcePackageIndex(resID);
+ const int t = Res_GETTYPE(resID);
+ const int e = Res_GETENTRY(resID);
+
+ if (p < 0) {
+ ALOGW("Invalid package identifier when getting bag for resource number 0x%08x", resID);
+ return BAD_INDEX;
+ }
+ if (t < 0) {
+ ALOGW("No type identifier when getting bag for resource number 0x%08x", resID);
+ return BAD_INDEX;
+ }
+
+ //printf("Get bag: id=0x%08x, p=%d, t=%d\n", resID, p, t);
+ PackageGroup* const grp = mPackageGroups[p];
+ if (grp == NULL) {
+ ALOGW("Bad identifier when getting bag for resource number 0x%08x", resID);
+ return false;
+ }
+
+ if (t >= (int)grp->typeCount) {
+ ALOGW("Type identifier 0x%x is larger than type count 0x%x",
+ t+1, (int)grp->typeCount);
+ return BAD_INDEX;
+ }
+
+ const Package* const basePackage = grp->packages[0];
+
+ const Type* const typeConfigs = basePackage->getType(t);
+
+ const size_t NENTRY = typeConfigs->entryCount;
+ if (e >= (int)NENTRY) {
+ ALOGW("Entry identifier 0x%x is larger than entry count 0x%x",
+ e, (int)typeConfigs->entryCount);
+ return BAD_INDEX;
+ }
+
+ // First see if we've already computed this bag...
+ if (grp->bags) {
+ bag_set** typeSet = grp->bags[t];
+ if (typeSet) {
+ bag_set* set = typeSet[e];
+ if (set) {
+ if (set != (bag_set*)0xFFFFFFFF) {
+ if (outTypeSpecFlags != NULL) {
+ *outTypeSpecFlags = set->typeSpecFlags;
+ }
+ *outBag = (bag_entry*)(set+1);
+ //ALOGI("Found existing bag for: %p\n", (void*)resID);
+ return set->numAttrs;
+ }
+ ALOGW("Attempt to retrieve bag 0x%08x which is invalid or in a cycle.",
+ resID);
+ return BAD_INDEX;
+ }
+ }
+ }
+
+ // Bag not found, we need to compute it!
+ if (!grp->bags) {
+ grp->bags = (bag_set***)calloc(grp->typeCount, sizeof(bag_set*));
+ if (!grp->bags) return NO_MEMORY;
+ }
+
+ bag_set** typeSet = grp->bags[t];
+ if (!typeSet) {
+ typeSet = (bag_set**)calloc(NENTRY, sizeof(bag_set*));
+ if (!typeSet) return NO_MEMORY;
+ grp->bags[t] = typeSet;
+ }
+
+ // Mark that we are currently working on this one.
+ typeSet[e] = (bag_set*)0xFFFFFFFF;
+
+ // This is what we are building.
+ bag_set* set = NULL;
+
+ TABLE_NOISY(ALOGI("Building bag: %p\n", (void*)resID));
+
+ ResTable_config bestConfig;
+ memset(&bestConfig, 0, sizeof(bestConfig));
+
+ // Now collect all bag attributes from all packages.
+ size_t ip = grp->packages.size();
+ while (ip > 0) {
+ ip--;
+ int T = t;
+ int E = e;
+
+ const Package* const package = grp->packages[ip];
+ if (package->header->resourceIDMap) {
+ uint32_t overlayResID = 0x0;
+ status_t retval = idmapLookup(package->header->resourceIDMap,
+ package->header->resourceIDMapSize,
+ resID, &overlayResID);
+ if (retval == NO_ERROR && overlayResID != 0x0) {
+ // for this loop iteration, this is the type and entry we really want
+ ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
+ T = Res_GETTYPE(overlayResID);
+ E = Res_GETENTRY(overlayResID);
+ } else {
+ // resource not present in overlay package, continue with the next package
+ continue;
+ }
+ }
+
+ const ResTable_type* type;
+ const ResTable_entry* entry;
+ const Type* typeClass;
+ ALOGV("Getting entry pkg=%p, t=%d, e=%d\n", package, T, E);
+ ssize_t offset = getEntry(package, T, E, &mParams, &type, &entry, &typeClass);
+ ALOGV("Resulting offset=%d\n", offset);
+ if (offset <= 0) {
+ // No {entry, appropriate config} pair found in package. If this
+ // package is an overlay package (ip != 0), this simply means the
+ // overlay package did not specify a default.
+ // Non-overlay packages are still required to provide a default.
+ if (offset < 0 && ip == 0) {
+ if (set) free(set);
+ return offset;
+ }
+ continue;
+ }
+
+ if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) == 0) {
+ ALOGW("Skipping entry %p in package table %d because it is not complex!\n",
+ (void*)resID, (int)ip);
+ continue;
+ }
+
+ if (set != NULL && !type->config.isBetterThan(bestConfig, NULL)) {
+ continue;
+ }
+ bestConfig = type->config;
+ if (set) {
+ free(set);
+ set = NULL;
+ }
+
+ const uint16_t entrySize = dtohs(entry->size);
+ const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
+ ? dtohl(((const ResTable_map_entry*)entry)->parent.ident) : 0;
+ const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
+ ? dtohl(((const ResTable_map_entry*)entry)->count) : 0;
+
+ size_t N = count;
+
+ TABLE_NOISY(ALOGI("Found map: size=%p parent=%p count=%d\n",
+ entrySize, parent, count));
+
+ // If this map inherits from another, we need to start
+ // with its parent's values. Otherwise start out empty.
+ TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n",
+ entrySize, parent));
+ if (parent) {
+ const bag_entry* parentBag;
+ uint32_t parentTypeSpecFlags = 0;
+ const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags);
+ const size_t NT = ((NP >= 0) ? NP : 0) + N;
+ set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
+ if (set == NULL) {
+ return NO_MEMORY;
+ }
+ if (NP > 0) {
+ memcpy(set+1, parentBag, NP*sizeof(bag_entry));
+ set->numAttrs = NP;
+ TABLE_NOISY(ALOGI("Initialized new bag with %d inherited attributes.\n", NP));
+ } else {
+ TABLE_NOISY(ALOGI("Initialized new bag with no inherited attributes.\n"));
+ set->numAttrs = 0;
+ }
+ set->availAttrs = NT;
+ set->typeSpecFlags = parentTypeSpecFlags;
+ } else {
+ set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N);
+ if (set == NULL) {
+ return NO_MEMORY;
+ }
+ set->numAttrs = 0;
+ set->availAttrs = N;
+ set->typeSpecFlags = 0;
+ }
+
+ if (typeClass->typeSpecFlags != NULL) {
+ set->typeSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
+ } else {
+ set->typeSpecFlags = -1;
+ }
+
+ // Now merge in the new attributes...
+ ssize_t curOff = offset;
+ const ResTable_map* map;
+ bag_entry* entries = (bag_entry*)(set+1);
+ size_t curEntry = 0;
+ uint32_t pos = 0;
+ TABLE_NOISY(ALOGI("Starting with set %p, entries=%p, avail=%d\n",
+ set, entries, set->availAttrs));
+ while (pos < count) {
+ TABLE_NOISY(printf("Now at %p\n", (void*)curOff));
+
+ if ((size_t)curOff > (dtohl(type->header.size)-sizeof(ResTable_map))) {
+ ALOGW("ResTable_map at %d is beyond type chunk data %d",
+ (int)curOff, dtohl(type->header.size));
+ return BAD_TYPE;
+ }
+ map = (const ResTable_map*)(((const uint8_t*)type) + curOff);
+ N++;
+
+ const uint32_t newName = htodl(map->name.ident);
+ bool isInside;
+ uint32_t oldName = 0;
+ while ((isInside=(curEntry < set->numAttrs))
+ && (oldName=entries[curEntry].map.name.ident) < newName) {
+ TABLE_NOISY(printf("#%d: Keeping existing attribute: 0x%08x\n",
+ curEntry, entries[curEntry].map.name.ident));
+ curEntry++;
+ }
+
+ if ((!isInside) || oldName != newName) {
+ // This is a new attribute... figure out what to do with it.
+ if (set->numAttrs >= set->availAttrs) {
+ // Need to alloc more memory...
+ const size_t newAvail = set->availAttrs+N;
+ set = (bag_set*)realloc(set,
+ sizeof(bag_set)
+ + sizeof(bag_entry)*newAvail);
+ if (set == NULL) {
+ return NO_MEMORY;
+ }
+ set->availAttrs = newAvail;
+ entries = (bag_entry*)(set+1);
+ TABLE_NOISY(printf("Reallocated set %p, entries=%p, avail=%d\n",
+ set, entries, set->availAttrs));
+ }
+ if (isInside) {
+ // Going in the middle, need to make space.
+ memmove(entries+curEntry+1, entries+curEntry,
+ sizeof(bag_entry)*(set->numAttrs-curEntry));
+ set->numAttrs++;
+ }
+ TABLE_NOISY(printf("#%d: Inserting new attribute: 0x%08x\n",
+ curEntry, newName));
+ } else {
+ TABLE_NOISY(printf("#%d: Replacing existing attribute: 0x%08x\n",
+ curEntry, oldName));
+ }
+
+ bag_entry* cur = entries+curEntry;
+
+ cur->stringBlock = package->header->index;
+ cur->map.name.ident = newName;
+ cur->map.value.copyFrom_dtoh(map->value);
+ TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n",
+ curEntry, cur, cur->stringBlock, cur->map.name.ident,
+ cur->map.value.dataType, cur->map.value.data));
+
+ // On to the next!
+ curEntry++;
+ pos++;
+ const size_t size = dtohs(map->value.size);
+ curOff += size + sizeof(*map)-sizeof(map->value);
+ };
+ if (curEntry > set->numAttrs) {
+ set->numAttrs = curEntry;
+ }
+ }
+
+ // And this is it...
+ typeSet[e] = set;
+ if (set) {
+ if (outTypeSpecFlags != NULL) {
+ *outTypeSpecFlags = set->typeSpecFlags;
+ }
+ *outBag = (bag_entry*)(set+1);
+ TABLE_NOISY(ALOGI("Returning %d attrs\n", set->numAttrs));
+ return set->numAttrs;
+ }
+ return BAD_INDEX;
+}
+
+void ResTable::setParameters(const ResTable_config* params)
+{
+ mLock.lock();
+ TABLE_GETENTRY(ALOGI("Setting parameters: %s\n", params->toString().string()));
+ mParams = *params;
+ for (size_t i=0; i<mPackageGroups.size(); i++) {
+ TABLE_NOISY(ALOGI("CLEARING BAGS FOR GROUP %d!", i));
+ mPackageGroups[i]->clearBagCache();
+ }
+ mLock.unlock();
+}
+
+void ResTable::getParameters(ResTable_config* params) const
+{
+ mLock.lock();
+ *params = mParams;
+ mLock.unlock();
+}
+
+struct id_name_map {
+ uint32_t id;
+ size_t len;
+ char16_t name[6];
+};
+
+const static id_name_map ID_NAMES[] = {
+ { ResTable_map::ATTR_TYPE, 5, { '^', 't', 'y', 'p', 'e' } },
+ { ResTable_map::ATTR_L10N, 5, { '^', 'l', '1', '0', 'n' } },
+ { ResTable_map::ATTR_MIN, 4, { '^', 'm', 'i', 'n' } },
+ { ResTable_map::ATTR_MAX, 4, { '^', 'm', 'a', 'x' } },
+ { ResTable_map::ATTR_OTHER, 6, { '^', 'o', 't', 'h', 'e', 'r' } },
+ { ResTable_map::ATTR_ZERO, 5, { '^', 'z', 'e', 'r', 'o' } },
+ { ResTable_map::ATTR_ONE, 4, { '^', 'o', 'n', 'e' } },
+ { ResTable_map::ATTR_TWO, 4, { '^', 't', 'w', 'o' } },
+ { ResTable_map::ATTR_FEW, 4, { '^', 'f', 'e', 'w' } },
+ { ResTable_map::ATTR_MANY, 5, { '^', 'm', 'a', 'n', 'y' } },
+};
+
+uint32_t ResTable::identifierForName(const char16_t* name, size_t nameLen,
+ const char16_t* type, size_t typeLen,
+ const char16_t* package,
+ size_t packageLen,
+ uint32_t* outTypeSpecFlags) const
+{
+ TABLE_SUPER_NOISY(printf("Identifier for name: error=%d\n", mError));
+
+ // Check for internal resource identifier as the very first thing, so
+ // that we will always find them even when there are no resources.
+ if (name[0] == '^') {
+ const int N = (sizeof(ID_NAMES)/sizeof(ID_NAMES[0]));
+ size_t len;
+ for (int i=0; i<N; i++) {
+ const id_name_map* m = ID_NAMES + i;
+ len = m->len;
+ if (len != nameLen) {
+ continue;
+ }
+ for (size_t j=1; j<len; j++) {
+ if (m->name[j] != name[j]) {
+ goto nope;
+ }
+ }
+ if (outTypeSpecFlags) {
+ *outTypeSpecFlags = ResTable_typeSpec::SPEC_PUBLIC;
+ }
+ return m->id;
+nope:
+ ;
+ }
+ if (nameLen > 7) {
+ if (name[1] == 'i' && name[2] == 'n'
+ && name[3] == 'd' && name[4] == 'e' && name[5] == 'x'
+ && name[6] == '_') {
+ int index = atoi(String8(name + 7, nameLen - 7).string());
+ if (Res_CHECKID(index)) {
+ ALOGW("Array resource index: %d is too large.",
+ index);
+ return 0;
+ }
+ if (outTypeSpecFlags) {
+ *outTypeSpecFlags = ResTable_typeSpec::SPEC_PUBLIC;
+ }
+ return Res_MAKEARRAY(index);
+ }
+ }
+ return 0;
+ }
+
+ if (mError != NO_ERROR) {
+ return 0;
+ }
+
+ bool fakePublic = false;
+
+ // Figure out the package and type we are looking in...
+
+ const char16_t* packageEnd = NULL;
+ const char16_t* typeEnd = NULL;
+ const char16_t* const nameEnd = name+nameLen;
+ const char16_t* p = name;
+ while (p < nameEnd) {
+ if (*p == ':') packageEnd = p;
+ else if (*p == '/') typeEnd = p;
+ p++;
+ }
+ if (*name == '@') {
+ name++;
+ if (*name == '*') {
+ fakePublic = true;
+ name++;
+ }
+ }
+ if (name >= nameEnd) {
+ return 0;
+ }
+
+ if (packageEnd) {
+ package = name;
+ packageLen = packageEnd-name;
+ name = packageEnd+1;
+ } else if (!package) {
+ return 0;
+ }
+
+ if (typeEnd) {
+ type = name;
+ typeLen = typeEnd-name;
+ name = typeEnd+1;
+ } else if (!type) {
+ return 0;
+ }
+
+ if (name >= nameEnd) {
+ return 0;
+ }
+ nameLen = nameEnd-name;
+
+ TABLE_NOISY(printf("Looking for identifier: type=%s, name=%s, package=%s\n",
+ String8(type, typeLen).string(),
+ String8(name, nameLen).string(),
+ String8(package, packageLen).string()));
+
+ const size_t NG = mPackageGroups.size();
+ for (size_t ig=0; ig<NG; ig++) {
+ const PackageGroup* group = mPackageGroups[ig];
+
+ if (strzcmp16(package, packageLen,
+ group->name.string(), group->name.size())) {
+ TABLE_NOISY(printf("Skipping package group: %s\n", String8(group->name).string()));
+ continue;
+ }
+
+ const ssize_t ti = group->basePackage->typeStrings.indexOfString(type, typeLen);
+ if (ti < 0) {
+ TABLE_NOISY(printf("Type not found in package %s\n", String8(group->name).string()));
+ continue;
+ }
+
+ const ssize_t ei = group->basePackage->keyStrings.indexOfString(name, nameLen);
+ if (ei < 0) {
+ TABLE_NOISY(printf("Name not found in package %s\n", String8(group->name).string()));
+ continue;
+ }
+
+ TABLE_NOISY(printf("Search indices: type=%d, name=%d\n", ti, ei));
+
+ const Type* const typeConfigs = group->packages[0]->getType(ti);
+ if (typeConfigs == NULL || typeConfigs->configs.size() <= 0) {
+ TABLE_NOISY(printf("Expected type structure not found in package %s for idnex %d\n",
+ String8(group->name).string(), ti));
+ }
+
+ size_t NTC = typeConfigs->configs.size();
+ for (size_t tci=0; tci<NTC; tci++) {
+ const ResTable_type* const ty = typeConfigs->configs[tci];
+ const uint32_t typeOffset = dtohl(ty->entriesStart);
+
+ const uint8_t* const end = ((const uint8_t*)ty) + dtohl(ty->header.size);
+ const uint32_t* const eindex = (const uint32_t*)
+ (((const uint8_t*)ty) + dtohs(ty->header.headerSize));
+
+ const size_t NE = dtohl(ty->entryCount);
+ for (size_t i=0; i<NE; i++) {
+ uint32_t offset = dtohl(eindex[i]);
+ if (offset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
+
+ offset += typeOffset;
+
+ if (offset > (dtohl(ty->header.size)-sizeof(ResTable_entry))) {
+ ALOGW("ResTable_entry at %d is beyond type chunk data %d",
+ offset, dtohl(ty->header.size));
+ return 0;
+ }
+ if ((offset&0x3) != 0) {
+ ALOGW("ResTable_entry at %d (pkg=%d type=%d ent=%d) is not on an integer boundary when looking for %s:%s/%s",
+ (int)offset, (int)group->id, (int)ti+1, (int)i,
+ String8(package, packageLen).string(),
+ String8(type, typeLen).string(),
+ String8(name, nameLen).string());
+ return 0;
+ }
+
+ const ResTable_entry* const entry = (const ResTable_entry*)
+ (((const uint8_t*)ty) + offset);
+ if (dtohs(entry->size) < sizeof(*entry)) {
+ ALOGW("ResTable_entry size %d is too small", dtohs(entry->size));
+ return BAD_TYPE;
+ }
+
+ TABLE_SUPER_NOISY(printf("Looking at entry #%d: want str %d, have %d\n",
+ i, ei, dtohl(entry->key.index)));
+ if (dtohl(entry->key.index) == (size_t)ei) {
+ if (outTypeSpecFlags) {
+ *outTypeSpecFlags = typeConfigs->typeSpecFlags[i];
+ if (fakePublic) {
+ *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
+ }
+ }
+ return Res_MAKEID(group->id-1, ti, i);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+bool ResTable::expandResourceRef(const uint16_t* refStr, size_t refLen,
+ String16* outPackage,
+ String16* outType,
+ String16* outName,
+ const String16* defType,
+ const String16* defPackage,
+ const char** outErrorMsg,
+ bool* outPublicOnly)
+{
+ const char16_t* packageEnd = NULL;
+ const char16_t* typeEnd = NULL;
+ const char16_t* p = refStr;
+ const char16_t* const end = p + refLen;
+ while (p < end) {
+ if (*p == ':') packageEnd = p;
+ else if (*p == '/') {
+ typeEnd = p;
+ break;
+ }
+ p++;
+ }
+ p = refStr;
+ if (*p == '@') p++;
+
+ if (outPublicOnly != NULL) {
+ *outPublicOnly = true;
+ }
+ if (*p == '*') {
+ p++;
+ if (outPublicOnly != NULL) {
+ *outPublicOnly = false;
+ }
+ }
+
+ if (packageEnd) {
+ *outPackage = String16(p, packageEnd-p);
+ p = packageEnd+1;
+ } else {
+ if (!defPackage) {
+ if (outErrorMsg) {
+ *outErrorMsg = "No resource package specified";
+ }
+ return false;
+ }
+ *outPackage = *defPackage;
+ }
+ if (typeEnd) {
+ *outType = String16(p, typeEnd-p);
+ p = typeEnd+1;
+ } else {
+ if (!defType) {
+ if (outErrorMsg) {
+ *outErrorMsg = "No resource type specified";
+ }
+ return false;
+ }
+ *outType = *defType;
+ }
+ *outName = String16(p, end-p);
+ if(**outPackage == 0) {
+ if(outErrorMsg) {
+ *outErrorMsg = "Resource package cannot be an empty string";
+ }
+ return false;
+ }
+ if(**outType == 0) {
+ if(outErrorMsg) {
+ *outErrorMsg = "Resource type cannot be an empty string";
+ }
+ return false;
+ }
+ if(**outName == 0) {
+ if(outErrorMsg) {
+ *outErrorMsg = "Resource id cannot be an empty string";
+ }
+ return false;
+ }
+ return true;
+}
+
+static uint32_t get_hex(char c, bool* outError)
+{
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ return c - 'a' + 0xa;
+ } else if (c >= 'A' && c <= 'F') {
+ return c - 'A' + 0xa;
+ }
+ *outError = true;
+ return 0;
+}
+
+struct unit_entry
+{
+ const char* name;
+ size_t len;
+ uint8_t type;
+ uint32_t unit;
+ float scale;
+};
+
+static const unit_entry unitNames[] = {
+ { "px", strlen("px"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_PX, 1.0f },
+ { "dip", strlen("dip"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_DIP, 1.0f },
+ { "dp", strlen("dp"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_DIP, 1.0f },
+ { "sp", strlen("sp"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_SP, 1.0f },
+ { "pt", strlen("pt"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_PT, 1.0f },
+ { "in", strlen("in"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_IN, 1.0f },
+ { "mm", strlen("mm"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_MM, 1.0f },
+ { "%", strlen("%"), Res_value::TYPE_FRACTION, Res_value::COMPLEX_UNIT_FRACTION, 1.0f/100 },
+ { "%p", strlen("%p"), Res_value::TYPE_FRACTION, Res_value::COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100 },
+ { NULL, 0, 0, 0, 0 }
+};
+
+static bool parse_unit(const char* str, Res_value* outValue,
+ float* outScale, const char** outEnd)
+{
+ const char* end = str;
+ while (*end != 0 && !isspace((unsigned char)*end)) {
+ end++;
+ }
+ const size_t len = end-str;
+
+ const char* realEnd = end;
+ while (*realEnd != 0 && isspace((unsigned char)*realEnd)) {
+ realEnd++;
+ }
+ if (*realEnd != 0) {
+ return false;
+ }
+
+ const unit_entry* cur = unitNames;
+ while (cur->name) {
+ if (len == cur->len && strncmp(cur->name, str, len) == 0) {
+ outValue->dataType = cur->type;
+ outValue->data = cur->unit << Res_value::COMPLEX_UNIT_SHIFT;
+ *outScale = cur->scale;
+ *outEnd = end;
+ //printf("Found unit %s for %s\n", cur->name, str);
+ return true;
+ }
+ cur++;
+ }
+
+ return false;
+}
+
+
+bool ResTable::stringToInt(const char16_t* s, size_t len, Res_value* outValue)
+{
+ while (len > 0 && isspace16(*s)) {
+ s++;
+ len--;
+ }
+
+ if (len <= 0) {
+ return false;
+ }
+
+ size_t i = 0;
+ int32_t val = 0;
+ bool neg = false;
+
+ if (*s == '-') {
+ neg = true;
+ i++;
+ }
+
+ if (s[i] < '0' || s[i] > '9') {
+ return false;
+ }
+
+ // Decimal or hex?
+ if (s[i] == '0' && s[i+1] == 'x') {
+ if (outValue)
+ outValue->dataType = outValue->TYPE_INT_HEX;
+ i += 2;
+ bool error = false;
+ while (i < len && !error) {
+ val = (val*16) + get_hex(s[i], &error);
+ i++;
+ }
+ if (error) {
+ return false;
+ }
+ } else {
+ if (outValue)
+ outValue->dataType = outValue->TYPE_INT_DEC;
+ while (i < len) {
+ if (s[i] < '0' || s[i] > '9') {
+ return false;
+ }
+ val = (val*10) + s[i]-'0';
+ i++;
+ }
+ }
+
+ if (neg) val = -val;
+
+ while (i < len && isspace16(s[i])) {
+ i++;
+ }
+
+ if (i == len) {
+ if (outValue)
+ outValue->data = val;
+ return true;
+ }
+
+ return false;
+}
+
+bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue)
+{
+ while (len > 0 && isspace16(*s)) {
+ s++;
+ len--;
+ }
+
+ if (len <= 0) {
+ return false;
+ }
+
+ char buf[128];
+ int i=0;
+ while (len > 0 && *s != 0 && i < 126) {
+ if (*s > 255) {
+ return false;
+ }
+ buf[i++] = *s++;
+ len--;
+ }
+
+ if (len > 0) {
+ return false;
+ }
+ if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.') {
+ return false;
+ }
+
+ buf[i] = 0;
+ const char* end;
+ float f = strtof(buf, (char**)&end);
+
+ if (*end != 0 && !isspace((unsigned char)*end)) {
+ // Might be a unit...
+ float scale;
+ if (parse_unit(end, outValue, &scale, &end)) {
+ f *= scale;
+ const bool neg = f < 0;
+ if (neg) f = -f;
+ uint64_t bits = (uint64_t)(f*(1<<23)+.5f);
+ uint32_t radix;
+ uint32_t shift;
+ if ((bits&0x7fffff) == 0) {
+ // Always use 23p0 if there is no fraction, just to make
+ // things easier to read.
+ radix = Res_value::COMPLEX_RADIX_23p0;
+ shift = 23;
+ } else if ((bits&0xffffffffff800000LL) == 0) {
+ // Magnitude is zero -- can fit in 0 bits of precision.
+ radix = Res_value::COMPLEX_RADIX_0p23;
+ shift = 0;
+ } else if ((bits&0xffffffff80000000LL) == 0) {
+ // Magnitude can fit in 8 bits of precision.
+ radix = Res_value::COMPLEX_RADIX_8p15;
+ shift = 8;
+ } else if ((bits&0xffffff8000000000LL) == 0) {
+ // Magnitude can fit in 16 bits of precision.
+ radix = Res_value::COMPLEX_RADIX_16p7;
+ shift = 16;
+ } else {
+ // Magnitude needs entire range, so no fractional part.
+ radix = Res_value::COMPLEX_RADIX_23p0;
+ shift = 23;
+ }
+ int32_t mantissa = (int32_t)(
+ (bits>>shift) & Res_value::COMPLEX_MANTISSA_MASK);
+ if (neg) {
+ mantissa = (-mantissa) & Res_value::COMPLEX_MANTISSA_MASK;
+ }
+ outValue->data |=
+ (radix<<Res_value::COMPLEX_RADIX_SHIFT)
+ | (mantissa<<Res_value::COMPLEX_MANTISSA_SHIFT);
+ //printf("Input value: %f 0x%016Lx, mult: %f, radix: %d, shift: %d, final: 0x%08x\n",
+ // f * (neg ? -1 : 1), bits, f*(1<<23),
+ // radix, shift, outValue->data);
+ return true;
+ }
+ return false;
+ }
+
+ while (*end != 0 && isspace((unsigned char)*end)) {
+ end++;
+ }
+
+ if (*end == 0) {
+ if (outValue) {
+ outValue->dataType = outValue->TYPE_FLOAT;
+ *(float*)(&outValue->data) = f;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ResTable::stringToValue(Res_value* outValue, String16* outString,
+ const char16_t* s, size_t len,
+ bool preserveSpaces, bool coerceType,
+ uint32_t attrID,
+ const String16* defType,
+ const String16* defPackage,
+ Accessor* accessor,
+ void* accessorCookie,
+ uint32_t attrType,
+ bool enforcePrivate) const
+{
+ bool localizationSetting = accessor != NULL && accessor->getLocalizationSetting();
+ const char* errorMsg = NULL;
+
+ outValue->size = sizeof(Res_value);
+ outValue->res0 = 0;
+
+ // First strip leading/trailing whitespace. Do this before handling
+ // escapes, so they can be used to force whitespace into the string.
+ if (!preserveSpaces) {
+ while (len > 0 && isspace16(*s)) {
+ s++;
+ len--;
+ }
+ while (len > 0 && isspace16(s[len-1])) {
+ len--;
+ }
+ // If the string ends with '\', then we keep the space after it.
+ if (len > 0 && s[len-1] == '\\' && s[len] != 0) {
+ len++;
+ }
+ }
+
+ //printf("Value for: %s\n", String8(s, len).string());
+
+ uint32_t l10nReq = ResTable_map::L10N_NOT_REQUIRED;
+ uint32_t attrMin = 0x80000000, attrMax = 0x7fffffff;
+ bool fromAccessor = false;
+ if (attrID != 0 && !Res_INTERNALID(attrID)) {
+ const ssize_t p = getResourcePackageIndex(attrID);
+ const bag_entry* bag;
+ ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1;
+ //printf("For attr 0x%08x got bag of %d\n", attrID, cnt);
+ if (cnt >= 0) {
+ while (cnt > 0) {
+ //printf("Entry 0x%08x = 0x%08x\n", bag->map.name.ident, bag->map.value.data);
+ switch (bag->map.name.ident) {
+ case ResTable_map::ATTR_TYPE:
+ attrType = bag->map.value.data;
+ break;
+ case ResTable_map::ATTR_MIN:
+ attrMin = bag->map.value.data;
+ break;
+ case ResTable_map::ATTR_MAX:
+ attrMax = bag->map.value.data;
+ break;
+ case ResTable_map::ATTR_L10N:
+ l10nReq = bag->map.value.data;
+ break;
+ }
+ bag++;
+ cnt--;
+ }
+ unlockBag(bag);
+ } else if (accessor && accessor->getAttributeType(attrID, &attrType)) {
+ fromAccessor = true;
+ if (attrType == ResTable_map::TYPE_ENUM
+ || attrType == ResTable_map::TYPE_FLAGS
+ || attrType == ResTable_map::TYPE_INTEGER) {
+ accessor->getAttributeMin(attrID, &attrMin);
+ accessor->getAttributeMax(attrID, &attrMax);
+ }
+ if (localizationSetting) {
+ l10nReq = accessor->getAttributeL10N(attrID);
+ }
+ }
+ }
+
+ const bool canStringCoerce =
+ coerceType && (attrType&ResTable_map::TYPE_STRING) != 0;
+
+ if (*s == '@') {
+ outValue->dataType = outValue->TYPE_REFERENCE;
+
+ // Note: we don't check attrType here because the reference can
+ // be to any other type; we just need to count on the client making
+ // sure the referenced type is correct.
+
+ //printf("Looking up ref: %s\n", String8(s, len).string());
+
+ // It's a reference!
+ if (len == 5 && s[1]=='n' && s[2]=='u' && s[3]=='l' && s[4]=='l') {
+ outValue->data = 0;
+ return true;
+ } else {
+ bool createIfNotFound = false;
+ const char16_t* resourceRefName;
+ int resourceNameLen;
+ if (len > 2 && s[1] == '+') {
+ createIfNotFound = true;
+ resourceRefName = s + 2;
+ resourceNameLen = len - 2;
+ } else if (len > 2 && s[1] == '*') {
+ enforcePrivate = false;
+ resourceRefName = s + 2;
+ resourceNameLen = len - 2;
+ } else {
+ createIfNotFound = false;
+ resourceRefName = s + 1;
+ resourceNameLen = len - 1;
+ }
+ String16 package, type, name;
+ if (!expandResourceRef(resourceRefName,resourceNameLen, &package, &type, &name,
+ defType, defPackage, &errorMsg)) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, errorMsg);
+ }
+ return false;
+ }
+
+ uint32_t specFlags = 0;
+ uint32_t rid = identifierForName(name.string(), name.size(), type.string(),
+ type.size(), package.string(), package.size(), &specFlags);
+ if (rid != 0) {
+ if (enforcePrivate) {
+ if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Resource is not public.");
+ }
+ return false;
+ }
+ }
+ if (!accessor) {
+ outValue->data = rid;
+ return true;
+ }
+ rid = Res_MAKEID(
+ accessor->getRemappedPackage(Res_GETPACKAGE(rid)),
+ Res_GETTYPE(rid), Res_GETENTRY(rid));
+ TABLE_NOISY(printf("Incl %s:%s/%s: 0x%08x\n",
+ String8(package).string(), String8(type).string(),
+ String8(name).string(), rid));
+ outValue->data = rid;
+ return true;
+ }
+
+ if (accessor) {
+ uint32_t rid = accessor->getCustomResourceWithCreation(package, type, name,
+ createIfNotFound);
+ if (rid != 0) {
+ TABLE_NOISY(printf("Pckg %s:%s/%s: 0x%08x\n",
+ String8(package).string(), String8(type).string(),
+ String8(name).string(), rid));
+ outValue->data = rid;
+ return true;
+ }
+ }
+ }
+
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "No resource found that matches the given name");
+ }
+ return false;
+ }
+
+ // if we got to here, and localization is required and it's not a reference,
+ // complain and bail.
+ if (l10nReq == ResTable_map::L10N_SUGGESTED) {
+ if (localizationSetting) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "This attribute must be localized.");
+ }
+ }
+ }
+
+ if (*s == '#') {
+ // It's a color! Convert to an integer of the form 0xaarrggbb.
+ uint32_t color = 0;
+ bool error = false;
+ if (len == 4) {
+ outValue->dataType = outValue->TYPE_INT_COLOR_RGB4;
+ color |= 0xFF000000;
+ color |= get_hex(s[1], &error) << 20;
+ color |= get_hex(s[1], &error) << 16;
+ color |= get_hex(s[2], &error) << 12;
+ color |= get_hex(s[2], &error) << 8;
+ color |= get_hex(s[3], &error) << 4;
+ color |= get_hex(s[3], &error);
+ } else if (len == 5) {
+ outValue->dataType = outValue->TYPE_INT_COLOR_ARGB4;
+ color |= get_hex(s[1], &error) << 28;
+ color |= get_hex(s[1], &error) << 24;
+ color |= get_hex(s[2], &error) << 20;
+ color |= get_hex(s[2], &error) << 16;
+ color |= get_hex(s[3], &error) << 12;
+ color |= get_hex(s[3], &error) << 8;
+ color |= get_hex(s[4], &error) << 4;
+ color |= get_hex(s[4], &error);
+ } else if (len == 7) {
+ outValue->dataType = outValue->TYPE_INT_COLOR_RGB8;
+ color |= 0xFF000000;
+ color |= get_hex(s[1], &error) << 20;
+ color |= get_hex(s[2], &error) << 16;
+ color |= get_hex(s[3], &error) << 12;
+ color |= get_hex(s[4], &error) << 8;
+ color |= get_hex(s[5], &error) << 4;
+ color |= get_hex(s[6], &error);
+ } else if (len == 9) {
+ outValue->dataType = outValue->TYPE_INT_COLOR_ARGB8;
+ color |= get_hex(s[1], &error) << 28;
+ color |= get_hex(s[2], &error) << 24;
+ color |= get_hex(s[3], &error) << 20;
+ color |= get_hex(s[4], &error) << 16;
+ color |= get_hex(s[5], &error) << 12;
+ color |= get_hex(s[6], &error) << 8;
+ color |= get_hex(s[7], &error) << 4;
+ color |= get_hex(s[8], &error);
+ } else {
+ error = true;
+ }
+ if (!error) {
+ if ((attrType&ResTable_map::TYPE_COLOR) == 0) {
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie,
+ "Color types not allowed");
+ }
+ return false;
+ }
+ } else {
+ outValue->data = color;
+ //printf("Color input=%s, output=0x%x\n", String8(s, len).string(), color);
+ return true;
+ }
+ } else {
+ if ((attrType&ResTable_map::TYPE_COLOR) != 0) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Color value not valid --"
+ " must be #rgb, #argb, #rrggbb, or #aarrggbb");
+ }
+ #if 0
+ fprintf(stderr, "%s: Color ID %s value %s is not valid\n",
+ "Resource File", //(const char*)in->getPrintableSource(),
+ String8(*curTag).string(),
+ String8(s, len).string());
+ #endif
+ return false;
+ }
+ }
+ }
+
+ if (*s == '?') {
+ outValue->dataType = outValue->TYPE_ATTRIBUTE;
+
+ // Note: we don't check attrType here because the reference can
+ // be to any other type; we just need to count on the client making
+ // sure the referenced type is correct.
+
+ //printf("Looking up attr: %s\n", String8(s, len).string());
+
+ static const String16 attr16("attr");
+ String16 package, type, name;
+ if (!expandResourceRef(s+1, len-1, &package, &type, &name,
+ &attr16, defPackage, &errorMsg)) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, errorMsg);
+ }
+ return false;
+ }
+
+ //printf("Pkg: %s, Type: %s, Name: %s\n",
+ // String8(package).string(), String8(type).string(),
+ // String8(name).string());
+ uint32_t specFlags = 0;
+ uint32_t rid =
+ identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size(), &specFlags);
+ if (rid != 0) {
+ if (enforcePrivate) {
+ if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Attribute is not public.");
+ }
+ return false;
+ }
+ }
+ if (!accessor) {
+ outValue->data = rid;
+ return true;
+ }
+ rid = Res_MAKEID(
+ accessor->getRemappedPackage(Res_GETPACKAGE(rid)),
+ Res_GETTYPE(rid), Res_GETENTRY(rid));
+ //printf("Incl %s:%s/%s: 0x%08x\n",
+ // String8(package).string(), String8(type).string(),
+ // String8(name).string(), rid);
+ outValue->data = rid;
+ return true;
+ }
+
+ if (accessor) {
+ uint32_t rid = accessor->getCustomResource(package, type, name);
+ if (rid != 0) {
+ //printf("Mine %s:%s/%s: 0x%08x\n",
+ // String8(package).string(), String8(type).string(),
+ // String8(name).string(), rid);
+ outValue->data = rid;
+ return true;
+ }
+ }
+
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "No resource found that matches the given name");
+ }
+ return false;
+ }
+
+ if (stringToInt(s, len, outValue)) {
+ if ((attrType&ResTable_map::TYPE_INTEGER) == 0) {
+ // If this type does not allow integers, but does allow floats,
+ // fall through on this error case because the float type should
+ // be able to accept any integer value.
+ if (!canStringCoerce && (attrType&ResTable_map::TYPE_FLOAT) == 0) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Integer types not allowed");
+ }
+ return false;
+ }
+ } else {
+ if (((int32_t)outValue->data) < ((int32_t)attrMin)
+ || ((int32_t)outValue->data) > ((int32_t)attrMax)) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Integer value out of range");
+ }
+ return false;
+ }
+ return true;
+ }
+ }
+
+ if (stringToFloat(s, len, outValue)) {
+ if (outValue->dataType == Res_value::TYPE_DIMENSION) {
+ if ((attrType&ResTable_map::TYPE_DIMENSION) != 0) {
+ return true;
+ }
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Dimension types not allowed");
+ }
+ return false;
+ }
+ } else if (outValue->dataType == Res_value::TYPE_FRACTION) {
+ if ((attrType&ResTable_map::TYPE_FRACTION) != 0) {
+ return true;
+ }
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Fraction types not allowed");
+ }
+ return false;
+ }
+ } else if ((attrType&ResTable_map::TYPE_FLOAT) == 0) {
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Float types not allowed");
+ }
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+
+ if (len == 4) {
+ if ((s[0] == 't' || s[0] == 'T') &&
+ (s[1] == 'r' || s[1] == 'R') &&
+ (s[2] == 'u' || s[2] == 'U') &&
+ (s[3] == 'e' || s[3] == 'E')) {
+ if ((attrType&ResTable_map::TYPE_BOOLEAN) == 0) {
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Boolean types not allowed");
+ }
+ return false;
+ }
+ } else {
+ outValue->dataType = outValue->TYPE_INT_BOOLEAN;
+ outValue->data = (uint32_t)-1;
+ return true;
+ }
+ }
+ }
+
+ if (len == 5) {
+ if ((s[0] == 'f' || s[0] == 'F') &&
+ (s[1] == 'a' || s[1] == 'A') &&
+ (s[2] == 'l' || s[2] == 'L') &&
+ (s[3] == 's' || s[3] == 'S') &&
+ (s[4] == 'e' || s[4] == 'E')) {
+ if ((attrType&ResTable_map::TYPE_BOOLEAN) == 0) {
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Boolean types not allowed");
+ }
+ return false;
+ }
+ } else {
+ outValue->dataType = outValue->TYPE_INT_BOOLEAN;
+ outValue->data = 0;
+ return true;
+ }
+ }
+ }
+
+ if ((attrType&ResTable_map::TYPE_ENUM) != 0) {
+ const ssize_t p = getResourcePackageIndex(attrID);
+ const bag_entry* bag;
+ ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1;
+ //printf("Got %d for enum\n", cnt);
+ if (cnt >= 0) {
+ resource_name rname;
+ while (cnt > 0) {
+ if (!Res_INTERNALID(bag->map.name.ident)) {
+ //printf("Trying attr #%08x\n", bag->map.name.ident);
+ if (getResourceName(bag->map.name.ident, false, &rname)) {
+ #if 0
+ printf("Matching %s against %s (0x%08x)\n",
+ String8(s, len).string(),
+ String8(rname.name, rname.nameLen).string(),
+ bag->map.name.ident);
+ #endif
+ if (strzcmp16(s, len, rname.name, rname.nameLen) == 0) {
+ outValue->dataType = bag->map.value.dataType;
+ outValue->data = bag->map.value.data;
+ unlockBag(bag);
+ return true;
+ }
+ }
+
+ }
+ bag++;
+ cnt--;
+ }
+ unlockBag(bag);
+ }
+
+ if (fromAccessor) {
+ if (accessor->getAttributeEnum(attrID, s, len, outValue)) {
+ return true;
+ }
+ }
+ }
+
+ if ((attrType&ResTable_map::TYPE_FLAGS) != 0) {
+ const ssize_t p = getResourcePackageIndex(attrID);
+ const bag_entry* bag;
+ ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1;
+ //printf("Got %d for flags\n", cnt);
+ if (cnt >= 0) {
+ bool failed = false;
+ resource_name rname;
+ outValue->dataType = Res_value::TYPE_INT_HEX;
+ outValue->data = 0;
+ const char16_t* end = s + len;
+ const char16_t* pos = s;
+ while (pos < end && !failed) {
+ const char16_t* start = pos;
+ pos++;
+ while (pos < end && *pos != '|') {
+ pos++;
+ }
+ //printf("Looking for: %s\n", String8(start, pos-start).string());
+ const bag_entry* bagi = bag;
+ ssize_t i;
+ for (i=0; i<cnt; i++, bagi++) {
+ if (!Res_INTERNALID(bagi->map.name.ident)) {
+ //printf("Trying attr #%08x\n", bagi->map.name.ident);
+ if (getResourceName(bagi->map.name.ident, false, &rname)) {
+ #if 0
+ printf("Matching %s against %s (0x%08x)\n",
+ String8(start,pos-start).string(),
+ String8(rname.name, rname.nameLen).string(),
+ bagi->map.name.ident);
+ #endif
+ if (strzcmp16(start, pos-start, rname.name, rname.nameLen) == 0) {
+ outValue->data |= bagi->map.value.data;
+ break;
+ }
+ }
+ }
+ }
+ if (i >= cnt) {
+ // Didn't find this flag identifier.
+ failed = true;
+ }
+ if (pos < end) {
+ pos++;
+ }
+ }
+ unlockBag(bag);
+ if (!failed) {
+ //printf("Final flag value: 0x%lx\n", outValue->data);
+ return true;
+ }
+ }
+
+
+ if (fromAccessor) {
+ if (accessor->getAttributeFlags(attrID, s, len, outValue)) {
+ //printf("Final flag value: 0x%lx\n", outValue->data);
+ return true;
+ }
+ }
+ }
+
+ if ((attrType&ResTable_map::TYPE_STRING) == 0) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "String types not allowed");
+ }
+ return false;
+ }
+
+ // Generic string handling...
+ outValue->dataType = outValue->TYPE_STRING;
+ if (outString) {
+ bool failed = collectString(outString, s, len, preserveSpaces, &errorMsg);
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, errorMsg);
+ }
+ return failed;
+ }
+
+ return true;
+}
+
+bool ResTable::collectString(String16* outString,
+ const char16_t* s, size_t len,
+ bool preserveSpaces,
+ const char** outErrorMsg,
+ bool append)
+{
+ String16 tmp;
+
+ char quoted = 0;
+ const char16_t* p = s;
+ while (p < (s+len)) {
+ while (p < (s+len)) {
+ const char16_t c = *p;
+ if (c == '\\') {
+ break;
+ }
+ if (!preserveSpaces) {
+ if (quoted == 0 && isspace16(c)
+ && (c != ' ' || isspace16(*(p+1)))) {
+ break;
+ }
+ if (c == '"' && (quoted == 0 || quoted == '"')) {
+ break;
+ }
+ if (c == '\'' && (quoted == 0 || quoted == '\'')) {
+ /*
+ * In practice, when people write ' instead of \'
+ * in a string, they are doing it by accident
+ * instead of really meaning to use ' as a quoting
+ * character. Warn them so they don't lose it.
+ */
+ if (outErrorMsg) {
+ *outErrorMsg = "Apostrophe not preceded by \\";
+ }
+ return false;
+ }
+ }
+ p++;
+ }
+ if (p < (s+len)) {
+ if (p > s) {
+ tmp.append(String16(s, p-s));
+ }
+ if (!preserveSpaces && (*p == '"' || *p == '\'')) {
+ if (quoted == 0) {
+ quoted = *p;
+ } else {
+ quoted = 0;
+ }
+ p++;
+ } else if (!preserveSpaces && isspace16(*p)) {
+ // Space outside of a quote -- consume all spaces and
+ // leave a single plain space char.
+ tmp.append(String16(" "));
+ p++;
+ while (p < (s+len) && isspace16(*p)) {
+ p++;
+ }
+ } else if (*p == '\\') {
+ p++;
+ if (p < (s+len)) {
+ switch (*p) {
+ case 't':
+ tmp.append(String16("\t"));
+ break;
+ case 'n':
+ tmp.append(String16("\n"));
+ break;
+ case '#':
+ tmp.append(String16("#"));
+ break;
+ case '@':
+ tmp.append(String16("@"));
+ break;
+ case '?':
+ tmp.append(String16("?"));
+ break;
+ case '"':
+ tmp.append(String16("\""));
+ break;
+ case '\'':
+ tmp.append(String16("'"));
+ break;
+ case '\\':
+ tmp.append(String16("\\"));
+ break;
+ case 'u':
+ {
+ char16_t chr = 0;
+ int i = 0;
+ while (i < 4 && p[1] != 0) {
+ p++;
+ i++;
+ int c;
+ if (*p >= '0' && *p <= '9') {
+ c = *p - '0';
+ } else if (*p >= 'a' && *p <= 'f') {
+ c = *p - 'a' + 10;
+ } else if (*p >= 'A' && *p <= 'F') {
+ c = *p - 'A' + 10;
+ } else {
+ if (outErrorMsg) {
+ *outErrorMsg = "Bad character in \\u unicode escape sequence";
+ }
+ return false;
+ }
+ chr = (chr<<4) | c;
+ }
+ tmp.append(String16(&chr, 1));
+ } break;
+ default:
+ // ignore unknown escape chars.
+ break;
+ }
+ p++;
+ }
+ }
+ len -= (p-s);
+ s = p;
+ }
+ }
+
+ if (tmp.size() != 0) {
+ if (len > 0) {
+ tmp.append(String16(s, len));
+ }
+ if (append) {
+ outString->append(tmp);
+ } else {
+ outString->setTo(tmp);
+ }
+ } else {
+ if (append) {
+ outString->append(String16(s, len));
+ } else {
+ outString->setTo(s, len);
+ }
+ }
+
+ return true;
+}
+
+size_t ResTable::getBasePackageCount() const
+{
+ if (mError != NO_ERROR) {
+ return 0;
+ }
+ return mPackageGroups.size();
+}
+
+const char16_t* ResTable::getBasePackageName(size_t idx) const
+{
+ if (mError != NO_ERROR) {
+ return 0;
+ }
+ LOG_FATAL_IF(idx >= mPackageGroups.size(),
+ "Requested package index %d past package count %d",
+ (int)idx, (int)mPackageGroups.size());
+ return mPackageGroups[idx]->name.string();
+}
+
+uint32_t ResTable::getBasePackageId(size_t idx) const
+{
+ if (mError != NO_ERROR) {
+ return 0;
+ }
+ LOG_FATAL_IF(idx >= mPackageGroups.size(),
+ "Requested package index %d past package count %d",
+ (int)idx, (int)mPackageGroups.size());
+ return mPackageGroups[idx]->id;
+}
+
+size_t ResTable::getTableCount() const
+{
+ return mHeaders.size();
+}
+
+const ResStringPool* ResTable::getTableStringBlock(size_t index) const
+{
+ return &mHeaders[index]->values;
+}
+
+void* ResTable::getTableCookie(size_t index) const
+{
+ return mHeaders[index]->cookie;
+}
+
+void ResTable::getConfigurations(Vector<ResTable_config>* configs) const
+{
+ const size_t I = mPackageGroups.size();
+ for (size_t i=0; i<I; i++) {
+ const PackageGroup* packageGroup = mPackageGroups[i];
+ const size_t J = packageGroup->packages.size();
+ for (size_t j=0; j<J; j++) {
+ const Package* package = packageGroup->packages[j];
+ const size_t K = package->types.size();
+ for (size_t k=0; k<K; k++) {
+ const Type* type = package->types[k];
+ if (type == NULL) continue;
+ const size_t L = type->configs.size();
+ for (size_t l=0; l<L; l++) {
+ const ResTable_type* config = type->configs[l];
+ const ResTable_config* cfg = &config->config;
+ // only insert unique
+ const size_t M = configs->size();
+ size_t m;
+ for (m=0; m<M; m++) {
+ if (0 == (*configs)[m].compare(*cfg)) {
+ break;
+ }
+ }
+ // if we didn't find it
+ if (m == M) {
+ configs->add(*cfg);
+ }
+ }
+ }
+ }
+ }
+}
+
+void ResTable::getLocales(Vector<String8>* locales) const
+{
+ Vector<ResTable_config> configs;
+ ALOGV("calling getConfigurations");
+ getConfigurations(&configs);
+ ALOGV("called getConfigurations size=%d", (int)configs.size());
+ const size_t I = configs.size();
+ for (size_t i=0; i<I; i++) {
+ char locale[6];
+ configs[i].getLocale(locale);
+ const size_t J = locales->size();
+ size_t j;
+ for (j=0; j<J; j++) {
+ if (0 == strcmp(locale, (*locales)[j].string())) {
+ break;
+ }
+ }
+ if (j == J) {
+ locales->add(String8(locale));
+ }
+ }
+}
+
+ssize_t ResTable::getEntry(
+ const Package* package, int typeIndex, int entryIndex,
+ const ResTable_config* config,
+ const ResTable_type** outType, const ResTable_entry** outEntry,
+ const Type** outTypeClass) const
+{
+ ALOGV("Getting entry from package %p\n", package);
+ const ResTable_package* const pkg = package->package;
+
+ const Type* allTypes = package->getType(typeIndex);
+ ALOGV("allTypes=%p\n", allTypes);
+ if (allTypes == NULL) {
+ ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex);
+ return 0;
+ }
+
+ if ((size_t)entryIndex >= allTypes->entryCount) {
+ ALOGW("getEntry failing because entryIndex %d is beyond type entryCount %d",
+ entryIndex, (int)allTypes->entryCount);
+ return BAD_TYPE;
+ }
+
+ const ResTable_type* type = NULL;
+ uint32_t offset = ResTable_type::NO_ENTRY;
+ ResTable_config bestConfig;
+ memset(&bestConfig, 0, sizeof(bestConfig)); // make the compiler shut up
+
+ const size_t NT = allTypes->configs.size();
+ for (size_t i=0; i<NT; i++) {
+ const ResTable_type* const thisType = allTypes->configs[i];
+ if (thisType == NULL) continue;
+
+ ResTable_config thisConfig;
+ thisConfig.copyFromDtoH(thisType->config);
+
+ TABLE_GETENTRY(ALOGI("Match entry 0x%x in type 0x%x (sz 0x%x): %s\n",
+ entryIndex, typeIndex+1, dtohl(thisType->config.size),
+ thisConfig.toString().string()));
+
+ // Check to make sure this one is valid for the current parameters.
+ if (config && !thisConfig.match(*config)) {
+ TABLE_GETENTRY(ALOGI("Does not match config!\n"));
+ continue;
+ }
+
+ // Check if there is the desired entry in this type.
+
+ const uint8_t* const end = ((const uint8_t*)thisType)
+ + dtohl(thisType->header.size);
+ const uint32_t* const eindex = (const uint32_t*)
+ (((const uint8_t*)thisType) + dtohs(thisType->header.headerSize));
+
+ uint32_t thisOffset = dtohl(eindex[entryIndex]);
+ if (thisOffset == ResTable_type::NO_ENTRY) {
+ TABLE_GETENTRY(ALOGI("Skipping because it is not defined!\n"));
+ continue;
+ }
+
+ if (type != NULL) {
+ // Check if this one is less specific than the last found. If so,
+ // we will skip it. We check starting with things we most care
+ // about to those we least care about.
+ if (!thisConfig.isBetterThan(bestConfig, config)) {
+ TABLE_GETENTRY(ALOGI("This config is worse than last!\n"));
+ continue;
+ }
+ }
+
+ type = thisType;
+ offset = thisOffset;
+ bestConfig = thisConfig;
+ TABLE_GETENTRY(ALOGI("Best entry so far -- using it!\n"));
+ if (!config) break;
+ }
+
+ if (type == NULL) {
+ TABLE_GETENTRY(ALOGI("No value found for requested entry!\n"));
+ return BAD_INDEX;
+ }
+
+ offset += dtohl(type->entriesStart);
+ TABLE_NOISY(aout << "Looking in resource table " << package->header->header
+ << ", typeOff="
+ << (void*)(((const char*)type)-((const char*)package->header->header))
+ << ", offset=" << (void*)offset << endl);
+
+ if (offset > (dtohl(type->header.size)-sizeof(ResTable_entry))) {
+ ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x",
+ offset, dtohl(type->header.size));
+ return BAD_TYPE;
+ }
+ if ((offset&0x3) != 0) {
+ ALOGW("ResTable_entry at 0x%x is not on an integer boundary",
+ offset);
+ return BAD_TYPE;
+ }
+
+ const ResTable_entry* const entry = (const ResTable_entry*)
+ (((const uint8_t*)type) + offset);
+ if (dtohs(entry->size) < sizeof(*entry)) {
+ ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
+ return BAD_TYPE;
+ }
+
+ *outType = type;
+ *outEntry = entry;
+ if (outTypeClass != NULL) {
+ *outTypeClass = allTypes;
+ }
+ return offset + dtohs(entry->size);
+}
+
+status_t ResTable::parsePackage(const ResTable_package* const pkg,
+ const Header* const header, uint32_t idmap_id)
+{
+ const uint8_t* base = (const uint8_t*)pkg;
+ status_t err = validate_chunk(&pkg->header, sizeof(*pkg),
+ header->dataEnd, "ResTable_package");
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+
+ const size_t pkgSize = dtohl(pkg->header.size);
+
+ if (dtohl(pkg->typeStrings) >= pkgSize) {
+ ALOGW("ResTable_package type strings at %p are past chunk size %p.",
+ (void*)dtohl(pkg->typeStrings), (void*)pkgSize);
+ return (mError=BAD_TYPE);
+ }
+ if ((dtohl(pkg->typeStrings)&0x3) != 0) {
+ ALOGW("ResTable_package type strings at %p is not on an integer boundary.",
+ (void*)dtohl(pkg->typeStrings));
+ return (mError=BAD_TYPE);
+ }
+ if (dtohl(pkg->keyStrings) >= pkgSize) {
+ ALOGW("ResTable_package key strings at %p are past chunk size %p.",
+ (void*)dtohl(pkg->keyStrings), (void*)pkgSize);
+ return (mError=BAD_TYPE);
+ }
+ if ((dtohl(pkg->keyStrings)&0x3) != 0) {
+ ALOGW("ResTable_package key strings at %p is not on an integer boundary.",
+ (void*)dtohl(pkg->keyStrings));
+ return (mError=BAD_TYPE);
+ }
+
+ Package* package = NULL;
+ PackageGroup* group = NULL;
+ uint32_t id = idmap_id != 0 ? idmap_id : dtohl(pkg->id);
+ // If at this point id == 0, pkg is an overlay package without a
+ // corresponding idmap. During regular usage, overlay packages are
+ // always loaded alongside their idmaps, but during idmap creation
+ // the package is temporarily loaded by itself.
+ if (id < 256) {
+
+ package = new Package(this, header, pkg);
+ if (package == NULL) {
+ return (mError=NO_MEMORY);
+ }
+
+ size_t idx = mPackageMap[id];
+ if (idx == 0) {
+ idx = mPackageGroups.size()+1;
+
+ char16_t tmpName[sizeof(pkg->name)/sizeof(char16_t)];
+ strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(char16_t));
+ group = new PackageGroup(this, String16(tmpName), id);
+ if (group == NULL) {
+ delete package;
+ return (mError=NO_MEMORY);
+ }
+
+ err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
+ header->dataEnd-(base+dtohl(pkg->typeStrings)));
+ if (err != NO_ERROR) {
+ delete group;
+ delete package;
+ return (mError=err);
+ }
+ err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings),
+ header->dataEnd-(base+dtohl(pkg->keyStrings)));
+ if (err != NO_ERROR) {
+ delete group;
+ delete package;
+ return (mError=err);
+ }
+
+ //printf("Adding new package id %d at index %d\n", id, idx);
+ err = mPackageGroups.add(group);
+ if (err < NO_ERROR) {
+ return (mError=err);
+ }
+ group->basePackage = package;
+
+ mPackageMap[id] = (uint8_t)idx;
+ } else {
+ group = mPackageGroups.itemAt(idx-1);
+ if (group == NULL) {
+ return (mError=UNKNOWN_ERROR);
+ }
+ }
+ err = group->packages.add(package);
+ if (err < NO_ERROR) {
+ return (mError=err);
+ }
+ } else {
+ LOG_ALWAYS_FATAL("Package id out of range");
+ return NO_ERROR;
+ }
+
+
+ // Iterate through all chunks.
+ size_t curPackage = 0;
+
+ const ResChunk_header* chunk =
+ (const ResChunk_header*)(((const uint8_t*)pkg)
+ + dtohs(pkg->header.headerSize));
+ const uint8_t* endPos = ((const uint8_t*)pkg) + dtohs(pkg->header.size);
+ while (((const uint8_t*)chunk) <= (endPos-sizeof(ResChunk_header)) &&
+ ((const uint8_t*)chunk) <= (endPos-dtohl(chunk->size))) {
+ TABLE_NOISY(ALOGV("PackageChunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%p\n",
+ dtohs(chunk->type), dtohs(chunk->headerSize), dtohl(chunk->size),
+ (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header))));
+ const size_t csize = dtohl(chunk->size);
+ const uint16_t ctype = dtohs(chunk->type);
+ if (ctype == RES_TABLE_TYPE_SPEC_TYPE) {
+ const ResTable_typeSpec* typeSpec = (const ResTable_typeSpec*)(chunk);
+ err = validate_chunk(&typeSpec->header, sizeof(*typeSpec),
+ endPos, "ResTable_typeSpec");
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+
+ const size_t typeSpecSize = dtohl(typeSpec->header.size);
+
+ LOAD_TABLE_NOISY(printf("TypeSpec off %p: type=0x%x, headerSize=0x%x, size=%p\n",
+ (void*)(base-(const uint8_t*)chunk),
+ dtohs(typeSpec->header.type),
+ dtohs(typeSpec->header.headerSize),
+ (void*)typeSize));
+ // look for block overrun or int overflow when multiplying by 4
+ if ((dtohl(typeSpec->entryCount) > (INT32_MAX/sizeof(uint32_t))
+ || dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*dtohl(typeSpec->entryCount))
+ > typeSpecSize)) {
+ ALOGW("ResTable_typeSpec entry index to %p extends beyond chunk end %p.",
+ (void*)(dtohs(typeSpec->header.headerSize)
+ +(sizeof(uint32_t)*dtohl(typeSpec->entryCount))),
+ (void*)typeSpecSize);
+ return (mError=BAD_TYPE);
+ }
+
+ if (typeSpec->id == 0) {
+ ALOGW("ResTable_type has an id of 0.");
+ return (mError=BAD_TYPE);
+ }
+
+ while (package->types.size() < typeSpec->id) {
+ package->types.add(NULL);
+ }
+ Type* t = package->types[typeSpec->id-1];
+ if (t == NULL) {
+ t = new Type(header, package, dtohl(typeSpec->entryCount));
+ package->types.editItemAt(typeSpec->id-1) = t;
+ } else if (dtohl(typeSpec->entryCount) != t->entryCount) {
+ ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
+ (int)dtohl(typeSpec->entryCount), (int)t->entryCount);
+ return (mError=BAD_TYPE);
+ }
+ t->typeSpecFlags = (const uint32_t*)(
+ ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
+ t->typeSpec = typeSpec;
+
+ } else if (ctype == RES_TABLE_TYPE_TYPE) {
+ const ResTable_type* type = (const ResTable_type*)(chunk);
+ err = validate_chunk(&type->header, sizeof(*type)-sizeof(ResTable_config)+4,
+ endPos, "ResTable_type");
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+
+ const size_t typeSize = dtohl(type->header.size);
+
+ LOAD_TABLE_NOISY(printf("Type off %p: type=0x%x, headerSize=0x%x, size=%p\n",
+ (void*)(base-(const uint8_t*)chunk),
+ dtohs(type->header.type),
+ dtohs(type->header.headerSize),
+ (void*)typeSize));
+ if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*dtohl(type->entryCount))
+ > typeSize) {
+ ALOGW("ResTable_type entry index to %p extends beyond chunk end %p.",
+ (void*)(dtohs(type->header.headerSize)
+ +(sizeof(uint32_t)*dtohl(type->entryCount))),
+ (void*)typeSize);
+ return (mError=BAD_TYPE);
+ }
+ if (dtohl(type->entryCount) != 0
+ && dtohl(type->entriesStart) > (typeSize-sizeof(ResTable_entry))) {
+ ALOGW("ResTable_type entriesStart at %p extends beyond chunk end %p.",
+ (void*)dtohl(type->entriesStart), (void*)typeSize);
+ return (mError=BAD_TYPE);
+ }
+ if (type->id == 0) {
+ ALOGW("ResTable_type has an id of 0.");
+ return (mError=BAD_TYPE);
+ }
+
+ while (package->types.size() < type->id) {
+ package->types.add(NULL);
+ }
+ Type* t = package->types[type->id-1];
+ if (t == NULL) {
+ t = new Type(header, package, dtohl(type->entryCount));
+ package->types.editItemAt(type->id-1) = t;
+ } else if (dtohl(type->entryCount) != t->entryCount) {
+ ALOGW("ResTable_type entry count inconsistent: given %d, previously %d",
+ (int)dtohl(type->entryCount), (int)t->entryCount);
+ return (mError=BAD_TYPE);
+ }
+
+ TABLE_GETENTRY(
+ ResTable_config thisConfig;
+ thisConfig.copyFromDtoH(type->config);
+ ALOGI("Adding config to type %d: %s\n",
+ type->id, thisConfig.toString().string()));
+ t->configs.add(type);
+ } else {
+ status_t err = validate_chunk(chunk, sizeof(ResChunk_header),
+ endPos, "ResTable_package:unknown");
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+ }
+ chunk = (const ResChunk_header*)
+ (((const uint8_t*)chunk) + csize);
+ }
+
+ if (group->typeCount == 0) {
+ group->typeCount = package->types.size();
+ }
+
+ return NO_ERROR;
+}
+
+status_t ResTable::createIdmap(const ResTable& overlay, uint32_t originalCrc, uint32_t overlayCrc,
+ void** outData, size_t* outSize) const
+{
+ // see README for details on the format of map
+ if (mPackageGroups.size() == 0) {
+ return UNKNOWN_ERROR;
+ }
+ if (mPackageGroups[0]->packages.size() == 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ Vector<Vector<uint32_t> > map;
+ const PackageGroup* pg = mPackageGroups[0];
+ const Package* pkg = pg->packages[0];
+ size_t typeCount = pkg->types.size();
+ // starting size is header + first item (number of types in map)
+ *outSize = (IDMAP_HEADER_SIZE + 1) * sizeof(uint32_t);
+ const String16 overlayPackage(overlay.mPackageGroups[0]->packages[0]->package->name);
+ const uint32_t pkg_id = pkg->package->id << 24;
+
+ for (size_t typeIndex = 0; typeIndex < typeCount; ++typeIndex) {
+ ssize_t first = -1;
+ ssize_t last = -1;
+ const Type* typeConfigs = pkg->getType(typeIndex);
+ ssize_t mapIndex = map.add();
+ if (mapIndex < 0) {
+ return NO_MEMORY;
+ }
+ Vector<uint32_t>& vector = map.editItemAt(mapIndex);
+ for (size_t entryIndex = 0; entryIndex < typeConfigs->entryCount; ++entryIndex) {
+ uint32_t resID = pkg_id
+ | (0x00ff0000 & ((typeIndex+1)<<16))
+ | (0x0000ffff & (entryIndex));
+ resource_name resName;
+ if (!this->getResourceName(resID, true, &resName)) {
+ ALOGW("idmap: resource 0x%08x has spec but lacks values, skipping\n", resID);
+ // add dummy value, or trimming leading/trailing zeroes later will fail
+ vector.push(0);
+ continue;
+ }
+
+ const String16 overlayType(resName.type, resName.typeLen);
+ const String16 overlayName(resName.name, resName.nameLen);
+ uint32_t overlayResID = overlay.identifierForName(overlayName.string(),
+ overlayName.size(),
+ overlayType.string(),
+ overlayType.size(),
+ overlayPackage.string(),
+ overlayPackage.size());
+ if (overlayResID != 0) {
+ overlayResID = pkg_id | (0x00ffffff & overlayResID);
+ last = Res_GETENTRY(resID);
+ if (first == -1) {
+ first = Res_GETENTRY(resID);
+ }
+ }
+ vector.push(overlayResID);
+#if 0
+ if (overlayResID != 0) {
+ ALOGD("%s/%s 0x%08x -> 0x%08x\n",
+ String8(String16(resName.type)).string(),
+ String8(String16(resName.name)).string(),
+ resID, overlayResID);
+ }
+#endif
+ }
+
+ if (first != -1) {
+ // shave off trailing entries which lack overlay values
+ const size_t last_past_one = last + 1;
+ if (last_past_one < vector.size()) {
+ vector.removeItemsAt(last_past_one, vector.size() - last_past_one);
+ }
+ // shave off leading entries which lack overlay values
+ vector.removeItemsAt(0, first);
+ // store offset to first overlaid resource ID of this type
+ vector.insertAt((uint32_t)first, 0, 1);
+ // reserve space for number and offset of entries, and the actual entries
+ *outSize += (2 + vector.size()) * sizeof(uint32_t);
+ } else {
+ // no entries of current type defined in overlay package
+ vector.clear();
+ // reserve space for type offset
+ *outSize += 1 * sizeof(uint32_t);
+ }
+ }
+
+ if ((*outData = malloc(*outSize)) == NULL) {
+ return NO_MEMORY;
+ }
+ uint32_t* data = (uint32_t*)*outData;
+ *data++ = htodl(IDMAP_MAGIC);
+ *data++ = htodl(originalCrc);
+ *data++ = htodl(overlayCrc);
+ const size_t mapSize = map.size();
+ *data++ = htodl(mapSize);
+ size_t offset = mapSize;
+ for (size_t i = 0; i < mapSize; ++i) {
+ const Vector<uint32_t>& vector = map.itemAt(i);
+ const size_t N = vector.size();
+ if (N == 0) {
+ *data++ = htodl(0);
+ } else {
+ offset++;
+ *data++ = htodl(offset);
+ offset += N;
+ }
+ }
+ for (size_t i = 0; i < mapSize; ++i) {
+ const Vector<uint32_t>& vector = map.itemAt(i);
+ const size_t N = vector.size();
+ if (N == 0) {
+ continue;
+ }
+ if (N == 1) { // vector expected to hold (offset) + (N > 0 entries)
+ ALOGW("idmap: type %d supposedly has entries, but no entries found\n", i);
+ return UNKNOWN_ERROR;
+ }
+ *data++ = htodl(N - 1); // do not count the offset (which is vector's first element)
+ for (size_t j = 0; j < N; ++j) {
+ const uint32_t& overlayResID = vector.itemAt(j);
+ *data++ = htodl(overlayResID);
+ }
+ }
+
+ return NO_ERROR;
+}
+
+bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes,
+ uint32_t* pOriginalCrc, uint32_t* pOverlayCrc)
+{
+ const uint32_t* map = (const uint32_t*)idmap;
+ if (!assertIdmapHeader(map, sizeBytes)) {
+ return false;
+ }
+ *pOriginalCrc = map[1];
+ *pOverlayCrc = map[2];
+ return true;
+}
+
+
+#define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).string())
+
+#define CHAR16_ARRAY_EQ(constant, var, len) \
+ ((len == (sizeof(constant)/sizeof(constant[0]))) && (0 == memcmp((var), (constant), (len))))
+
+static void print_complex(uint32_t complex, bool isFraction)
+{
+ const float MANTISSA_MULT =
+ 1.0f / (1<<Res_value::COMPLEX_MANTISSA_SHIFT);
+ const float RADIX_MULTS[] = {
+ 1.0f*MANTISSA_MULT, 1.0f/(1<<7)*MANTISSA_MULT,
+ 1.0f/(1<<15)*MANTISSA_MULT, 1.0f/(1<<23)*MANTISSA_MULT
+ };
+
+ float value = (complex&(Res_value::COMPLEX_MANTISSA_MASK
+ <<Res_value::COMPLEX_MANTISSA_SHIFT))
+ * RADIX_MULTS[(complex>>Res_value::COMPLEX_RADIX_SHIFT)
+ & Res_value::COMPLEX_RADIX_MASK];
+ printf("%f", value);
+
+ if (!isFraction) {
+ switch ((complex>>Res_value::COMPLEX_UNIT_SHIFT)&Res_value::COMPLEX_UNIT_MASK) {
+ case Res_value::COMPLEX_UNIT_PX: printf("px"); break;
+ case Res_value::COMPLEX_UNIT_DIP: printf("dp"); break;
+ case Res_value::COMPLEX_UNIT_SP: printf("sp"); break;
+ case Res_value::COMPLEX_UNIT_PT: printf("pt"); break;
+ case Res_value::COMPLEX_UNIT_IN: printf("in"); break;
+ case Res_value::COMPLEX_UNIT_MM: printf("mm"); break;
+ default: printf(" (unknown unit)"); break;
+ }
+ } else {
+ switch ((complex>>Res_value::COMPLEX_UNIT_SHIFT)&Res_value::COMPLEX_UNIT_MASK) {
+ case Res_value::COMPLEX_UNIT_FRACTION: printf("%%"); break;
+ case Res_value::COMPLEX_UNIT_FRACTION_PARENT: printf("%%p"); break;
+ default: printf(" (unknown unit)"); break;
+ }
+ }
+}
+
+// Normalize a string for output
+String8 ResTable::normalizeForOutput( const char *input )
+{
+ String8 ret;
+ char buff[2];
+ buff[1] = '\0';
+
+ while (*input != '\0') {
+ switch (*input) {
+ // All interesting characters are in the ASCII zone, so we are making our own lives
+ // easier by scanning the string one byte at a time.
+ case '\\':
+ ret += "\\\\";
+ break;
+ case '\n':
+ ret += "\\n";
+ break;
+ case '"':
+ ret += "\\\"";
+ break;
+ default:
+ buff[0] = *input;
+ ret += buff;
+ break;
+ }
+
+ input++;
+ }
+
+ return ret;
+}
+
+void ResTable::print_value(const Package* pkg, const Res_value& value) const
+{
+ if (value.dataType == Res_value::TYPE_NULL) {
+ printf("(null)\n");
+ } else if (value.dataType == Res_value::TYPE_REFERENCE) {
+ printf("(reference) 0x%08x\n", value.data);
+ } else if (value.dataType == Res_value::TYPE_ATTRIBUTE) {
+ printf("(attribute) 0x%08x\n", value.data);
+ } else if (value.dataType == Res_value::TYPE_STRING) {
+ size_t len;
+ const char* str8 = pkg->header->values.string8At(
+ value.data, &len);
+ if (str8 != NULL) {
+ printf("(string8) \"%s\"\n", normalizeForOutput(str8).string());
+ } else {
+ const char16_t* str16 = pkg->header->values.stringAt(
+ value.data, &len);
+ if (str16 != NULL) {
+ printf("(string16) \"%s\"\n",
+ normalizeForOutput(String8(str16, len).string()).string());
+ } else {
+ printf("(string) null\n");
+ }
+ }
+ } else if (value.dataType == Res_value::TYPE_FLOAT) {
+ printf("(float) %g\n", *(const float*)&value.data);
+ } else if (value.dataType == Res_value::TYPE_DIMENSION) {
+ printf("(dimension) ");
+ print_complex(value.data, false);
+ printf("\n");
+ } else if (value.dataType == Res_value::TYPE_FRACTION) {
+ printf("(fraction) ");
+ print_complex(value.data, true);
+ printf("\n");
+ } else if (value.dataType >= Res_value::TYPE_FIRST_COLOR_INT
+ || value.dataType <= Res_value::TYPE_LAST_COLOR_INT) {
+ printf("(color) #%08x\n", value.data);
+ } else if (value.dataType == Res_value::TYPE_INT_BOOLEAN) {
+ printf("(boolean) %s\n", value.data ? "true" : "false");
+ } else if (value.dataType >= Res_value::TYPE_FIRST_INT
+ || value.dataType <= Res_value::TYPE_LAST_INT) {
+ printf("(int) 0x%08x or %d\n", value.data, value.data);
+ } else {
+ printf("(unknown type) t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)\n",
+ (int)value.dataType, (int)value.data,
+ (int)value.size, (int)value.res0);
+ }
+}
+
+void ResTable::print(bool inclValues) const
+{
+ if (mError != 0) {
+ printf("mError=0x%x (%s)\n", mError, strerror(mError));
+ }
+#if 0
+ printf("mParams=%c%c-%c%c,\n",
+ mParams.language[0], mParams.language[1],
+ mParams.country[0], mParams.country[1]);
+#endif
+ size_t pgCount = mPackageGroups.size();
+ printf("Package Groups (%d)\n", (int)pgCount);
+ for (size_t pgIndex=0; pgIndex<pgCount; pgIndex++) {
+ const PackageGroup* pg = mPackageGroups[pgIndex];
+ printf("Package Group %d id=%d packageCount=%d name=%s\n",
+ (int)pgIndex, pg->id, (int)pg->packages.size(),
+ String8(pg->name).string());
+
+ size_t pkgCount = pg->packages.size();
+ for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) {
+ const Package* pkg = pg->packages[pkgIndex];
+ size_t typeCount = pkg->types.size();
+ printf(" Package %d id=%d name=%s typeCount=%d\n", (int)pkgIndex,
+ pkg->package->id, String8(String16(pkg->package->name)).string(),
+ (int)typeCount);
+ for (size_t typeIndex=0; typeIndex<typeCount; typeIndex++) {
+ const Type* typeConfigs = pkg->getType(typeIndex);
+ if (typeConfigs == NULL) {
+ printf(" type %d NULL\n", (int)typeIndex);
+ continue;
+ }
+ const size_t NTC = typeConfigs->configs.size();
+ printf(" type %d configCount=%d entryCount=%d\n",
+ (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount);
+ if (typeConfigs->typeSpecFlags != NULL) {
+ for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) {
+ uint32_t resID = (0xff000000 & ((pkg->package->id)<<24))
+ | (0x00ff0000 & ((typeIndex+1)<<16))
+ | (0x0000ffff & (entryIndex));
+ resource_name resName;
+ if (this->getResourceName(resID, true, &resName)) {
+ String8 type8;
+ String8 name8;
+ if (resName.type8 != NULL) {
+ type8 = String8(resName.type8, resName.typeLen);
+ } else {
+ type8 = String8(resName.type, resName.typeLen);
+ }
+ if (resName.name8 != NULL) {
+ name8 = String8(resName.name8, resName.nameLen);
+ } else {
+ name8 = String8(resName.name, resName.nameLen);
+ }
+ printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n",
+ resID,
+ CHAR16_TO_CSTR(resName.package, resName.packageLen),
+ type8.string(), name8.string(),
+ dtohl(typeConfigs->typeSpecFlags[entryIndex]));
+ } else {
+ printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID);
+ }
+ }
+ }
+ for (size_t configIndex=0; configIndex<NTC; configIndex++) {
+ const ResTable_type* type = typeConfigs->configs[configIndex];
+ if ((((uint64_t)type)&0x3) != 0) {
+ printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type);
+ continue;
+ }
+ String8 configStr = type->config.toString();
+ printf(" config %s:\n", configStr.size() > 0
+ ? configStr.string() : "(default)");
+ size_t entryCount = dtohl(type->entryCount);
+ uint32_t entriesStart = dtohl(type->entriesStart);
+ if ((entriesStart&0x3) != 0) {
+ printf(" NON-INTEGER ResTable_type entriesStart OFFSET: %p\n", (void*)entriesStart);
+ continue;
+ }
+ uint32_t typeSize = dtohl(type->header.size);
+ if ((typeSize&0x3) != 0) {
+ printf(" NON-INTEGER ResTable_type header.size: %p\n", (void*)typeSize);
+ continue;
+ }
+ for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) {
+
+ const uint8_t* const end = ((const uint8_t*)type)
+ + dtohl(type->header.size);
+ const uint32_t* const eindex = (const uint32_t*)
+ (((const uint8_t*)type) + dtohs(type->header.headerSize));
+
+ uint32_t thisOffset = dtohl(eindex[entryIndex]);
+ if (thisOffset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
+
+ uint32_t resID = (0xff000000 & ((pkg->package->id)<<24))
+ | (0x00ff0000 & ((typeIndex+1)<<16))
+ | (0x0000ffff & (entryIndex));
+ resource_name resName;
+ if (this->getResourceName(resID, true, &resName)) {
+ String8 type8;
+ String8 name8;
+ if (resName.type8 != NULL) {
+ type8 = String8(resName.type8, resName.typeLen);
+ } else {
+ type8 = String8(resName.type, resName.typeLen);
+ }
+ if (resName.name8 != NULL) {
+ name8 = String8(resName.name8, resName.nameLen);
+ } else {
+ name8 = String8(resName.name, resName.nameLen);
+ }
+ printf(" resource 0x%08x %s:%s/%s: ", resID,
+ CHAR16_TO_CSTR(resName.package, resName.packageLen),
+ type8.string(), name8.string());
+ } else {
+ printf(" INVALID RESOURCE 0x%08x: ", resID);
+ }
+ if ((thisOffset&0x3) != 0) {
+ printf("NON-INTEGER OFFSET: %p\n", (void*)thisOffset);
+ continue;
+ }
+ if ((thisOffset+sizeof(ResTable_entry)) > typeSize) {
+ printf("OFFSET OUT OF BOUNDS: %p+%p (size is %p)\n",
+ (void*)entriesStart, (void*)thisOffset,
+ (void*)typeSize);
+ continue;
+ }
+
+ const ResTable_entry* ent = (const ResTable_entry*)
+ (((const uint8_t*)type) + entriesStart + thisOffset);
+ if (((entriesStart + thisOffset)&0x3) != 0) {
+ printf("NON-INTEGER ResTable_entry OFFSET: %p\n",
+ (void*)(entriesStart + thisOffset));
+ continue;
+ }
+
+ uint16_t esize = dtohs(ent->size);
+ if ((esize&0x3) != 0) {
+ printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void*)esize);
+ continue;
+ }
+ if ((thisOffset+esize) > typeSize) {
+ printf("ResTable_entry OUT OF BOUNDS: %p+%p+%p (size is %p)\n",
+ (void*)entriesStart, (void*)thisOffset,
+ (void*)esize, (void*)typeSize);
+ continue;
+ }
+
+ const Res_value* valuePtr = NULL;
+ const ResTable_map_entry* bagPtr = NULL;
+ Res_value value;
+ if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
+ printf("<bag>");
+ bagPtr = (const ResTable_map_entry*)ent;
+ } else {
+ valuePtr = (const Res_value*)
+ (((const uint8_t*)ent) + esize);
+ value.copyFrom_dtoh(*valuePtr);
+ printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
+ (int)value.dataType, (int)value.data,
+ (int)value.size, (int)value.res0);
+ }
+
+ if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
+ printf(" (PUBLIC)");
+ }
+ printf("\n");
+
+ if (inclValues) {
+ if (valuePtr != NULL) {
+ printf(" ");
+ print_value(pkg, value);
+ } else if (bagPtr != NULL) {
+ const int N = dtohl(bagPtr->count);
+ const uint8_t* baseMapPtr = (const uint8_t*)ent;
+ size_t mapOffset = esize;
+ const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
+ printf(" Parent=0x%08x, Count=%d\n",
+ dtohl(bagPtr->parent.ident), N);
+ for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) {
+ printf(" #%i (Key=0x%08x): ",
+ i, dtohl(mapPtr->name.ident));
+ value.copyFrom_dtoh(mapPtr->value);
+ print_value(pkg, value);
+ const size_t size = dtohs(mapPtr->value.size);
+ mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value);
+ mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+} // namespace android
diff --git a/libs/androidfw/StreamingZipInflater.cpp b/libs/androidfw/StreamingZipInflater.cpp
new file mode 100644
index 0000000..1dfec23
--- /dev/null
+++ b/libs/androidfw/StreamingZipInflater.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "szipinf"
+#include <utils/Log.h>
+
+#include <androidfw/StreamingZipInflater.h>
+#include <utils/FileMap.h>
+#include <string.h>
+#include <stddef.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+
+/*
+ * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
+ * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
+ * not already defined, then define it here.
+ */
+#ifndef TEMP_FAILURE_RETRY
+/* Used to retry syscalls that can return EINTR. */
+#define TEMP_FAILURE_RETRY(exp) ({ \
+ typeof (exp) _rc; \
+ do { \
+ _rc = (exp); \
+ } while (_rc == -1 && errno == EINTR); \
+ _rc; })
+#endif
+
+static inline size_t min_of(size_t a, size_t b) { return (a < b) ? a : b; }
+
+using namespace android;
+
+/*
+ * Streaming access to compressed asset data in an open fd
+ */
+StreamingZipInflater::StreamingZipInflater(int fd, off64_t compDataStart,
+ size_t uncompSize, size_t compSize) {
+ mFd = fd;
+ mDataMap = NULL;
+ mInFileStart = compDataStart;
+ mOutTotalSize = uncompSize;
+ mInTotalSize = compSize;
+
+ mInBufSize = StreamingZipInflater::INPUT_CHUNK_SIZE;
+ mInBuf = new uint8_t[mInBufSize];
+
+ mOutBufSize = StreamingZipInflater::OUTPUT_CHUNK_SIZE;
+ mOutBuf = new uint8_t[mOutBufSize];
+
+ initInflateState();
+}
+
+/*
+ * Streaming access to compressed data held in an mmapped region of memory
+ */
+StreamingZipInflater::StreamingZipInflater(FileMap* dataMap, size_t uncompSize) {
+ mFd = -1;
+ mDataMap = dataMap;
+ mOutTotalSize = uncompSize;
+ mInTotalSize = dataMap->getDataLength();
+
+ mInBuf = (uint8_t*) dataMap->getDataPtr();
+ mInBufSize = mInTotalSize;
+
+ mOutBufSize = StreamingZipInflater::OUTPUT_CHUNK_SIZE;
+ mOutBuf = new uint8_t[mOutBufSize];
+
+ initInflateState();
+}
+
+StreamingZipInflater::~StreamingZipInflater() {
+ // tear down the in-flight zip state just in case
+ ::inflateEnd(&mInflateState);
+
+ if (mDataMap == NULL) {
+ delete [] mInBuf;
+ }
+ delete [] mOutBuf;
+}
+
+void StreamingZipInflater::initInflateState() {
+ ALOGV("Initializing inflate state");
+
+ memset(&mInflateState, 0, sizeof(mInflateState));
+ mInflateState.zalloc = Z_NULL;
+ mInflateState.zfree = Z_NULL;
+ mInflateState.opaque = Z_NULL;
+ mInflateState.next_in = (Bytef*)mInBuf;
+ mInflateState.next_out = (Bytef*) mOutBuf;
+ mInflateState.avail_out = mOutBufSize;
+ mInflateState.data_type = Z_UNKNOWN;
+
+ mOutLastDecoded = mOutDeliverable = mOutCurPosition = 0;
+ mInNextChunkOffset = 0;
+ mStreamNeedsInit = true;
+
+ if (mDataMap == NULL) {
+ ::lseek(mFd, mInFileStart, SEEK_SET);
+ mInflateState.avail_in = 0; // set when a chunk is read in
+ } else {
+ mInflateState.avail_in = mInBufSize;
+ }
+}
+
+/*
+ * Basic approach:
+ *
+ * 1. If we have undelivered uncompressed data, send it. At this point
+ * either we've satisfied the request, or we've exhausted the available
+ * output data in mOutBuf.
+ *
+ * 2. While we haven't sent enough data to satisfy the request:
+ * 0. if the request is for more data than exists, bail.
+ * a. if there is no input data to decode, read some into the input buffer
+ * and readjust the z_stream input pointers
+ * b. point the output to the start of the output buffer and decode what we can
+ * c. deliver whatever output data we can
+ */
+ssize_t StreamingZipInflater::read(void* outBuf, size_t count) {
+ uint8_t* dest = (uint8_t*) outBuf;
+ size_t bytesRead = 0;
+ size_t toRead = min_of(count, size_t(mOutTotalSize - mOutCurPosition));
+ while (toRead > 0) {
+ // First, write from whatever we already have decoded and ready to go
+ size_t deliverable = min_of(toRead, mOutLastDecoded - mOutDeliverable);
+ if (deliverable > 0) {
+ if (outBuf != NULL) memcpy(dest, mOutBuf + mOutDeliverable, deliverable);
+ mOutDeliverable += deliverable;
+ mOutCurPosition += deliverable;
+ dest += deliverable;
+ bytesRead += deliverable;
+ toRead -= deliverable;
+ }
+
+ // need more data? time to decode some.
+ if (toRead > 0) {
+ // if we don't have any data to decode, read some in. If we're working
+ // from mmapped data this won't happen, because the clipping to total size
+ // will prevent reading off the end of the mapped input chunk.
+ if ((mInflateState.avail_in == 0) && (mDataMap == NULL)) {
+ int err = readNextChunk();
+ if (err < 0) {
+ ALOGE("Unable to access asset data: %d", err);
+ if (!mStreamNeedsInit) {
+ ::inflateEnd(&mInflateState);
+ initInflateState();
+ }
+ return -1;
+ }
+ }
+ // we know we've drained whatever is in the out buffer now, so just
+ // start from scratch there, reading all the input we have at present.
+ mInflateState.next_out = (Bytef*) mOutBuf;
+ mInflateState.avail_out = mOutBufSize;
+
+ /*
+ ALOGV("Inflating to outbuf: avail_in=%u avail_out=%u next_in=%p next_out=%p",
+ mInflateState.avail_in, mInflateState.avail_out,
+ mInflateState.next_in, mInflateState.next_out);
+ */
+ int result = Z_OK;
+ if (mStreamNeedsInit) {
+ ALOGV("Initializing zlib to inflate");
+ result = inflateInit2(&mInflateState, -MAX_WBITS);
+ mStreamNeedsInit = false;
+ }
+ if (result == Z_OK) result = ::inflate(&mInflateState, Z_SYNC_FLUSH);
+ if (result < 0) {
+ // Whoops, inflation failed
+ ALOGE("Error inflating asset: %d", result);
+ ::inflateEnd(&mInflateState);
+ initInflateState();
+ return -1;
+ } else {
+ if (result == Z_STREAM_END) {
+ // we know we have to have reached the target size here and will
+ // not try to read any further, so just wind things up.
+ ::inflateEnd(&mInflateState);
+ }
+
+ // Note how much data we got, and off we go
+ mOutDeliverable = 0;
+ mOutLastDecoded = mOutBufSize - mInflateState.avail_out;
+ }
+ }
+ }
+ return bytesRead;
+}
+
+int StreamingZipInflater::readNextChunk() {
+ assert(mDataMap == NULL);
+
+ if (mInNextChunkOffset < mInTotalSize) {
+ size_t toRead = min_of(mInBufSize, mInTotalSize - mInNextChunkOffset);
+ if (toRead > 0) {
+ ssize_t didRead = TEMP_FAILURE_RETRY(::read(mFd, mInBuf, toRead));
+ //ALOGV("Reading input chunk, size %08x didread %08x", toRead, didRead);
+ if (didRead < 0) {
+ ALOGE("Error reading asset data: %s", strerror(errno));
+ return didRead;
+ } else {
+ mInNextChunkOffset += didRead;
+ mInflateState.next_in = (Bytef*) mInBuf;
+ mInflateState.avail_in = didRead;
+ }
+ }
+ }
+ return 0;
+}
+
+// seeking backwards requires uncompressing fom the beginning, so is very
+// expensive. seeking forwards only requires uncompressing from the current
+// position to the destination.
+off64_t StreamingZipInflater::seekAbsolute(off64_t absoluteInputPosition) {
+ if (absoluteInputPosition < mOutCurPosition) {
+ // rewind and reprocess the data from the beginning
+ if (!mStreamNeedsInit) {
+ ::inflateEnd(&mInflateState);
+ }
+ initInflateState();
+ read(NULL, absoluteInputPosition);
+ } else if (absoluteInputPosition > mOutCurPosition) {
+ read(NULL, absoluteInputPosition - mOutCurPosition);
+ }
+ // else if the target position *is* our current position, do nothing
+ return absoluteInputPosition;
+}
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
new file mode 100644
index 0000000..1ab18ad
--- /dev/null
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+//
+// Read-only access to Zip archives, with minimal heap allocation.
+//
+#define LOG_TAG "zipro"
+//#define LOG_NDEBUG 0
+#include <androidfw/ZipFileRO.h>
+#include <utils/Log.h>
+#include <utils/Compat.h>
+#include <utils/misc.h>
+#include <utils/threads.h>
+#include <ziparchive/zip_archive.h>
+
+#include <zlib.h>
+
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <assert.h>
+#include <unistd.h>
+
+/*
+ * We must open binary files using open(path, ... | O_BINARY) under Windows.
+ * Otherwise strange read errors will happen.
+ */
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+using namespace android;
+
+class _ZipEntryRO {
+public:
+ ZipEntry entry;
+ ZipEntryName name;
+ void *cookie;
+
+ _ZipEntryRO() : cookie(NULL) {
+ }
+
+private:
+ _ZipEntryRO(const _ZipEntryRO& other);
+ _ZipEntryRO& operator=(const _ZipEntryRO& other);
+};
+
+ZipFileRO::~ZipFileRO() {
+ CloseArchive(mHandle);
+ free(mFileName);
+}
+
+/*
+ * Open the specified file read-only. We memory-map the entire thing and
+ * close the file before returning.
+ */
+/* static */ ZipFileRO* ZipFileRO::open(const char* zipFileName)
+{
+ ZipArchiveHandle handle;
+ const int32_t error = OpenArchive(zipFileName, &handle);
+ if (error) {
+ ALOGW("Error opening archive %s: %s", zipFileName, ErrorCodeString(error));
+ return NULL;
+ }
+
+ return new ZipFileRO(handle, strdup(zipFileName));
+}
+
+
+ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const
+{
+ _ZipEntryRO* data = new _ZipEntryRO;
+ const int32_t error = FindEntry(mHandle, entryName, &(data->entry));
+ if (error) {
+ delete data;
+ return NULL;
+ }
+
+ data->name.name = entryName;
+ data->name.name_length = strlen(entryName);
+
+ return (ZipEntryRO) data;
+}
+
+/*
+ * Get the useful fields from the zip entry.
+ *
+ * Returns "false" if the offsets to the fields or the contents of the fields
+ * appear to be bogus.
+ */
+bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
+ size_t* pCompLen, off64_t* pOffset, long* pModWhen, long* pCrc32) const
+{
+ const _ZipEntryRO* zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
+ const ZipEntry& ze = zipEntry->entry;
+
+ if (pMethod != NULL) {
+ *pMethod = ze.method;
+ }
+ if (pUncompLen != NULL) {
+ *pUncompLen = ze.uncompressed_length;
+ }
+ if (pCompLen != NULL) {
+ *pCompLen = ze.compressed_length;
+ }
+ if (pOffset != NULL) {
+ *pOffset = ze.offset;
+ }
+ if (pModWhen != NULL) {
+ *pModWhen = ze.mod_time;
+ }
+ if (pCrc32 != NULL) {
+ *pCrc32 = ze.crc32;
+ }
+
+ return true;
+}
+
+bool ZipFileRO::startIteration(void** cookie)
+{
+ _ZipEntryRO* ze = new _ZipEntryRO;
+ int32_t error = StartIteration(mHandle, &(ze->cookie), NULL /* prefix */);
+ if (error) {
+ ALOGW("Could not start iteration over %s: %s", mFileName, ErrorCodeString(error));
+ delete ze;
+ return false;
+ }
+
+ *cookie = ze;
+ return true;
+}
+
+ZipEntryRO ZipFileRO::nextEntry(void* cookie)
+{
+ _ZipEntryRO* ze = reinterpret_cast<_ZipEntryRO*>(cookie);
+ int32_t error = Next(ze->cookie, &(ze->entry), &(ze->name));
+ if (error) {
+ if (error != -1) {
+ ALOGW("Error iteration over %s: %s", mFileName, ErrorCodeString(error));
+ }
+ return NULL;
+ }
+
+ return &(ze->entry);
+}
+
+void ZipFileRO::endIteration(void* cookie)
+{
+ delete reinterpret_cast<_ZipEntryRO*>(cookie);
+}
+
+void ZipFileRO::releaseEntry(ZipEntryRO entry) const
+{
+ delete reinterpret_cast<_ZipEntryRO*>(entry);
+}
+
+/*
+ * Copy the entry's filename to the buffer.
+ */
+int ZipFileRO::getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen)
+ const
+{
+ const _ZipEntryRO* zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
+ const uint16_t requiredSize = zipEntry->name.name_length + 1;
+
+ if (bufLen < requiredSize) {
+ ALOGW("Buffer too short, requires %d bytes for entry name", requiredSize);
+ return requiredSize;
+ }
+
+ memcpy(buffer, zipEntry->name.name, requiredSize - 1);
+ buffer[requiredSize - 1] = '\0';
+
+ return 0;
+}
+
+/*
+ * Create a new FileMap object that spans the data in "entry".
+ */
+FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const
+{
+ const _ZipEntryRO *zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
+ const ZipEntry& ze = zipEntry->entry;
+ int fd = GetFileDescriptor(mHandle);
+ size_t actualLen = 0;
+
+ if (ze.method == kCompressStored) {
+ actualLen = ze.uncompressed_length;
+ } else {
+ actualLen = ze.compressed_length;
+ }
+
+ FileMap* newMap = new FileMap();
+ if (!newMap->create(mFileName, fd, ze.offset, actualLen, true)) {
+ newMap->release();
+ return NULL;
+ }
+
+ return newMap;
+}
+
+/*
+ * Uncompress an entry, in its entirety, into the provided output buffer.
+ *
+ * This doesn't verify the data's CRC, which might be useful for
+ * uncompressed data. The caller should be able to manage it.
+ */
+bool ZipFileRO::uncompressEntry(ZipEntryRO entry, void* buffer, size_t size) const
+{
+ _ZipEntryRO *zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
+ const int32_t error = ExtractToMemory(mHandle, &(zipEntry->entry),
+ (uint8_t*) buffer, size);
+ if (error) {
+ ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Uncompress an entry, in its entirety, to an open file descriptor.
+ *
+ * This doesn't verify the data's CRC, but probably should.
+ */
+bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const
+{
+ _ZipEntryRO *zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
+ const int32_t error = ExtractEntryToFile(mHandle, &(zipEntry->entry), fd);
+ if (error) {
+ ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error));
+ return false;
+ }
+
+ return true;
+}
diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp
new file mode 100644
index 0000000..e9ac2fe
--- /dev/null
+++ b/libs/androidfw/ZipUtils.cpp
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+//
+// Misc zip/gzip utility functions.
+//
+
+#define LOG_TAG "ziputil"
+
+#include <androidfw/ZipUtils.h>
+#include <androidfw/ZipFileRO.h>
+#include <utils/Log.h>
+#include <utils/Compat.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <zlib.h>
+
+using namespace android;
+
+static inline unsigned long get4LE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+}
+
+
+static const unsigned long kReadBufSize = 32768;
+
+/*
+ * Utility function that expands zip/gzip "deflate" compressed data
+ * into a buffer.
+ *
+ * (This is a clone of the previous function, but it takes a FILE* instead
+ * of an fd. We could pass fileno(fd) to the above, but we can run into
+ * trouble when "fp" has a different notion of what fd's file position is.)
+ *
+ * "fp" is an open file positioned at the start of the "deflate" data
+ * "buf" must hold at least "uncompressedLen" bytes.
+ */
+/*static*/ template<typename T> bool inflateToBuffer(T& reader, void* buf,
+ long uncompressedLen, long compressedLen)
+{
+ bool result = false;
+
+ z_stream zstream;
+ int zerr;
+ unsigned long compRemaining;
+
+ assert(uncompressedLen >= 0);
+ assert(compressedLen >= 0);
+
+ compRemaining = compressedLen;
+
+ /*
+ * Initialize the zlib stream.
+ */
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.zalloc = Z_NULL;
+ zstream.zfree = Z_NULL;
+ zstream.opaque = Z_NULL;
+ zstream.next_in = NULL;
+ zstream.avail_in = 0;
+ zstream.next_out = (Bytef*) buf;
+ zstream.avail_out = uncompressedLen;
+ zstream.data_type = Z_UNKNOWN;
+
+ /*
+ * Use the undocumented "negative window bits" feature to tell zlib
+ * that there's no zlib header waiting for it.
+ */
+ zerr = inflateInit2(&zstream, -MAX_WBITS);
+ if (zerr != Z_OK) {
+ if (zerr == Z_VERSION_ERROR) {
+ ALOGE("Installed zlib is not compatible with linked version (%s)\n",
+ ZLIB_VERSION);
+ } else {
+ ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
+ }
+ goto bail;
+ }
+
+ /*
+ * Loop while we have data.
+ */
+ do {
+ unsigned long getSize;
+
+ /* read as much as we can */
+ if (zstream.avail_in == 0) {
+ getSize = (compRemaining > kReadBufSize) ?
+ kReadBufSize : compRemaining;
+ ALOGV("+++ reading %ld bytes (%ld left)\n",
+ getSize, compRemaining);
+
+ unsigned char* nextBuffer = NULL;
+ const unsigned long nextSize = reader.read(&nextBuffer, getSize);
+
+ if (nextSize < getSize || nextBuffer == NULL) {
+ ALOGD("inflate read failed (%ld vs %ld)\n", nextSize, getSize);
+ goto z_bail;
+ }
+
+ compRemaining -= nextSize;
+
+ zstream.next_in = nextBuffer;
+ zstream.avail_in = nextSize;
+ }
+
+ /* uncompress the data */
+ zerr = inflate(&zstream, Z_NO_FLUSH);
+ if (zerr != Z_OK && zerr != Z_STREAM_END) {
+ ALOGD("zlib inflate call failed (zerr=%d)\n", zerr);
+ goto z_bail;
+ }
+
+ /* output buffer holds all, so no need to write the output */
+ } while (zerr == Z_OK);
+
+ assert(zerr == Z_STREAM_END); /* other errors should've been caught */
+
+ if ((long) zstream.total_out != uncompressedLen) {
+ ALOGW("Size mismatch on inflated file (%ld vs %ld)\n",
+ zstream.total_out, uncompressedLen);
+ goto z_bail;
+ }
+
+ // success!
+ result = true;
+
+z_bail:
+ inflateEnd(&zstream); /* free up any allocated structures */
+
+bail:
+ return result;
+}
+
+class FileReader {
+public:
+ FileReader(FILE* fp) :
+ mFp(fp), mReadBuf(new unsigned char[kReadBufSize])
+ {
+ }
+
+ ~FileReader() {
+ delete[] mReadBuf;
+ }
+
+ long read(unsigned char** nextBuffer, long readSize) const {
+ *nextBuffer = mReadBuf;
+ return fread(mReadBuf, 1, readSize, mFp);
+ }
+
+ FILE* mFp;
+ unsigned char* mReadBuf;
+};
+
+class FdReader {
+public:
+ FdReader(int fd) :
+ mFd(fd), mReadBuf(new unsigned char[kReadBufSize])
+ {
+ }
+
+ ~FdReader() {
+ delete[] mReadBuf;
+ }
+
+ long read(unsigned char** nextBuffer, long readSize) const {
+ *nextBuffer = mReadBuf;
+ return TEMP_FAILURE_RETRY(::read(mFd, mReadBuf, readSize));
+ }
+
+ int mFd;
+ unsigned char* mReadBuf;
+};
+
+class BufferReader {
+public:
+ BufferReader(void* input, size_t inputSize) :
+ mInput(reinterpret_cast<unsigned char*>(input)),
+ mInputSize(inputSize),
+ mBufferReturned(false)
+ {
+ }
+
+ long read(unsigned char** nextBuffer, long readSize) {
+ if (!mBufferReturned) {
+ mBufferReturned = true;
+ *nextBuffer = mInput;
+ return mInputSize;
+ }
+
+ *nextBuffer = NULL;
+ return 0;
+ }
+
+ unsigned char* mInput;
+ const size_t mInputSize;
+ bool mBufferReturned;
+};
+
+/*static*/ bool ZipUtils::inflateToBuffer(FILE* fp, void* buf,
+ long uncompressedLen, long compressedLen)
+{
+ FileReader reader(fp);
+ return ::inflateToBuffer<FileReader>(reader, buf,
+ uncompressedLen, compressedLen);
+}
+
+/*static*/ bool ZipUtils::inflateToBuffer(int fd, void* buf,
+ long uncompressedLen, long compressedLen)
+{
+ FdReader reader(fd);
+ return ::inflateToBuffer<FdReader>(reader, buf,
+ uncompressedLen, compressedLen);
+}
+
+/*static*/ bool ZipUtils::inflateToBuffer(void* in, void* buf,
+ long uncompressedLen, long compressedLen)
+{
+ BufferReader reader(in, compressedLen);
+ return ::inflateToBuffer<BufferReader>(reader, buf,
+ uncompressedLen, compressedLen);
+}
+
+
+
+/*
+ * Look at the contents of a gzip archive. We want to know where the
+ * data starts, and how long it will be after it is uncompressed.
+ *
+ * We expect to find the CRC and length as the last 8 bytes on the file.
+ * This is a pretty reasonable thing to expect for locally-compressed
+ * files, but there's a small chance that some extra padding got thrown
+ * on (the man page talks about compressed data written to tape). We
+ * don't currently deal with that here. If "gzip -l" whines, we're going
+ * to fail too.
+ *
+ * On exit, "fp" is pointing at the start of the compressed data.
+ */
+/*static*/ bool ZipUtils::examineGzip(FILE* fp, int* pCompressionMethod,
+ long* pUncompressedLen, long* pCompressedLen, unsigned long* pCRC32)
+{
+ enum { // flags
+ FTEXT = 0x01,
+ FHCRC = 0x02,
+ FEXTRA = 0x04,
+ FNAME = 0x08,
+ FCOMMENT = 0x10,
+ };
+ int ic;
+ int method, flags;
+ int i;
+
+ ic = getc(fp);
+ if (ic != 0x1f || getc(fp) != 0x8b)
+ return false; // not gzip
+ method = getc(fp);
+ flags = getc(fp);
+
+ /* quick sanity checks */
+ if (method == EOF || flags == EOF)
+ return false;
+ if (method != ZipFileRO::kCompressDeflated)
+ return false;
+
+ /* skip over 4 bytes of mod time, 1 byte XFL, 1 byte OS */
+ for (i = 0; i < 6; i++)
+ (void) getc(fp);
+ /* consume "extra" field, if present */
+ if ((flags & FEXTRA) != 0) {
+ int len;
+
+ len = getc(fp);
+ len |= getc(fp) << 8;
+ while (len-- && getc(fp) != EOF)
+ ;
+ }
+ /* consume filename, if present */
+ if ((flags & FNAME) != 0) {
+ do {
+ ic = getc(fp);
+ } while (ic != 0 && ic != EOF);
+ }
+ /* consume comment, if present */
+ if ((flags & FCOMMENT) != 0) {
+ do {
+ ic = getc(fp);
+ } while (ic != 0 && ic != EOF);
+ }
+ /* consume 16-bit header CRC, if present */
+ if ((flags & FHCRC) != 0) {
+ (void) getc(fp);
+ (void) getc(fp);
+ }
+
+ if (feof(fp) || ferror(fp))
+ return false;
+
+ /* seek to the end; CRC and length are in the last 8 bytes */
+ long curPosn = ftell(fp);
+ unsigned char buf[8];
+ fseek(fp, -8, SEEK_END);
+ *pCompressedLen = ftell(fp) - curPosn;
+
+ if (fread(buf, 1, 8, fp) != 8)
+ return false;
+ /* seek back to start of compressed data */
+ fseek(fp, curPosn, SEEK_SET);
+
+ *pCompressionMethod = method;
+ *pCRC32 = get4LE(&buf[0]);
+ *pUncompressedLen = get4LE(&buf[4]);
+
+ return true;
+}
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
new file mode 100644
index 0000000..29686ef
--- /dev/null
+++ b/libs/androidfw/misc.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2005 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.
+ */
+
+#define LOG_TAG "misc"
+
+//
+// Miscellaneous utility functions.
+//
+#include <androidfw/misc.h>
+
+#include <sys/stat.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+
+using namespace android;
+
+namespace android {
+
+/*
+ * Get a file's type.
+ */
+FileType getFileType(const char* fileName)
+{
+ struct stat sb;
+
+ if (stat(fileName, &sb) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return kFileTypeNonexistent;
+ else {
+ fprintf(stderr, "getFileType got errno=%d on '%s'\n",
+ errno, fileName);
+ return kFileTypeUnknown;
+ }
+ } else {
+ if (S_ISREG(sb.st_mode))
+ return kFileTypeRegular;
+ else if (S_ISDIR(sb.st_mode))
+ return kFileTypeDirectory;
+ else if (S_ISCHR(sb.st_mode))
+ return kFileTypeCharDev;
+ else if (S_ISBLK(sb.st_mode))
+ return kFileTypeBlockDev;
+ else if (S_ISFIFO(sb.st_mode))
+ return kFileTypeFifo;
+#ifdef HAVE_SYMLINKS
+ else if (S_ISLNK(sb.st_mode))
+ return kFileTypeSymlink;
+ else if (S_ISSOCK(sb.st_mode))
+ return kFileTypeSocket;
+#endif
+ else
+ return kFileTypeUnknown;
+ }
+}
+
+/*
+ * Get a file's modification date.
+ */
+time_t getFileModDate(const char* fileName)
+{
+ struct stat sb;
+
+ if (stat(fileName, &sb) < 0)
+ return (time_t) -1;
+
+ return sb.st_mtime;
+}
+
+}; // namespace android
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
new file mode 100644
index 0000000..6e6522c
--- /dev/null
+++ b/libs/androidfw/tests/Android.mk
@@ -0,0 +1,32 @@
+# Build the unit tests.
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# Build the unit tests.
+test_src_files := \
+ BackupData_test.cpp \
+ ObbFile_test.cpp \
+ ZipUtils_test.cpp
+
+shared_libraries := \
+ libandroidfw \
+ libcutils \
+ libutils \
+ libui \
+ libstlport
+
+static_libraries := \
+ libgtest \
+ libgtest_main
+
+$(foreach file,$(test_src_files), \
+ $(eval include $(CLEAR_VARS)) \
+ $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
+ $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
+ $(eval LOCAL_SRC_FILES := $(file)) \
+ $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+ $(eval include $(BUILD_NATIVE_TEST)) \
+)
+
+# Build the manual test programs.
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libs/androidfw/tests/BackupData_test.cpp b/libs/androidfw/tests/BackupData_test.cpp
new file mode 100644
index 0000000..17f91ca
--- /dev/null
+++ b/libs/androidfw/tests/BackupData_test.cpp
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#define LOG_TAG "ObbFile_test"
+#include <androidfw/BackupHelpers.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+namespace android {
+
+#define TEST_FILENAME "/test.bd"
+
+// keys of different lengths to test padding
+#define KEY1 "key1"
+#define KEY2 "key2a"
+#define KEY3 "key3bc"
+#define KEY4 "key4def"
+
+// payloads of different lengths to test padding
+#define DATA1 "abcdefg"
+#define DATA2 "hijklmnopq"
+#define DATA3 "rstuvwxyz"
+// KEY4 is only ever deleted
+
+class BackupDataTest : public testing::Test {
+protected:
+ char* m_external_storage;
+ char* m_filename;
+ String8 mKey1;
+ String8 mKey2;
+ String8 mKey3;
+ String8 mKey4;
+
+ virtual void SetUp() {
+ m_external_storage = getenv("EXTERNAL_STORAGE");
+
+ const int totalLen = strlen(m_external_storage) + strlen(TEST_FILENAME) + 1;
+ m_filename = new char[totalLen];
+ snprintf(m_filename, totalLen, "%s%s", m_external_storage, TEST_FILENAME);
+
+ ::unlink(m_filename);
+ int fd = ::open(m_filename, O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ FAIL() << "Couldn't create " << m_filename << " for writing";
+ }
+ mKey1 = String8(KEY1);
+ mKey2 = String8(KEY2);
+ mKey3 = String8(KEY3);
+ mKey4 = String8(KEY4);
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+TEST_F(BackupDataTest, WriteAndReadSingle) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+
+ EXPECT_EQ(NO_ERROR, writer->WriteEntityHeader(mKey1, sizeof(DATA1)))
+ << "WriteEntityHeader returned an error";
+ EXPECT_EQ(NO_ERROR, writer->WriteEntityData(DATA1, sizeof(DATA1)))
+ << "WriteEntityData returned an error";
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+ EXPECT_EQ(NO_ERROR, reader->Status())
+ << "Reader ctor failed";
+
+ bool done;
+ int type;
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader";
+
+ String8 key;
+ size_t dataSize;
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error";
+ EXPECT_EQ(mKey1, key)
+ << "wrong key from ReadEntityHeader";
+ EXPECT_EQ(sizeof(DATA1), dataSize)
+ << "wrong size from ReadEntityHeader";
+
+ char* dataBytes = new char[dataSize];
+ EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error";
+ for (unsigned int i = 0; i < sizeof(DATA1); i++) {
+ EXPECT_EQ(DATA1[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, WriteAndReadMultiple) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, sizeof(DATA2));
+ writer->WriteEntityData(DATA2, sizeof(DATA2));
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // read and verify second entity
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(sizeof(DATA2), dataSize)
+ << "wrong size from ReadEntityHeader on second entity";
+
+ dataBytes = new char[dataSize];
+ EXPECT_EQ((int)dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error on second entity";
+ for (unsigned int i = 0; i < sizeof(DATA2); i++) {
+ EXPECT_EQ(DATA2[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, SkipEntity) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, sizeof(DATA2));
+ writer->WriteEntityData(DATA2, sizeof(DATA2));
+ writer->WriteEntityHeader(mKey3, sizeof(DATA3));
+ writer->WriteEntityData(DATA3, sizeof(DATA3));
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // skip second entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ reader->SkipEntityData();
+
+ // read and verify third entity
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader after skip";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on third entity";
+ EXPECT_EQ(mKey3, key)
+ << "wrong key from ReadEntityHeader on third entity";
+ EXPECT_EQ(sizeof(DATA3), dataSize)
+ << "wrong size from ReadEntityHeader on third entity";
+
+ dataBytes = new char[dataSize];
+ EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error on third entity";
+ for (unsigned int i = 0; i < sizeof(DATA3); i++) {
+ EXPECT_EQ(DATA3[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, DeleteEntity) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, -1);
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // read and verify deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader on deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on second entity";
+
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, EneityAfterDelete) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, -1);
+ writer->WriteEntityHeader(mKey3, sizeof(DATA3));
+ writer->WriteEntityData(DATA3, sizeof(DATA3));
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // read and verify deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader on deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int)dataSize)
+ << "not recognizing deletion on second entity";
+
+ // read and verify third entity
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader after deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on third entity";
+ EXPECT_EQ(mKey3, key)
+ << "wrong key from ReadEntityHeader on third entity";
+ EXPECT_EQ(sizeof(DATA3), dataSize)
+ << "wrong size from ReadEntityHeader on third entity";
+
+ dataBytes = new char[dataSize];
+ EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error on third entity";
+ for (unsigned int i = 0; i < sizeof(DATA3); i++) {
+ EXPECT_EQ(DATA3[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, OnlyDeleteEntities) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, -1);
+ writer->WriteEntityHeader(mKey2, -1);
+ writer->WriteEntityHeader(mKey3, -1);
+ writer->WriteEntityHeader(mKey4, -1);
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ // read and verify first deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader first deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on first entity";
+ EXPECT_EQ(mKey1, key)
+ << "wrong key from ReadEntityHeader on first entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on first entity";
+
+ // read and verify second deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader second deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on second entity";
+
+ // read and verify third deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader third deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on third entity";
+ EXPECT_EQ(mKey3, key)
+ << "wrong key from ReadEntityHeader on third entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on third entity";
+
+ // read and verify fourth deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader fourth deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on fourth entity";
+ EXPECT_EQ(mKey4, key)
+ << "wrong key from ReadEntityHeader on fourth entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on fourth entity";
+
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, ReadDeletedEntityData) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, -1);
+ writer->WriteEntityHeader(mKey2, -1);
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ // read and verify first deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader first deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on first entity";
+ EXPECT_EQ(mKey1, key)
+ << "wrong key from ReadEntityHeader on first entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on first entity";
+
+ // erroneously try to read first entity data
+ char* dataBytes = new char[10];
+ dataBytes[0] = 'A';
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityData(dataBytes, dataSize));
+ // expect dataBytes to be unmodofied
+ EXPECT_EQ('A', dataBytes[0]);
+
+ // read and verify second deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader second deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on second entity";
+
+ delete writer;
+ delete reader;
+}
+
+}
diff --git a/libs/androidfw/tests/ObbFile_test.cpp b/libs/androidfw/tests/ObbFile_test.cpp
new file mode 100644
index 0000000..2c9f650
--- /dev/null
+++ b/libs/androidfw/tests/ObbFile_test.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#define LOG_TAG "ObbFile_test"
+#include <androidfw/ObbFile.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+namespace android {
+
+#define TEST_FILENAME "/test.obb"
+
+class ObbFileTest : public testing::Test {
+protected:
+ sp<ObbFile> mObbFile;
+ char* mExternalStorage;
+ char* mFileName;
+
+ virtual void SetUp() {
+ mObbFile = new ObbFile();
+ mExternalStorage = getenv("EXTERNAL_STORAGE");
+
+ const int totalLen = strlen(mExternalStorage) + strlen(TEST_FILENAME) + 1;
+ mFileName = new char[totalLen];
+ snprintf(mFileName, totalLen, "%s%s", mExternalStorage, TEST_FILENAME);
+
+ int fd = ::open(mFileName, O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ FAIL() << "Couldn't create " << mFileName << " for tests";
+ }
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+TEST_F(ObbFileTest, ReadFailure) {
+ EXPECT_FALSE(mObbFile->readFrom(-1))
+ << "No failure on invalid file descriptor";
+}
+
+TEST_F(ObbFileTest, WriteThenRead) {
+ const char* packageName = "com.example.obbfile";
+ const int32_t versionNum = 1;
+
+ mObbFile->setPackageName(String8(packageName));
+ mObbFile->setVersion(versionNum);
+#define SALT_SIZE 8
+ unsigned char salt[SALT_SIZE] = {0x01, 0x10, 0x55, 0xAA, 0xFF, 0x00, 0x5A, 0xA5};
+ EXPECT_TRUE(mObbFile->setSalt(salt, SALT_SIZE))
+ << "Salt should be successfully set";
+
+ EXPECT_TRUE(mObbFile->writeTo(mFileName))
+ << "couldn't write to fake .obb file";
+
+ mObbFile = new ObbFile();
+
+ EXPECT_TRUE(mObbFile->readFrom(mFileName))
+ << "couldn't read from fake .obb file";
+
+ EXPECT_EQ(versionNum, mObbFile->getVersion())
+ << "version didn't come out the same as it went in";
+ const char* currentPackageName = mObbFile->getPackageName().string();
+ EXPECT_STREQ(packageName, currentPackageName)
+ << "package name didn't come out the same as it went in";
+
+ size_t saltLen;
+ const unsigned char* newSalt = mObbFile->getSalt(&saltLen);
+
+ EXPECT_EQ(sizeof(salt), saltLen)
+ << "salt sizes were not the same";
+
+ for (int i = 0; i < sizeof(salt); i++) {
+ EXPECT_EQ(salt[i], newSalt[i])
+ << "salt character " << i << " should be equal";
+ }
+ EXPECT_TRUE(memcmp(newSalt, salt, sizeof(salt)) == 0)
+ << "salts should be the same";
+}
+
+}
diff --git a/libs/androidfw/tests/ZipUtils_test.cpp b/libs/androidfw/tests/ZipUtils_test.cpp
new file mode 100644
index 0000000..c6038b5
--- /dev/null
+++ b/libs/androidfw/tests/ZipUtils_test.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "ZipUtils_test"
+#include <utils/Log.h>
+#include <androidfw/ZipUtils.h>
+
+#include <gtest/gtest.h>
+
+#include <fcntl.h>
+#include <string.h>
+
+namespace android {
+
+class ZipUtilsTest : public testing::Test {
+protected:
+ virtual void SetUp() {
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+TEST_F(ZipUtilsTest, ZipTimeConvertSuccess) {
+ struct tm t;
+
+ // 2011-06-29 14:40:40
+ long when = 0x3EDD7514;
+
+ ZipUtils::zipTimeToTimespec(when, &t);
+
+ EXPECT_EQ(2011, t.tm_year + 1900)
+ << "Year was improperly converted.";
+
+ EXPECT_EQ(6, t.tm_mon)
+ << "Month was improperly converted.";
+
+ EXPECT_EQ(29, t.tm_mday)
+ << "Day was improperly converted.";
+
+ EXPECT_EQ(14, t.tm_hour)
+ << "Hour was improperly converted.";
+
+ EXPECT_EQ(40, t.tm_min)
+ << "Minute was improperly converted.";
+
+ EXPECT_EQ(40, t.tm_sec)
+ << "Second was improperly converted.";
+}
+
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 3ebb0ed..1dcb459 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -21,6 +21,9 @@
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.view.Surface;
import java.io.IOException;
@@ -173,6 +176,33 @@
*/
public static final int BUFFER_FLAG_END_OF_STREAM = 4;
+ private EventHandler mEventHandler;
+ private NotificationCallback mNotificationCallback;
+
+ static final int EVENT_NOTIFY = 1;
+
+ private class EventHandler extends Handler {
+ private MediaCodec mCodec;
+
+ public EventHandler(MediaCodec codec, Looper looper) {
+ super(looper);
+ mCodec = codec;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_NOTIFY:
+ {
+ if (mNotificationCallback != null) {
+ mNotificationCallback.onCodecNotify(mCodec);
+ }
+ break;
+ }
+ }
+ }
+ }
+
/**
* Instantiate a decoder supporting input data of the given mime type.
*
@@ -228,6 +258,15 @@
private MediaCodec(
String name, boolean nameIsType, boolean encoder) {
+ Looper looper;
+ if ((looper = Looper.myLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else {
+ mEventHandler = null;
+ }
+
native_setup(name, nameIsType, encoder);
}
@@ -308,7 +347,15 @@
* To ensure that it is available to other client call {@link #release}
* and don't just rely on garbage collection to eventually do this for you.
*/
- public native final void stop();
+ public final void stop() {
+ native_stop();
+
+ if (mEventHandler != null) {
+ mEventHandler.removeMessages(EVENT_NOTIFY);
+ }
+ }
+
+ private native final void native_stop();
/**
* Flush both input and output ports of the component, all indices
@@ -639,6 +686,22 @@
setParameters(keys, values);
}
+ public void setNotificationCallback(NotificationCallback cb) {
+ mNotificationCallback = cb;
+ }
+
+ public interface NotificationCallback {
+ void onCodecNotify(MediaCodec codec);
+ }
+
+ private void postEventFromNative(
+ int what, int arg1, int arg2, Object obj) {
+ if (mEventHandler != null) {
+ Message msg = mEventHandler.obtainMessage(what, arg1, arg2, obj);
+ mEventHandler.sendMessage(msg);
+ }
+ }
+
private native final void setParameters(String[] keys, Object[] values);
/**
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 221ea57..3ce483d 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -51,6 +51,10 @@
DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED = -3,
};
+enum {
+ EVENT_NOTIFY = 1,
+};
+
struct CryptoErrorCodes {
jint cryptoErrorNoKey;
jint cryptoErrorKeyExpired;
@@ -59,6 +63,7 @@
struct fields_t {
jfieldID context;
+ jmethodID postEventFromNativeID;
jfieldID cryptoInfoNumSubSamplesID;
jfieldID cryptoInfoNumBytesOfClearDataID;
jfieldID cryptoInfoNumBytesOfEncryptedDataID;
@@ -75,7 +80,9 @@
JNIEnv *env, jobject thiz,
const char *name, bool nameIsType, bool encoder)
: mClass(NULL),
- mObject(NULL) {
+ mObject(NULL),
+ mGeneration(1),
+ mRequestedActivityNotification(false) {
jclass clazz = env->GetObjectClass(thiz);
CHECK(clazz != NULL);
@@ -87,7 +94,7 @@
mLooper->start(
false, // runOnCallingThread
- false, // canCallJava
+ true, // canCallJava
PRIORITY_FOREGROUND);
if (nameIsType) {
@@ -101,6 +108,10 @@
return mCodec != NULL ? OK : NO_INIT;
}
+void JMediaCodec::registerSelf() {
+ mLooper->registerHandler(this);
+}
+
JMediaCodec::~JMediaCodec() {
if (mCodec != NULL) {
mCodec->release();
@@ -122,7 +133,8 @@
int flags) {
sp<Surface> client;
if (bufferProducer != NULL) {
- mSurfaceTextureClient = new Surface(bufferProducer, true /* controlledByApp */);
+ mSurfaceTextureClient =
+ new Surface(bufferProducer, true /* controlledByApp */);
} else {
mSurfaceTextureClient.clear();
}
@@ -136,13 +148,32 @@
}
status_t JMediaCodec::start() {
- return mCodec->start();
+ status_t err = mCodec->start();
+
+ if (err != OK) {
+ return err;
+ }
+
+ mActivityNotification = new AMessage(kWhatActivityNotify, id());
+ mActivityNotification->setInt32("generation", mGeneration);
+
+ requestActivityNotification();
+
+ return err;
}
status_t JMediaCodec::stop() {
mSurfaceTextureClient.clear();
- return mCodec->stop();
+ status_t err = mCodec->stop();
+
+ sp<AMessage> msg = new AMessage(kWhatStopActivityNotifications, id());
+ sp<AMessage> response;
+ msg->postAndAwaitResponse(&response);
+
+ mActivityNotification.clear();
+
+ return err;
}
status_t JMediaCodec::flush() {
@@ -174,7 +205,11 @@
}
status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
- return mCodec->dequeueInputBuffer(index, timeoutUs);
+ status_t err = mCodec->dequeueInputBuffer(index, timeoutUs);
+
+ requestActivityNotification();
+
+ return err;
}
status_t JMediaCodec::dequeueOutputBuffer(
@@ -182,9 +217,12 @@
size_t size, offset;
int64_t timeUs;
uint32_t flags;
- status_t err;
- if ((err = mCodec->dequeueOutputBuffer(
- index, &offset, &size, &timeUs, &flags, timeoutUs)) != OK) {
+ status_t err = mCodec->dequeueOutputBuffer(
+ index, &offset, &size, &timeUs, &flags, timeoutUs);
+
+ requestActivityNotification();
+
+ if (err != OK) {
return err;
}
@@ -320,6 +358,67 @@
}
}
+void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRequestActivityNotifications:
+ {
+ if (mRequestedActivityNotification) {
+ break;
+ }
+
+ mCodec->requestActivityNotification(mActivityNotification);
+ mRequestedActivityNotification = true;
+ break;
+ }
+
+ case kWhatActivityNotify:
+ {
+ {
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mGeneration) {
+ // stale
+ break;
+ }
+
+ mRequestedActivityNotification = false;
+ }
+
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(
+ mObject,
+ gFields.postEventFromNativeID,
+ EVENT_NOTIFY,
+ 0 /* arg1 */,
+ 0 /* arg2 */,
+ NULL /* obj */);
+
+ break;
+ }
+
+ case kWhatStopActivityNotifications:
+ {
+ uint32_t replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ ++mGeneration;
+ mRequestedActivityNotification = false;
+
+ sp<AMessage> response = new AMessage;
+ response->postReply(replyID);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+void JMediaCodec::requestActivityNotification() {
+ (new AMessage(kWhatRequestActivityNotifications, id()))->post();
+}
+
} // namespace android
////////////////////////////////////////////////////////////////////////////////
@@ -888,6 +987,12 @@
gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J");
CHECK(gFields.context != NULL);
+ gFields.postEventFromNativeID =
+ env->GetMethodID(
+ clazz.get(), "postEventFromNative", "(IIILjava/lang/Object;)V");
+
+ CHECK(gFields.postEventFromNativeID != NULL);
+
clazz.reset(env->FindClass("android/media/MediaCodec$CryptoInfo"));
CHECK(clazz.get() != NULL);
@@ -961,6 +1066,8 @@
return;
}
+ codec->registerSelf();
+
setMediaCodec(env,thiz, codec);
}
@@ -981,7 +1088,7 @@
(void *)android_media_MediaCodec_createInputSurface },
{ "start", "()V", (void *)android_media_MediaCodec_start },
- { "stop", "()V", (void *)android_media_MediaCodec_stop },
+ { "native_stop", "()V", (void *)android_media_MediaCodec_stop },
{ "flush", "()V", (void *)android_media_MediaCodec_flush },
{ "queueInputBuffer", "(IIIJI)V",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 2fbbd72..53254c9 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -21,8 +21,8 @@
#include <media/hardware/CryptoAPI.h>
#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandler.h>
#include <utils/Errors.h>
-#include <utils/RefBase.h>
namespace android {
@@ -34,13 +34,15 @@
struct MediaCodec;
class Surface;
-struct JMediaCodec : public RefBase {
+struct JMediaCodec : public AHandler {
JMediaCodec(
JNIEnv *env, jobject thiz,
const char *name, bool nameIsType, bool encoder);
status_t initCheck() const;
+ void registerSelf();
+
status_t configure(
const sp<AMessage> &format,
const sp<IGraphicBufferProducer> &bufferProducer,
@@ -94,7 +96,15 @@
protected:
virtual ~JMediaCodec();
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
private:
+ enum {
+ kWhatActivityNotify,
+ kWhatRequestActivityNotifications,
+ kWhatStopActivityNotifications,
+ };
+
jclass mClass;
jweak mObject;
sp<Surface> mSurfaceTextureClient;
@@ -102,6 +112,12 @@
sp<ALooper> mLooper;
sp<MediaCodec> mCodec;
+ sp<AMessage> mActivityNotification;
+ int32_t mGeneration;
+ bool mRequestedActivityNotification;
+
+ void requestActivityNotification();
+
DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
};
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
index 82b5467..ca4f811 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
@@ -272,7 +272,7 @@
private boolean checkPuk() {
// make sure the puk is at least 8 digits long.
- if (mPasswordEntry.getText().length() >= 8) {
+ if (mPasswordEntry.getText().length() == 8) {
mPukText = mPasswordEntry.getText().toString();
return true;
}
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 0fa9d4c..12deaef 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -198,7 +198,7 @@
<string name="quick_settings_wifi_not_connected" msgid="7171904845345573431">"Ikke tilkoblet"</string>
<string name="quick_settings_wifi_no_network" msgid="2221993077220856376">"Ingen nettverk"</string>
<string name="quick_settings_wifi_off_label" msgid="7558778100843885864">"Wi-Fi er av"</string>
- <string name="quick_settings_remote_display_no_connection_label" msgid="372107699274391290">"Send skjermen"</string>
+ <string name="quick_settings_remote_display_no_connection_label" msgid="372107699274391290">"Cast skjermen"</string>
<string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Lysstyrke"</string>
<string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTO"</string>
<string name="quick_settings_inversion_label" msgid="1666358784283020762">"Modus for fargeinvertering"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 1a6e06f..9ed493c 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -174,7 +174,7 @@
<string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string>
<string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Hali ya ndege"</string>
<string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Inachaji, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="quick_settings_battery_charged_label" msgid="8865413079414246081">"Imechajiwa"</string>
+ <string name="quick_settings_battery_charged_label" msgid="8865413079414246081">"Betri imejaa"</string>
<string name="quick_settings_bluetooth_label" msgid="6304190285170721401">"Bluetooth"</string>
<string name="quick_settings_bluetooth_multiple_devices_label" msgid="3912245565613684735">"Bluetooth (Vifaa <xliff:g id="NUMBER">%d</xliff:g>)"</string>
<string name="quick_settings_bluetooth_off_label" msgid="8159652146149219937">"Bluetooth Imezimwa"</string>
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 6574898..64ab3e4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2435,9 +2435,9 @@
"\n car=" + car);
}
} else {
- if (DBG) {
- log("handleConnectivityChange: address are the same reset per doReset" +
- " linkProperty[" + netType + "]:" +
+ if (VDBG) {
+ log("handleConnectivityChange: addresses are the same reset per" +
+ " doReset linkProperty[" + netType + "]:" +
" resetMask=" + resetMask);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 6c2128b..6115f06 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1525,9 +1525,6 @@
} else {
ActivityOptions.abort(options);
}
- if (r.task == null) Slog.v(TAG,
- "startActivityUncheckedLocked: task left null",
- new RuntimeException("here").fillInStackTrace());
return ActivityManager.START_RETURN_INTENT_TO_CALLER;
}
if ((launchFlags &
@@ -1620,9 +1617,6 @@
} else {
ActivityOptions.abort(options);
}
- if (r.task == null) Slog.v(TAG,
- "startActivityUncheckedLocked: task left null",
- new RuntimeException("here").fillInStackTrace());
return ActivityManager.START_TASK_TO_FRONT;
}
}
@@ -1727,9 +1721,6 @@
targetStack.resumeTopActivityLocked(null);
}
ActivityOptions.abort(options);
- if (r.task == null) Slog.w(TAG,
- "startActivityUncheckedLocked: task left null",
- new RuntimeException("here").fillInStackTrace());
return ActivityManager.START_DELIVERED_TO_TOP;
}
} else if (!addingToTask &&
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 1f0fc3a0..10574ed 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -392,14 +392,15 @@
origBase.makeInactive();
}
baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid,
- processName);
+ info.versionCode, processName);
baseProcessTracker.makeActive();
for (int i=0; i<pkgList.size(); i++) {
ProcessStats.ProcessState ps = pkgList.valueAt(i);
if (ps != null && ps != origBase) {
ps.makeInactive();
}
- ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid, processName);
+ ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
+ info.versionCode, processName);
if (ps != baseProcessTracker) {
ps.makeActive();
}
@@ -572,7 +573,7 @@
if (!pkgList.containsKey(pkg)) {
if (baseProcessTracker != null) {
ProcessStats.ProcessState state = tracker.getProcessStateLocked(
- pkg, info.uid, processName);
+ pkg, info.uid, info.versionCode, processName);
pkgList.put(pkg, state);
if (state != baseProcessTracker) {
state.makeActive();
@@ -619,7 +620,7 @@
}
pkgList.clear();
ProcessStats.ProcessState ps = tracker.getProcessStateLocked(
- info.packageName, info.uid, processName);
+ info.packageName, info.uid, info.versionCode, processName);
pkgList.put(info.packageName, ps);
if (ps != baseProcessTracker) {
ps.makeActive();
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 4a45aac..14f3ef9 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -108,13 +108,14 @@
}
public ProcessStats.ProcessState getProcessStateLocked(String packageName,
- int uid, String processName) {
- return mProcessStats.getProcessStateLocked(packageName, uid, processName);
+ int uid, int versionCode, String processName) {
+ return mProcessStats.getProcessStateLocked(packageName, uid, versionCode, processName);
}
public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid,
- String processName, String className) {
- return mProcessStats.getServiceStateLocked(packageName, uid, processName, className);
+ int versionCode, String processName, String className) {
+ return mProcessStats.getServiceStateLocked(packageName, uid, versionCode, processName,
+ className);
}
public boolean isMemFactorLowered() {
@@ -134,25 +135,29 @@
}
mProcessStats.mMemFactor = memFactor;
mProcessStats.mStartTime = now;
- ArrayMap<String, SparseArray<ProcessStats.PackageState>> pmap
+ final ArrayMap<String, SparseArray<SparseArray<ProcessStats.PackageState>>> pmap
= mProcessStats.mPackages.getMap();
- for (int i=0; i<pmap.size(); i++) {
- SparseArray<ProcessStats.PackageState> uids = pmap.valueAt(i);
- for (int j=0; j<uids.size(); j++) {
- ProcessStats.PackageState pkg = uids.valueAt(j);
- ArrayMap<String, ProcessStats.ServiceState> services = pkg.mServices;
- for (int k=0; k<services.size(); k++) {
- ProcessStats.ServiceState service = services.valueAt(k);
- if (service.isInUse()) {
- if (service.mStartedState != ProcessStats.STATE_NOTHING) {
- service.setStarted(true, memFactor, now);
+ for (int ipkg=pmap.size()-1; ipkg>=0; ipkg--) {
+ final SparseArray<SparseArray<ProcessStats.PackageState>> uids = pmap.valueAt(ipkg);
+ for (int iuid=uids.size()-1; iuid>=0; iuid--) {
+ final SparseArray<ProcessStats.PackageState> vers = uids.valueAt(iuid);
+ for (int iver=vers.size()-1; iver>=0; iver--) {
+ final ProcessStats.PackageState pkg = vers.valueAt(iver);
+ final ArrayMap<String, ProcessStats.ServiceState> services = pkg.mServices;
+ for (int isvc=services.size()-1; isvc>=0; isvc--) {
+ final ProcessStats.ServiceState service = services.valueAt(isvc);
+ if (service.isInUse()) {
+ if (service.mStartedState != ProcessStats.STATE_NOTHING) {
+ service.setStarted(true, memFactor, now);
+ }
+ if (service.mBoundState != ProcessStats.STATE_NOTHING) {
+ service.setBound(true, memFactor, now);
+ }
+ if (service.mExecState != ProcessStats.STATE_NOTHING) {
+ service.setExecuting(true, memFactor, now);
+ }
}
- if (service.mBoundState != ProcessStats.STATE_NOTHING) {
- service.setBound(true, memFactor, now);
- }
- if (service.mExecState != ProcessStats.STATE_NOTHING) {
- service.setExecuting(true, memFactor, now);
- }
+
}
}
}
@@ -291,25 +296,32 @@
Slog.w(TAG, " Uid " + uids.keyAt(iu) + ": " + uids.valueAt(iu));
}
}
- ArrayMap<String, SparseArray<ProcessStats.PackageState>> pkgMap
+ ArrayMap<String, SparseArray<SparseArray<ProcessStats.PackageState>>> pkgMap
= stats.mPackages.getMap();
final int NPKG = pkgMap.size();
for (int ip=0; ip<NPKG; ip++) {
Slog.w(TAG, "Package: " + pkgMap.keyAt(ip));
- SparseArray<ProcessStats.PackageState> uids = pkgMap.valueAt(ip);
+ SparseArray<SparseArray<ProcessStats.PackageState>> uids
+ = pkgMap.valueAt(ip);
final int NUID = uids.size();
for (int iu=0; iu<NUID; iu++) {
Slog.w(TAG, " Uid: " + uids.keyAt(iu));
- ProcessStats.PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- Slog.w(TAG, " Process " + pkgState.mProcesses.keyAt(iproc)
- + ": " + pkgState.mProcesses.valueAt(iproc));
- }
- final int NSRVS = pkgState.mServices.size();
- for (int isvc=0; isvc<NSRVS; isvc++) {
- Slog.w(TAG, " Service " + pkgState.mServices.keyAt(isvc)
- + ": " + pkgState.mServices.valueAt(isvc));
+ SparseArray<ProcessStats.PackageState> vers = uids.valueAt(iu);
+ final int NVERS = vers.size();
+ for (int iv=0; iv<NVERS; iv++) {
+ Slog.w(TAG, " Vers: " + vers.keyAt(iv));
+ ProcessStats.PackageState pkgState = vers.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ Slog.w(TAG, " Process " + pkgState.mProcesses.keyAt(iproc)
+ + ": " + pkgState.mProcesses.valueAt(iproc));
+ }
+ final int NSRVS = pkgState.mServices.size();
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ Slog.w(TAG, " Service " + pkgState.mServices.keyAt(isvc)
+ + ": " + pkgState.mServices.valueAt(isvc));
+
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index cb04835..363a9b7 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -329,7 +329,8 @@
}
if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
tracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
- serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name);
+ serviceInfo.applicationInfo.uid, serviceInfo.applicationInfo.versionCode,
+ serviceInfo.processName, serviceInfo.name);
tracker.applyNewOwner(this);
}
return tracker;
@@ -346,7 +347,8 @@
if (restartTracker == null) {
if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
restartTracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
- serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name);
+ serviceInfo.applicationInfo.uid, serviceInfo.applicationInfo.versionCode,
+ serviceInfo.processName, serviceInfo.name);
}
if (restartTracker == null) {
return;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index adc60dd..bf8c9da 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -868,7 +868,7 @@
int[] uidArray = new int[] { res.pkg.applicationInfo.uid };
ArrayList<String> pkgList = new ArrayList<String>(1);
pkgList.add(res.pkg.applicationInfo.packageName);
- sendResourcesChangedBroadcast(true, false,
+ sendResourcesChangedBroadcast(true, true,
pkgList,uidArray, null);
}
}
@@ -11116,7 +11116,7 @@
if (uidArr != null) {
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr);
}
- if (replacing && !mediaStatus) {
+ if (replacing) {
extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
}
String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
new file mode 100644
index 0000000..f11e9c2
--- /dev/null
+++ b/tools/aapt/AaptAssets.cpp
@@ -0,0 +1,2729 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+
+#include "AaptAssets.h"
+#include "ResourceFilter.h"
+#include "Main.h"
+
+#include <utils/misc.h>
+#include <utils/SortedVector.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+
+static const char* kDefaultLocale = "default";
+static const char* kWildcardName = "any";
+static const char* kAssetDir = "assets";
+static const char* kResourceDir = "res";
+static const char* kValuesDir = "values";
+static const char* kMipmapDir = "mipmap";
+static const char* kInvalidChars = "/\\:";
+static const size_t kMaxAssetFileName = 100;
+
+static const String8 kResString(kResourceDir);
+
+/*
+ * Names of asset files must meet the following criteria:
+ *
+ * - the filename length must be less than kMaxAssetFileName bytes long
+ * (and can't be empty)
+ * - all characters must be 7-bit printable ASCII
+ * - none of { '/' '\\' ':' }
+ *
+ * Pass in just the filename, not the full path.
+ */
+static bool validateFileName(const char* fileName)
+{
+ const char* cp = fileName;
+ size_t len = 0;
+
+ while (*cp != '\0') {
+ if ((*cp & 0x80) != 0)
+ return false; // reject high ASCII
+ if (*cp < 0x20 || *cp >= 0x7f)
+ return false; // reject control chars and 0x7f
+ if (strchr(kInvalidChars, *cp) != NULL)
+ return false; // reject path sep chars
+ cp++;
+ len++;
+ }
+
+ if (len < 1 || len > kMaxAssetFileName)
+ return false; // reject empty or too long
+
+ return true;
+}
+
+// The default to use if no other ignore pattern is defined.
+const char * const gDefaultIgnoreAssets =
+ "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~";
+// The ignore pattern that can be passed via --ignore-assets in Main.cpp
+const char * gUserIgnoreAssets = NULL;
+
+static bool isHidden(const char *root, const char *path)
+{
+ // Patterns syntax:
+ // - Delimiter is :
+ // - Entry can start with the flag ! to avoid printing a warning
+ // about the file being ignored.
+ // - Entry can have the flag "<dir>" to match only directories
+ // or <file> to match only files. Default is to match both.
+ // - Entry can be a simplified glob "<prefix>*" or "*<suffix>"
+ // where prefix/suffix must have at least 1 character (so that
+ // we don't match a '*' catch-all pattern.)
+ // - The special filenames "." and ".." are always ignored.
+ // - Otherwise the full string is matched.
+ // - match is not case-sensitive.
+
+ if (strcmp(path, ".") == 0 || strcmp(path, "..") == 0) {
+ return true;
+ }
+
+ const char *delim = ":";
+ const char *p = gUserIgnoreAssets;
+ if (!p || !p[0]) {
+ p = getenv("ANDROID_AAPT_IGNORE");
+ }
+ if (!p || !p[0]) {
+ p = gDefaultIgnoreAssets;
+ }
+ char *patterns = strdup(p);
+
+ bool ignore = false;
+ bool chatty = true;
+ char *matchedPattern = NULL;
+
+ String8 fullPath(root);
+ fullPath.appendPath(path);
+ FileType type = getFileType(fullPath);
+
+ int plen = strlen(path);
+
+ // Note: we don't have strtok_r under mingw.
+ for(char *token = strtok(patterns, delim);
+ !ignore && token != NULL;
+ token = strtok(NULL, delim)) {
+ chatty = token[0] != '!';
+ if (!chatty) token++; // skip !
+ if (strncasecmp(token, "<dir>" , 5) == 0) {
+ if (type != kFileTypeDirectory) continue;
+ token += 5;
+ }
+ if (strncasecmp(token, "<file>", 6) == 0) {
+ if (type != kFileTypeRegular) continue;
+ token += 6;
+ }
+
+ matchedPattern = token;
+ int n = strlen(token);
+
+ if (token[0] == '*') {
+ // Match *suffix
+ token++;
+ n--;
+ if (n <= plen) {
+ ignore = strncasecmp(token, path + plen - n, n) == 0;
+ }
+ } else if (n > 1 && token[n - 1] == '*') {
+ // Match prefix*
+ ignore = strncasecmp(token, path, n - 1) == 0;
+ } else {
+ ignore = strcasecmp(token, path) == 0;
+ }
+ }
+
+ if (ignore && chatty) {
+ fprintf(stderr, " (skipping %s '%s' due to ANDROID_AAPT_IGNORE pattern '%s')\n",
+ type == kFileTypeDirectory ? "dir" : "file",
+ path,
+ matchedPattern ? matchedPattern : "");
+ }
+
+ free(patterns);
+ return ignore;
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+status_t
+AaptGroupEntry::parseNamePart(const String8& part, int* axis, uint32_t* value)
+{
+ ResTable_config config;
+
+ // IMSI - MCC
+ if (getMccName(part.string(), &config)) {
+ *axis = AXIS_MCC;
+ *value = config.mcc;
+ return 0;
+ }
+
+ // IMSI - MNC
+ if (getMncName(part.string(), &config)) {
+ *axis = AXIS_MNC;
+ *value = config.mnc;
+ return 0;
+ }
+
+ // locale - language
+ if (part.length() == 2 && isalpha(part[0]) && isalpha(part[1])) {
+ *axis = AXIS_LANGUAGE;
+ *value = part[1] << 8 | part[0];
+ return 0;
+ }
+
+ // locale - language_REGION
+ if (part.length() == 5 && isalpha(part[0]) && isalpha(part[1])
+ && part[2] == '_' && isalpha(part[3]) && isalpha(part[4])) {
+ *axis = AXIS_LANGUAGE;
+ *value = (part[4] << 24) | (part[3] << 16) | (part[1] << 8) | (part[0]);
+ return 0;
+ }
+
+ // layout direction
+ if (getLayoutDirectionName(part.string(), &config)) {
+ *axis = AXIS_LAYOUTDIR;
+ *value = (config.screenLayout&ResTable_config::MASK_LAYOUTDIR);
+ return 0;
+ }
+
+ // smallest screen dp width
+ if (getSmallestScreenWidthDpName(part.string(), &config)) {
+ *axis = AXIS_SMALLESTSCREENWIDTHDP;
+ *value = config.smallestScreenWidthDp;
+ return 0;
+ }
+
+ // screen dp width
+ if (getScreenWidthDpName(part.string(), &config)) {
+ *axis = AXIS_SCREENWIDTHDP;
+ *value = config.screenWidthDp;
+ return 0;
+ }
+
+ // screen dp height
+ if (getScreenHeightDpName(part.string(), &config)) {
+ *axis = AXIS_SCREENHEIGHTDP;
+ *value = config.screenHeightDp;
+ return 0;
+ }
+
+ // screen layout size
+ if (getScreenLayoutSizeName(part.string(), &config)) {
+ *axis = AXIS_SCREENLAYOUTSIZE;
+ *value = (config.screenLayout&ResTable_config::MASK_SCREENSIZE);
+ return 0;
+ }
+
+ // screen layout long
+ if (getScreenLayoutLongName(part.string(), &config)) {
+ *axis = AXIS_SCREENLAYOUTLONG;
+ *value = (config.screenLayout&ResTable_config::MASK_SCREENLONG);
+ return 0;
+ }
+
+ // orientation
+ if (getOrientationName(part.string(), &config)) {
+ *axis = AXIS_ORIENTATION;
+ *value = config.orientation;
+ return 0;
+ }
+
+ // ui mode type
+ if (getUiModeTypeName(part.string(), &config)) {
+ *axis = AXIS_UIMODETYPE;
+ *value = (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE);
+ return 0;
+ }
+
+ // ui mode night
+ if (getUiModeNightName(part.string(), &config)) {
+ *axis = AXIS_UIMODENIGHT;
+ *value = (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT);
+ return 0;
+ }
+
+ // density
+ if (getDensityName(part.string(), &config)) {
+ *axis = AXIS_DENSITY;
+ *value = config.density;
+ return 0;
+ }
+
+ // touchscreen
+ if (getTouchscreenName(part.string(), &config)) {
+ *axis = AXIS_TOUCHSCREEN;
+ *value = config.touchscreen;
+ return 0;
+ }
+
+ // keyboard hidden
+ if (getKeysHiddenName(part.string(), &config)) {
+ *axis = AXIS_KEYSHIDDEN;
+ *value = config.inputFlags;
+ return 0;
+ }
+
+ // keyboard
+ if (getKeyboardName(part.string(), &config)) {
+ *axis = AXIS_KEYBOARD;
+ *value = config.keyboard;
+ return 0;
+ }
+
+ // navigation hidden
+ if (getNavHiddenName(part.string(), &config)) {
+ *axis = AXIS_NAVHIDDEN;
+ *value = config.inputFlags;
+ return 0;
+ }
+
+ // navigation
+ if (getNavigationName(part.string(), &config)) {
+ *axis = AXIS_NAVIGATION;
+ *value = config.navigation;
+ return 0;
+ }
+
+ // screen size
+ if (getScreenSizeName(part.string(), &config)) {
+ *axis = AXIS_SCREENSIZE;
+ *value = config.screenSize;
+ return 0;
+ }
+
+ // version
+ if (getVersionName(part.string(), &config)) {
+ *axis = AXIS_VERSION;
+ *value = config.version;
+ return 0;
+ }
+
+ return 1;
+}
+
+uint32_t
+AaptGroupEntry::getConfigValueForAxis(const ResTable_config& config, int axis)
+{
+ switch (axis) {
+ case AXIS_MCC:
+ return config.mcc;
+ case AXIS_MNC:
+ return config.mnc;
+ case AXIS_LANGUAGE:
+ return (((uint32_t)config.country[1]) << 24) | (((uint32_t)config.country[0]) << 16)
+ | (((uint32_t)config.language[1]) << 8) | (config.language[0]);
+ case AXIS_LAYOUTDIR:
+ return config.screenLayout&ResTable_config::MASK_LAYOUTDIR;
+ case AXIS_SCREENLAYOUTSIZE:
+ return config.screenLayout&ResTable_config::MASK_SCREENSIZE;
+ case AXIS_ORIENTATION:
+ return config.orientation;
+ case AXIS_UIMODETYPE:
+ return (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE);
+ case AXIS_UIMODENIGHT:
+ return (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT);
+ case AXIS_DENSITY:
+ return config.density;
+ case AXIS_TOUCHSCREEN:
+ return config.touchscreen;
+ case AXIS_KEYSHIDDEN:
+ return config.inputFlags;
+ case AXIS_KEYBOARD:
+ return config.keyboard;
+ case AXIS_NAVIGATION:
+ return config.navigation;
+ case AXIS_SCREENSIZE:
+ return config.screenSize;
+ case AXIS_SMALLESTSCREENWIDTHDP:
+ return config.smallestScreenWidthDp;
+ case AXIS_SCREENWIDTHDP:
+ return config.screenWidthDp;
+ case AXIS_SCREENHEIGHTDP:
+ return config.screenHeightDp;
+ case AXIS_VERSION:
+ return config.version;
+ }
+ return 0;
+}
+
+bool
+AaptGroupEntry::configSameExcept(const ResTable_config& config,
+ const ResTable_config& otherConfig, int axis)
+{
+ for (int i=AXIS_START; i<=AXIS_END; i++) {
+ if (i == axis) {
+ continue;
+ }
+ if (getConfigValueForAxis(config, i) != getConfigValueForAxis(otherConfig, i)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+AaptGroupEntry::initFromDirName(const char* dir, String8* resType)
+{
+ mParamsChanged = true;
+
+ Vector<String8> parts;
+
+ String8 mcc, mnc, loc, layoutsize, layoutlong, orient, den;
+ String8 touch, key, keysHidden, nav, navHidden, size, layoutDir, vers;
+ String8 uiModeType, uiModeNight, smallestwidthdp, widthdp, heightdp;
+
+ const char *p = dir;
+ const char *q;
+ while (NULL != (q = strchr(p, '-'))) {
+ String8 val(p, q-p);
+ val.toLower();
+ parts.add(val);
+ //printf("part: %s\n", parts[parts.size()-1].string());
+ p = q+1;
+ }
+ String8 val(p);
+ val.toLower();
+ parts.add(val);
+ //printf("part: %s\n", parts[parts.size()-1].string());
+
+ const int N = parts.size();
+ int index = 0;
+ String8 part = parts[index];
+
+ // resource type
+ if (!isValidResourceType(part)) {
+ return false;
+ }
+ *resType = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+
+ // imsi - mcc
+ if (getMccName(part.string())) {
+ mcc = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not mcc: %s\n", part.string());
+ }
+
+ // imsi - mnc
+ if (getMncName(part.string())) {
+ mnc = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not mcc: %s\n", part.string());
+ }
+
+ // locale - language
+ if (part.length() == 2 && isalpha(part[0]) && isalpha(part[1])) {
+ loc = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not language: %s\n", part.string());
+ }
+
+ // locale - region
+ if (loc.length() > 0
+ && part.length() == 3 && part[0] == 'r' && part[0] && part[1]) {
+ loc += "-";
+ part.toUpper();
+ loc += part.string() + 1;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not region: %s\n", part.string());
+ }
+
+ if (getLayoutDirectionName(part.string())) {
+ layoutDir = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not layout direction: %s\n", part.string());
+ }
+
+ if (getSmallestScreenWidthDpName(part.string())) {
+ smallestwidthdp = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not smallest screen width dp: %s\n", part.string());
+ }
+
+ if (getScreenWidthDpName(part.string())) {
+ widthdp = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not screen width dp: %s\n", part.string());
+ }
+
+ if (getScreenHeightDpName(part.string())) {
+ heightdp = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not screen height dp: %s\n", part.string());
+ }
+
+ if (getScreenLayoutSizeName(part.string())) {
+ layoutsize = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not screen layout size: %s\n", part.string());
+ }
+
+ if (getScreenLayoutLongName(part.string())) {
+ layoutlong = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not screen layout long: %s\n", part.string());
+ }
+
+ // orientation
+ if (getOrientationName(part.string())) {
+ orient = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not orientation: %s\n", part.string());
+ }
+
+ // ui mode type
+ if (getUiModeTypeName(part.string())) {
+ uiModeType = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not ui mode type: %s\n", part.string());
+ }
+
+ // ui mode night
+ if (getUiModeNightName(part.string())) {
+ uiModeNight = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not ui mode night: %s\n", part.string());
+ }
+
+ // density
+ if (getDensityName(part.string())) {
+ den = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not density: %s\n", part.string());
+ }
+
+ // touchscreen
+ if (getTouchscreenName(part.string())) {
+ touch = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not touchscreen: %s\n", part.string());
+ }
+
+ // keyboard hidden
+ if (getKeysHiddenName(part.string())) {
+ keysHidden = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not keysHidden: %s\n", part.string());
+ }
+
+ // keyboard
+ if (getKeyboardName(part.string())) {
+ key = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not keyboard: %s\n", part.string());
+ }
+
+ // navigation hidden
+ if (getNavHiddenName(part.string())) {
+ navHidden = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not navHidden: %s\n", part.string());
+ }
+
+ if (getNavigationName(part.string())) {
+ nav = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not navigation: %s\n", part.string());
+ }
+
+ if (getScreenSizeName(part.string())) {
+ size = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not screen size: %s\n", part.string());
+ }
+
+ if (getVersionName(part.string())) {
+ vers = part;
+
+ index++;
+ if (index == N) {
+ goto success;
+ }
+ part = parts[index];
+ } else {
+ //printf("not version: %s\n", part.string());
+ }
+
+ // if there are extra parts, it doesn't match
+ return false;
+
+success:
+ this->mcc = mcc;
+ this->mnc = mnc;
+ this->locale = loc;
+ this->screenLayoutSize = layoutsize;
+ this->screenLayoutLong = layoutlong;
+ this->smallestScreenWidthDp = smallestwidthdp;
+ this->screenWidthDp = widthdp;
+ this->screenHeightDp = heightdp;
+ this->orientation = orient;
+ this->uiModeType = uiModeType;
+ this->uiModeNight = uiModeNight;
+ this->density = den;
+ this->touchscreen = touch;
+ this->keysHidden = keysHidden;
+ this->keyboard = key;
+ this->navHidden = navHidden;
+ this->navigation = nav;
+ this->screenSize = size;
+ this->layoutDirection = layoutDir;
+ this->version = vers;
+
+ // what is this anyway?
+ this->vendor = "";
+
+ return true;
+}
+
+String8
+AaptGroupEntry::toString() const
+{
+ String8 s = this->mcc;
+ s += ",";
+ s += this->mnc;
+ s += ",";
+ s += this->locale;
+ s += ",";
+ s += layoutDirection;
+ s += ",";
+ s += smallestScreenWidthDp;
+ s += ",";
+ s += screenWidthDp;
+ s += ",";
+ s += screenHeightDp;
+ s += ",";
+ s += screenLayoutSize;
+ s += ",";
+ s += screenLayoutLong;
+ s += ",";
+ s += this->orientation;
+ s += ",";
+ s += uiModeType;
+ s += ",";
+ s += uiModeNight;
+ s += ",";
+ s += density;
+ s += ",";
+ s += touchscreen;
+ s += ",";
+ s += keysHidden;
+ s += ",";
+ s += keyboard;
+ s += ",";
+ s += navHidden;
+ s += ",";
+ s += navigation;
+ s += ",";
+ s += screenSize;
+ s += ",";
+ s += version;
+ return s;
+}
+
+String8
+AaptGroupEntry::toDirName(const String8& resType) const
+{
+ String8 s = resType;
+ if (this->mcc != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += mcc;
+ }
+ if (this->mnc != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += mnc;
+ }
+ if (this->locale != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += locale;
+ }
+ if (this->layoutDirection != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += layoutDirection;
+ }
+ if (this->smallestScreenWidthDp != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += smallestScreenWidthDp;
+ }
+ if (this->screenWidthDp != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += screenWidthDp;
+ }
+ if (this->screenHeightDp != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += screenHeightDp;
+ }
+ if (this->screenLayoutSize != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += screenLayoutSize;
+ }
+ if (this->screenLayoutLong != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += screenLayoutLong;
+ }
+ if (this->orientation != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += orientation;
+ }
+ if (this->uiModeType != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += uiModeType;
+ }
+ if (this->uiModeNight != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += uiModeNight;
+ }
+ if (this->density != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += density;
+ }
+ if (this->touchscreen != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += touchscreen;
+ }
+ if (this->keysHidden != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += keysHidden;
+ }
+ if (this->keyboard != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += keyboard;
+ }
+ if (this->navHidden != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += navHidden;
+ }
+ if (this->navigation != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += navigation;
+ }
+ if (this->screenSize != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += screenSize;
+ }
+ if (this->version != "") {
+ if (s.length() > 0) {
+ s += "-";
+ }
+ s += version;
+ }
+
+ return s;
+}
+
+bool AaptGroupEntry::getMccName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->mcc = 0;
+ return true;
+ }
+ const char* c = name;
+ if (tolower(*c) != 'm') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+
+ const char* val = c;
+
+ while (*c >= '0' && *c <= '9') {
+ c++;
+ }
+ if (*c != 0) return false;
+ if (c-val != 3) return false;
+
+ int d = atoi(val);
+ if (d != 0) {
+ if (out) out->mcc = d;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getMncName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->mcc = 0;
+ return true;
+ }
+ const char* c = name;
+ if (tolower(*c) != 'm') return false;
+ c++;
+ if (tolower(*c) != 'n') return false;
+ c++;
+ if (tolower(*c) != 'c') return false;
+ c++;
+
+ const char* val = c;
+
+ while (*c >= '0' && *c <= '9') {
+ c++;
+ }
+ if (*c != 0) return false;
+ if (c-val == 0 || c-val > 3) return false;
+
+ if (out) {
+ out->mnc = atoi(val);
+ if (out->mnc == 0) {
+ out->mnc = ACONFIGURATION_MNC_ZERO;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * Does this directory name fit the pattern of a locale dir ("en-rUS" or
+ * "default")?
+ *
+ * TODO: Should insist that the first two letters are lower case, and the
+ * second two are upper.
+ */
+bool AaptGroupEntry::getLocaleName(const char* fileName,
+ ResTable_config* out)
+{
+ if (strcmp(fileName, kWildcardName) == 0
+ || strcmp(fileName, kDefaultLocale) == 0) {
+ if (out) {
+ out->language[0] = 0;
+ out->language[1] = 0;
+ out->country[0] = 0;
+ out->country[1] = 0;
+ }
+ return true;
+ }
+
+ if (strlen(fileName) == 2 && isalpha(fileName[0]) && isalpha(fileName[1])) {
+ if (out) {
+ out->language[0] = fileName[0];
+ out->language[1] = fileName[1];
+ out->country[0] = 0;
+ out->country[1] = 0;
+ }
+ return true;
+ }
+
+ if (strlen(fileName) == 5 &&
+ isalpha(fileName[0]) &&
+ isalpha(fileName[1]) &&
+ fileName[2] == '-' &&
+ isalpha(fileName[3]) &&
+ isalpha(fileName[4])) {
+ if (out) {
+ out->language[0] = fileName[0];
+ out->language[1] = fileName[1];
+ out->country[0] = fileName[3];
+ out->country[1] = fileName[4];
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getLayoutDirectionName(const char* name, ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->screenLayout =
+ (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+ | ResTable_config::LAYOUTDIR_ANY;
+ return true;
+ } else if (strcmp(name, "ldltr") == 0) {
+ if (out) out->screenLayout =
+ (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+ | ResTable_config::LAYOUTDIR_LTR;
+ return true;
+ } else if (strcmp(name, "ldrtl") == 0) {
+ if (out) out->screenLayout =
+ (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
+ | ResTable_config::LAYOUTDIR_RTL;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getScreenLayoutSizeName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->screenLayout =
+ (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+ | ResTable_config::SCREENSIZE_ANY;
+ return true;
+ } else if (strcmp(name, "small") == 0) {
+ if (out) out->screenLayout =
+ (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+ | ResTable_config::SCREENSIZE_SMALL;
+ return true;
+ } else if (strcmp(name, "normal") == 0) {
+ if (out) out->screenLayout =
+ (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+ | ResTable_config::SCREENSIZE_NORMAL;
+ return true;
+ } else if (strcmp(name, "large") == 0) {
+ if (out) out->screenLayout =
+ (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+ | ResTable_config::SCREENSIZE_LARGE;
+ return true;
+ } else if (strcmp(name, "xlarge") == 0) {
+ if (out) out->screenLayout =
+ (out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+ | ResTable_config::SCREENSIZE_XLARGE;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getScreenLayoutLongName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->screenLayout =
+ (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+ | ResTable_config::SCREENLONG_ANY;
+ return true;
+ } else if (strcmp(name, "long") == 0) {
+ if (out) out->screenLayout =
+ (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+ | ResTable_config::SCREENLONG_YES;
+ return true;
+ } else if (strcmp(name, "notlong") == 0) {
+ if (out) out->screenLayout =
+ (out->screenLayout&~ResTable_config::MASK_SCREENLONG)
+ | ResTable_config::SCREENLONG_NO;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getOrientationName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->orientation = out->ORIENTATION_ANY;
+ return true;
+ } else if (strcmp(name, "port") == 0) {
+ if (out) out->orientation = out->ORIENTATION_PORT;
+ return true;
+ } else if (strcmp(name, "land") == 0) {
+ if (out) out->orientation = out->ORIENTATION_LAND;
+ return true;
+ } else if (strcmp(name, "square") == 0) {
+ if (out) out->orientation = out->ORIENTATION_SQUARE;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getUiModeTypeName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->uiMode =
+ (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+ | ResTable_config::UI_MODE_TYPE_ANY;
+ return true;
+ } else if (strcmp(name, "desk") == 0) {
+ if (out) out->uiMode =
+ (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+ | ResTable_config::UI_MODE_TYPE_DESK;
+ return true;
+ } else if (strcmp(name, "car") == 0) {
+ if (out) out->uiMode =
+ (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+ | ResTable_config::UI_MODE_TYPE_CAR;
+ return true;
+ } else if (strcmp(name, "television") == 0) {
+ if (out) out->uiMode =
+ (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+ | ResTable_config::UI_MODE_TYPE_TELEVISION;
+ return true;
+ } else if (strcmp(name, "appliance") == 0) {
+ if (out) out->uiMode =
+ (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+ | ResTable_config::UI_MODE_TYPE_APPLIANCE;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getUiModeNightName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->uiMode =
+ (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+ | ResTable_config::UI_MODE_NIGHT_ANY;
+ return true;
+ } else if (strcmp(name, "night") == 0) {
+ if (out) out->uiMode =
+ (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+ | ResTable_config::UI_MODE_NIGHT_YES;
+ return true;
+ } else if (strcmp(name, "notnight") == 0) {
+ if (out) out->uiMode =
+ (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+ | ResTable_config::UI_MODE_NIGHT_NO;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getDensityName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->density = ResTable_config::DENSITY_DEFAULT;
+ return true;
+ }
+
+ if (strcmp(name, "nodpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_NONE;
+ return true;
+ }
+
+ if (strcmp(name, "ldpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_LOW;
+ return true;
+ }
+
+ if (strcmp(name, "mdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_MEDIUM;
+ return true;
+ }
+
+ if (strcmp(name, "tvdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_TV;
+ return true;
+ }
+
+ if (strcmp(name, "hdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_HIGH;
+ return true;
+ }
+
+ if (strcmp(name, "xhdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_XHIGH;
+ return true;
+ }
+
+ if (strcmp(name, "xxhdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_XXHIGH;
+ return true;
+ }
+
+ if (strcmp(name, "xxxhdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
+ return true;
+ }
+
+ char* c = (char*)name;
+ while (*c >= '0' && *c <= '9') {
+ c++;
+ }
+
+ // check that we have 'dpi' after the last digit.
+ if (toupper(c[0]) != 'D' ||
+ toupper(c[1]) != 'P' ||
+ toupper(c[2]) != 'I' ||
+ c[3] != 0) {
+ return false;
+ }
+
+ // temporarily replace the first letter with \0 to
+ // use atoi.
+ char tmp = c[0];
+ c[0] = '\0';
+
+ int d = atoi(name);
+ c[0] = tmp;
+
+ if (d != 0) {
+ if (out) out->density = d;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getTouchscreenName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
+ return true;
+ } else if (strcmp(name, "notouch") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
+ return true;
+ } else if (strcmp(name, "stylus") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
+ return true;
+ } else if (strcmp(name, "finger") == 0) {
+ if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getKeysHiddenName(const char* name,
+ ResTable_config* out)
+{
+ uint8_t mask = 0;
+ uint8_t value = 0;
+ if (strcmp(name, kWildcardName) == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_ANY;
+ } else if (strcmp(name, "keysexposed") == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_NO;
+ } else if (strcmp(name, "keyshidden") == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_YES;
+ } else if (strcmp(name, "keyssoft") == 0) {
+ mask = ResTable_config::MASK_KEYSHIDDEN;
+ value = ResTable_config::KEYSHIDDEN_SOFT;
+ }
+
+ if (mask != 0) {
+ if (out) out->inputFlags = (out->inputFlags&~mask) | value;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getKeyboardName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->keyboard = out->KEYBOARD_ANY;
+ return true;
+ } else if (strcmp(name, "nokeys") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_NOKEYS;
+ return true;
+ } else if (strcmp(name, "qwerty") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_QWERTY;
+ return true;
+ } else if (strcmp(name, "12key") == 0) {
+ if (out) out->keyboard = out->KEYBOARD_12KEY;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getNavHiddenName(const char* name,
+ ResTable_config* out)
+{
+ uint8_t mask = 0;
+ uint8_t value = 0;
+ if (strcmp(name, kWildcardName) == 0) {
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_ANY;
+ } else if (strcmp(name, "navexposed") == 0) {
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_NO;
+ } else if (strcmp(name, "navhidden") == 0) {
+ mask = ResTable_config::MASK_NAVHIDDEN;
+ value = ResTable_config::NAVHIDDEN_YES;
+ }
+
+ if (mask != 0) {
+ if (out) out->inputFlags = (out->inputFlags&~mask) | value;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getNavigationName(const char* name,
+ ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) out->navigation = out->NAVIGATION_ANY;
+ return true;
+ } else if (strcmp(name, "nonav") == 0) {
+ if (out) out->navigation = out->NAVIGATION_NONAV;
+ return true;
+ } else if (strcmp(name, "dpad") == 0) {
+ if (out) out->navigation = out->NAVIGATION_DPAD;
+ return true;
+ } else if (strcmp(name, "trackball") == 0) {
+ if (out) out->navigation = out->NAVIGATION_TRACKBALL;
+ return true;
+ } else if (strcmp(name, "wheel") == 0) {
+ if (out) out->navigation = out->NAVIGATION_WHEEL;
+ return true;
+ }
+
+ return false;
+}
+
+bool AaptGroupEntry::getScreenSizeName(const char* name, ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) {
+ out->screenWidth = out->SCREENWIDTH_ANY;
+ out->screenHeight = out->SCREENHEIGHT_ANY;
+ }
+ return true;
+ }
+
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || *x != 'x') return false;
+ String8 xName(name, x-name);
+ x++;
+
+ const char* y = x;
+ while (*y >= '0' && *y <= '9') y++;
+ if (y == name || *y != 0) return false;
+ String8 yName(x, y-x);
+
+ uint16_t w = (uint16_t)atoi(xName.string());
+ uint16_t h = (uint16_t)atoi(yName.string());
+ if (w < h) {
+ return false;
+ }
+
+ if (out) {
+ out->screenWidth = w;
+ out->screenHeight = h;
+ }
+
+ return true;
+}
+
+bool AaptGroupEntry::getSmallestScreenWidthDpName(const char* name, ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) {
+ out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
+ }
+ return true;
+ }
+
+ if (*name != 's') return false;
+ name++;
+ if (*name != 'w') return false;
+ name++;
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+ String8 xName(name, x-name);
+
+ if (out) {
+ out->smallestScreenWidthDp = (uint16_t)atoi(xName.string());
+ }
+
+ return true;
+}
+
+bool AaptGroupEntry::getScreenWidthDpName(const char* name, ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) {
+ out->screenWidthDp = out->SCREENWIDTH_ANY;
+ }
+ return true;
+ }
+
+ if (*name != 'w') return false;
+ name++;
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+ String8 xName(name, x-name);
+
+ if (out) {
+ out->screenWidthDp = (uint16_t)atoi(xName.string());
+ }
+
+ return true;
+}
+
+bool AaptGroupEntry::getScreenHeightDpName(const char* name, ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) {
+ out->screenHeightDp = out->SCREENWIDTH_ANY;
+ }
+ return true;
+ }
+
+ if (*name != 'h') return false;
+ name++;
+ const char* x = name;
+ while (*x >= '0' && *x <= '9') x++;
+ if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+ String8 xName(name, x-name);
+
+ if (out) {
+ out->screenHeightDp = (uint16_t)atoi(xName.string());
+ }
+
+ return true;
+}
+
+bool AaptGroupEntry::getVersionName(const char* name, ResTable_config* out)
+{
+ if (strcmp(name, kWildcardName) == 0) {
+ if (out) {
+ out->sdkVersion = out->SDKVERSION_ANY;
+ out->minorVersion = out->MINORVERSION_ANY;
+ }
+ return true;
+ }
+
+ if (*name != 'v') {
+ return false;
+ }
+
+ name++;
+ const char* s = name;
+ while (*s >= '0' && *s <= '9') s++;
+ if (s == name || *s != 0) return false;
+ String8 sdkName(name, s-name);
+
+ if (out) {
+ out->sdkVersion = (uint16_t)atoi(sdkName.string());
+ out->minorVersion = 0;
+ }
+
+ return true;
+}
+
+int AaptGroupEntry::compare(const AaptGroupEntry& o) const
+{
+ int v = mcc.compare(o.mcc);
+ if (v == 0) v = mnc.compare(o.mnc);
+ if (v == 0) v = locale.compare(o.locale);
+ if (v == 0) v = layoutDirection.compare(o.layoutDirection);
+ if (v == 0) v = vendor.compare(o.vendor);
+ if (v == 0) v = smallestScreenWidthDp.compare(o.smallestScreenWidthDp);
+ if (v == 0) v = screenWidthDp.compare(o.screenWidthDp);
+ if (v == 0) v = screenHeightDp.compare(o.screenHeightDp);
+ if (v == 0) v = screenLayoutSize.compare(o.screenLayoutSize);
+ if (v == 0) v = screenLayoutLong.compare(o.screenLayoutLong);
+ if (v == 0) v = orientation.compare(o.orientation);
+ if (v == 0) v = uiModeType.compare(o.uiModeType);
+ if (v == 0) v = uiModeNight.compare(o.uiModeNight);
+ if (v == 0) v = density.compare(o.density);
+ if (v == 0) v = touchscreen.compare(o.touchscreen);
+ if (v == 0) v = keysHidden.compare(o.keysHidden);
+ if (v == 0) v = keyboard.compare(o.keyboard);
+ if (v == 0) v = navHidden.compare(o.navHidden);
+ if (v == 0) v = navigation.compare(o.navigation);
+ if (v == 0) v = screenSize.compare(o.screenSize);
+ if (v == 0) v = version.compare(o.version);
+ return v;
+}
+
+const ResTable_config& AaptGroupEntry::toParams() const
+{
+ if (!mParamsChanged) {
+ return mParams;
+ }
+
+ mParamsChanged = false;
+ ResTable_config& params(mParams);
+ memset(¶ms, 0, sizeof(params));
+ getMccName(mcc.string(), ¶ms);
+ getMncName(mnc.string(), ¶ms);
+ getLocaleName(locale.string(), ¶ms);
+ getLayoutDirectionName(layoutDirection.string(), ¶ms);
+ getSmallestScreenWidthDpName(smallestScreenWidthDp.string(), ¶ms);
+ getScreenWidthDpName(screenWidthDp.string(), ¶ms);
+ getScreenHeightDpName(screenHeightDp.string(), ¶ms);
+ getScreenLayoutSizeName(screenLayoutSize.string(), ¶ms);
+ getScreenLayoutLongName(screenLayoutLong.string(), ¶ms);
+ getOrientationName(orientation.string(), ¶ms);
+ getUiModeTypeName(uiModeType.string(), ¶ms);
+ getUiModeNightName(uiModeNight.string(), ¶ms);
+ getDensityName(density.string(), ¶ms);
+ getTouchscreenName(touchscreen.string(), ¶ms);
+ getKeysHiddenName(keysHidden.string(), ¶ms);
+ getKeyboardName(keyboard.string(), ¶ms);
+ getNavHiddenName(navHidden.string(), ¶ms);
+ getNavigationName(navigation.string(), ¶ms);
+ getScreenSizeName(screenSize.string(), ¶ms);
+ getVersionName(version.string(), ¶ms);
+
+ // Fix up version number based on specified parameters.
+ int minSdk = 0;
+ if (params.smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
+ || params.screenWidthDp != ResTable_config::SCREENWIDTH_ANY
+ || params.screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
+ minSdk = SDK_HONEYCOMB_MR2;
+ } else if ((params.uiMode&ResTable_config::MASK_UI_MODE_TYPE)
+ != ResTable_config::UI_MODE_TYPE_ANY
+ || (params.uiMode&ResTable_config::MASK_UI_MODE_NIGHT)
+ != ResTable_config::UI_MODE_NIGHT_ANY) {
+ minSdk = SDK_FROYO;
+ } else if ((params.screenLayout&ResTable_config::MASK_SCREENSIZE)
+ != ResTable_config::SCREENSIZE_ANY
+ || (params.screenLayout&ResTable_config::MASK_SCREENLONG)
+ != ResTable_config::SCREENLONG_ANY
+ || params.density != ResTable_config::DENSITY_DEFAULT) {
+ minSdk = SDK_DONUT;
+ }
+
+ if (minSdk > params.sdkVersion) {
+ params.sdkVersion = minSdk;
+ }
+
+ return params;
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+void* AaptFile::editData(size_t size)
+{
+ if (size <= mBufferSize) {
+ mDataSize = size;
+ return mData;
+ }
+ size_t allocSize = (size*3)/2;
+ void* buf = realloc(mData, allocSize);
+ if (buf == NULL) {
+ return NULL;
+ }
+ mData = buf;
+ mDataSize = size;
+ mBufferSize = allocSize;
+ return buf;
+}
+
+void* AaptFile::editData(size_t* outSize)
+{
+ if (outSize) {
+ *outSize = mDataSize;
+ }
+ return mData;
+}
+
+void* AaptFile::padData(size_t wordSize)
+{
+ const size_t extra = mDataSize%wordSize;
+ if (extra == 0) {
+ return mData;
+ }
+
+ size_t initial = mDataSize;
+ void* data = editData(initial+(wordSize-extra));
+ if (data != NULL) {
+ memset(((uint8_t*)data) + initial, 0, wordSize-extra);
+ }
+ return data;
+}
+
+status_t AaptFile::writeData(const void* data, size_t size)
+{
+ size_t end = mDataSize;
+ size_t total = size + end;
+ void* buf = editData(total);
+ if (buf == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ memcpy(((char*)buf)+end, data, size);
+ return NO_ERROR;
+}
+
+void AaptFile::clearData()
+{
+ if (mData != NULL) free(mData);
+ mData = NULL;
+ mDataSize = 0;
+ mBufferSize = 0;
+}
+
+String8 AaptFile::getPrintableSource() const
+{
+ if (hasData()) {
+ String8 name(mGroupEntry.toDirName(String8()));
+ name.appendPath(mPath);
+ name.append(" #generated");
+ return name;
+ }
+ return mSourceFile;
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+status_t AaptGroup::addFile(const sp<AaptFile>& file)
+{
+ if (mFiles.indexOfKey(file->getGroupEntry()) < 0) {
+ file->mPath = mPath;
+ mFiles.add(file->getGroupEntry(), file);
+ return NO_ERROR;
+ }
+
+#if 0
+ printf("Error adding file %s: group %s already exists in leaf=%s path=%s\n",
+ file->getSourceFile().string(),
+ file->getGroupEntry().toDirName(String8()).string(),
+ mLeaf.string(), mPath.string());
+#endif
+
+ SourcePos(file->getSourceFile(), -1).error("Duplicate file.\n%s: Original is here.",
+ getPrintableSource().string());
+ return UNKNOWN_ERROR;
+}
+
+void AaptGroup::removeFile(size_t index)
+{
+ mFiles.removeItemsAt(index);
+}
+
+void AaptGroup::print(const String8& prefix) const
+{
+ printf("%s%s\n", prefix.string(), getPath().string());
+ const size_t N=mFiles.size();
+ size_t i;
+ for (i=0; i<N; i++) {
+ sp<AaptFile> file = mFiles.valueAt(i);
+ const AaptGroupEntry& e = file->getGroupEntry();
+ if (file->hasData()) {
+ printf("%s Gen: (%s) %d bytes\n", prefix.string(), e.toDirName(String8()).string(),
+ (int)file->getSize());
+ } else {
+ printf("%s Src: (%s) %s\n", prefix.string(), e.toDirName(String8()).string(),
+ file->getPrintableSource().string());
+ }
+ //printf("%s File Group Entry: %s\n", prefix.string(),
+ // file->getGroupEntry().toDirName(String8()).string());
+ }
+}
+
+String8 AaptGroup::getPrintableSource() const
+{
+ if (mFiles.size() > 0) {
+ // Arbitrarily pull the first source file out of the list.
+ return mFiles.valueAt(0)->getPrintableSource();
+ }
+
+ // Should never hit this case, but to be safe...
+ return getPath();
+
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+status_t AaptDir::addFile(const String8& name, const sp<AaptGroup>& file)
+{
+ if (mFiles.indexOfKey(name) >= 0) {
+ return ALREADY_EXISTS;
+ }
+ mFiles.add(name, file);
+ return NO_ERROR;
+}
+
+status_t AaptDir::addDir(const String8& name, const sp<AaptDir>& dir)
+{
+ if (mDirs.indexOfKey(name) >= 0) {
+ return ALREADY_EXISTS;
+ }
+ mDirs.add(name, dir);
+ return NO_ERROR;
+}
+
+sp<AaptDir> AaptDir::makeDir(const String8& path)
+{
+ String8 name;
+ String8 remain = path;
+
+ sp<AaptDir> subdir = this;
+ while (name = remain.walkPath(&remain), remain != "") {
+ subdir = subdir->makeDir(name);
+ }
+
+ ssize_t i = subdir->mDirs.indexOfKey(name);
+ if (i >= 0) {
+ return subdir->mDirs.valueAt(i);
+ }
+ sp<AaptDir> dir = new AaptDir(name, subdir->mPath.appendPathCopy(name));
+ subdir->mDirs.add(name, dir);
+ return dir;
+}
+
+void AaptDir::removeFile(const String8& name)
+{
+ mFiles.removeItem(name);
+}
+
+void AaptDir::removeDir(const String8& name)
+{
+ mDirs.removeItem(name);
+}
+
+status_t AaptDir::addLeafFile(const String8& leafName, const sp<AaptFile>& file)
+{
+ sp<AaptGroup> group;
+ if (mFiles.indexOfKey(leafName) >= 0) {
+ group = mFiles.valueFor(leafName);
+ } else {
+ group = new AaptGroup(leafName, mPath.appendPathCopy(leafName));
+ mFiles.add(leafName, group);
+ }
+
+ return group->addFile(file);
+}
+
+ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir,
+ const AaptGroupEntry& kind, const String8& resType,
+ sp<FilePathStore>& fullResPaths)
+{
+ Vector<String8> fileNames;
+ {
+ DIR* dir = NULL;
+
+ dir = opendir(srcDir.string());
+ if (dir == NULL) {
+ fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+
+ /*
+ * Slurp the filenames out of the directory.
+ */
+ while (1) {
+ struct dirent* entry;
+
+ entry = readdir(dir);
+ if (entry == NULL)
+ break;
+
+ if (isHidden(srcDir.string(), entry->d_name))
+ continue;
+
+ String8 name(entry->d_name);
+ fileNames.add(name);
+ // Add fully qualified path for dependency purposes
+ // if we're collecting them
+ if (fullResPaths != NULL) {
+ fullResPaths->add(srcDir.appendPathCopy(name));
+ }
+ }
+ closedir(dir);
+ }
+
+ ssize_t count = 0;
+
+ /*
+ * Stash away the files and recursively descend into subdirectories.
+ */
+ const size_t N = fileNames.size();
+ size_t i;
+ for (i = 0; i < N; i++) {
+ String8 pathName(srcDir);
+ FileType type;
+
+ pathName.appendPath(fileNames[i].string());
+ type = getFileType(pathName.string());
+ if (type == kFileTypeDirectory) {
+ sp<AaptDir> subdir;
+ bool notAdded = false;
+ if (mDirs.indexOfKey(fileNames[i]) >= 0) {
+ subdir = mDirs.valueFor(fileNames[i]);
+ } else {
+ subdir = new AaptDir(fileNames[i], mPath.appendPathCopy(fileNames[i]));
+ notAdded = true;
+ }
+ ssize_t res = subdir->slurpFullTree(bundle, pathName, kind,
+ resType, fullResPaths);
+ if (res < NO_ERROR) {
+ return res;
+ }
+ if (res > 0 && notAdded) {
+ mDirs.add(fileNames[i], subdir);
+ }
+ count += res;
+ } else if (type == kFileTypeRegular) {
+ sp<AaptFile> file = new AaptFile(pathName, kind, resType);
+ status_t err = addLeafFile(fileNames[i], file);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ count++;
+
+ } else {
+ if (bundle->getVerbose())
+ printf(" (ignoring non-file/dir '%s')\n", pathName.string());
+ }
+ }
+
+ return count;
+}
+
+status_t AaptDir::validate() const
+{
+ const size_t NF = mFiles.size();
+ const size_t ND = mDirs.size();
+ size_t i;
+ for (i = 0; i < NF; i++) {
+ if (!validateFileName(mFiles.valueAt(i)->getLeaf().string())) {
+ SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
+ "Invalid filename. Unable to add.");
+ return UNKNOWN_ERROR;
+ }
+
+ size_t j;
+ for (j = i+1; j < NF; j++) {
+ if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(),
+ mFiles.valueAt(j)->getLeaf().string()) == 0) {
+ SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
+ "File is case-insensitive equivalent to: %s",
+ mFiles.valueAt(j)->getPrintableSource().string());
+ return UNKNOWN_ERROR;
+ }
+
+ // TODO: if ".gz", check for non-.gz; if non-, check for ".gz"
+ // (this is mostly caught by the "marked" stuff, below)
+ }
+
+ for (j = 0; j < ND; j++) {
+ if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(),
+ mDirs.valueAt(j)->getLeaf().string()) == 0) {
+ SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
+ "File conflicts with dir from: %s",
+ mDirs.valueAt(j)->getPrintableSource().string());
+ return UNKNOWN_ERROR;
+ }
+ }
+ }
+
+ for (i = 0; i < ND; i++) {
+ if (!validateFileName(mDirs.valueAt(i)->getLeaf().string())) {
+ SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error(
+ "Invalid directory name, unable to add.");
+ return UNKNOWN_ERROR;
+ }
+
+ size_t j;
+ for (j = i+1; j < ND; j++) {
+ if (strcasecmp(mDirs.valueAt(i)->getLeaf().string(),
+ mDirs.valueAt(j)->getLeaf().string()) == 0) {
+ SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error(
+ "Directory is case-insensitive equivalent to: %s",
+ mDirs.valueAt(j)->getPrintableSource().string());
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ status_t err = mDirs.valueAt(i)->validate();
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+void AaptDir::print(const String8& prefix) const
+{
+ const size_t ND=getDirs().size();
+ size_t i;
+ for (i=0; i<ND; i++) {
+ getDirs().valueAt(i)->print(prefix);
+ }
+
+ const size_t NF=getFiles().size();
+ for (i=0; i<NF; i++) {
+ getFiles().valueAt(i)->print(prefix);
+ }
+}
+
+String8 AaptDir::getPrintableSource() const
+{
+ if (mFiles.size() > 0) {
+ // Arbitrarily pull the first file out of the list as the source dir.
+ return mFiles.valueAt(0)->getPrintableSource().getPathDir();
+ }
+ if (mDirs.size() > 0) {
+ // Or arbitrarily pull the first dir out of the list as the source dir.
+ return mDirs.valueAt(0)->getPrintableSource().getPathDir();
+ }
+
+ // Should never hit this case, but to be safe...
+ return mPath;
+
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+status_t AaptSymbols::applyJavaSymbols(const sp<AaptSymbols>& javaSymbols)
+{
+ status_t err = NO_ERROR;
+ size_t N = javaSymbols->mSymbols.size();
+ for (size_t i=0; i<N; i++) {
+ const String8& name = javaSymbols->mSymbols.keyAt(i);
+ const AaptSymbolEntry& entry = javaSymbols->mSymbols.valueAt(i);
+ ssize_t pos = mSymbols.indexOfKey(name);
+ if (pos < 0) {
+ entry.sourcePos.error("Symbol '%s' declared with <java-symbol> not defined\n", name.string());
+ err = UNKNOWN_ERROR;
+ continue;
+ }
+ //printf("**** setting symbol #%d/%d %s to isJavaSymbol=%d\n",
+ // i, N, name.string(), entry.isJavaSymbol ? 1 : 0);
+ mSymbols.editValueAt(pos).isJavaSymbol = entry.isJavaSymbol;
+ }
+
+ N = javaSymbols->mNestedSymbols.size();
+ for (size_t i=0; i<N; i++) {
+ const String8& name = javaSymbols->mNestedSymbols.keyAt(i);
+ const sp<AaptSymbols>& symbols = javaSymbols->mNestedSymbols.valueAt(i);
+ ssize_t pos = mNestedSymbols.indexOfKey(name);
+ if (pos < 0) {
+ SourcePos pos;
+ pos.error("Java symbol dir %s not defined\n", name.string());
+ err = UNKNOWN_ERROR;
+ continue;
+ }
+ //printf("**** applying java symbols in dir %s\n", name.string());
+ status_t myerr = mNestedSymbols.valueAt(pos)->applyJavaSymbols(symbols);
+ if (myerr != NO_ERROR) {
+ err = myerr;
+ }
+ }
+
+ return err;
+}
+
+// =========================================================================
+// =========================================================================
+// =========================================================================
+
+AaptAssets::AaptAssets()
+ : AaptDir(String8(), String8()),
+ mChanged(false), mHaveIncludedAssets(false), mRes(NULL)
+{
+}
+
+const SortedVector<AaptGroupEntry>& AaptAssets::getGroupEntries() const {
+ if (mChanged) {
+ }
+ return mGroupEntries;
+}
+
+status_t AaptAssets::addFile(const String8& name, const sp<AaptGroup>& file)
+{
+ mChanged = true;
+ return AaptDir::addFile(name, file);
+}
+
+sp<AaptFile> AaptAssets::addFile(
+ const String8& filePath, const AaptGroupEntry& entry,
+ const String8& srcDir, sp<AaptGroup>* outGroup,
+ const String8& resType)
+{
+ sp<AaptDir> dir = this;
+ sp<AaptGroup> group;
+ sp<AaptFile> file;
+ String8 root, remain(filePath), partialPath;
+ while (remain.length() > 0) {
+ root = remain.walkPath(&remain);
+ partialPath.appendPath(root);
+
+ const String8 rootStr(root);
+
+ if (remain.length() == 0) {
+ ssize_t i = dir->getFiles().indexOfKey(rootStr);
+ if (i >= 0) {
+ group = dir->getFiles().valueAt(i);
+ } else {
+ group = new AaptGroup(rootStr, filePath);
+ status_t res = dir->addFile(rootStr, group);
+ if (res != NO_ERROR) {
+ return NULL;
+ }
+ }
+ file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType);
+ status_t res = group->addFile(file);
+ if (res != NO_ERROR) {
+ return NULL;
+ }
+ break;
+
+ } else {
+ ssize_t i = dir->getDirs().indexOfKey(rootStr);
+ if (i >= 0) {
+ dir = dir->getDirs().valueAt(i);
+ } else {
+ sp<AaptDir> subdir = new AaptDir(rootStr, partialPath);
+ status_t res = dir->addDir(rootStr, subdir);
+ if (res != NO_ERROR) {
+ return NULL;
+ }
+ dir = subdir;
+ }
+ }
+ }
+
+ mGroupEntries.add(entry);
+ if (outGroup) *outGroup = group;
+ return file;
+}
+
+void AaptAssets::addResource(const String8& leafName, const String8& path,
+ const sp<AaptFile>& file, const String8& resType)
+{
+ sp<AaptDir> res = AaptDir::makeDir(kResString);
+ String8 dirname = file->getGroupEntry().toDirName(resType);
+ sp<AaptDir> subdir = res->makeDir(dirname);
+ sp<AaptGroup> grr = new AaptGroup(leafName, path);
+ grr->addFile(file);
+
+ subdir->addFile(leafName, grr);
+}
+
+
+ssize_t AaptAssets::slurpFromArgs(Bundle* bundle)
+{
+ int count;
+ int totalCount = 0;
+ FileType type;
+ const Vector<const char *>& resDirs = bundle->getResourceSourceDirs();
+ const size_t dirCount =resDirs.size();
+ sp<AaptAssets> current = this;
+
+ const int N = bundle->getFileSpecCount();
+
+ /*
+ * If a package manifest was specified, include that first.
+ */
+ if (bundle->getAndroidManifestFile() != NULL) {
+ // place at root of zip.
+ String8 srcFile(bundle->getAndroidManifestFile());
+ addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(),
+ NULL, String8());
+ totalCount++;
+ }
+
+ /*
+ * If a directory of custom assets was supplied, slurp 'em up.
+ */
+ if (bundle->getAssetSourceDir()) {
+ const char* assetDir = bundle->getAssetSourceDir();
+
+ FileType type = getFileType(assetDir);
+ if (type == kFileTypeNonexistent) {
+ fprintf(stderr, "ERROR: asset directory '%s' does not exist\n", assetDir);
+ return UNKNOWN_ERROR;
+ }
+ if (type != kFileTypeDirectory) {
+ fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
+ return UNKNOWN_ERROR;
+ }
+
+ String8 assetRoot(assetDir);
+ sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));
+ AaptGroupEntry group;
+ count = assetAaptDir->slurpFullTree(bundle, assetRoot, group,
+ String8(), mFullAssetPaths);
+ if (count < 0) {
+ totalCount = count;
+ goto bail;
+ }
+ if (count > 0) {
+ mGroupEntries.add(group);
+ }
+ totalCount += count;
+
+ if (bundle->getVerbose())
+ printf("Found %d custom asset file%s in %s\n",
+ count, (count==1) ? "" : "s", assetDir);
+ }
+
+ /*
+ * If a directory of resource-specific assets was supplied, slurp 'em up.
+ */
+ for (size_t i=0; i<dirCount; i++) {
+ const char *res = resDirs[i];
+ if (res) {
+ type = getFileType(res);
+ if (type == kFileTypeNonexistent) {
+ fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res);
+ return UNKNOWN_ERROR;
+ }
+ if (type == kFileTypeDirectory) {
+ if (i>0) {
+ sp<AaptAssets> nextOverlay = new AaptAssets();
+ current->setOverlay(nextOverlay);
+ current = nextOverlay;
+ current->setFullResPaths(mFullResPaths);
+ }
+ count = current->slurpResourceTree(bundle, String8(res));
+ if (i > 0 && count > 0) {
+ count = current->filter(bundle);
+ }
+
+ if (count < 0) {
+ totalCount = count;
+ goto bail;
+ }
+ totalCount += count;
+ }
+ else {
+ fprintf(stderr, "ERROR: '%s' is not a directory\n", res);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ }
+ /*
+ * Now do any additional raw files.
+ */
+ for (int arg=0; arg<N; arg++) {
+ const char* assetDir = bundle->getFileSpecEntry(arg);
+
+ FileType type = getFileType(assetDir);
+ if (type == kFileTypeNonexistent) {
+ fprintf(stderr, "ERROR: input directory '%s' does not exist\n", assetDir);
+ return UNKNOWN_ERROR;
+ }
+ if (type != kFileTypeDirectory) {
+ fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
+ return UNKNOWN_ERROR;
+ }
+
+ String8 assetRoot(assetDir);
+
+ if (bundle->getVerbose())
+ printf("Processing raw dir '%s'\n", (const char*) assetDir);
+
+ /*
+ * Do a recursive traversal of subdir tree. We don't make any
+ * guarantees about ordering, so we're okay with an inorder search
+ * using whatever order the OS happens to hand back to us.
+ */
+ count = slurpFullTree(bundle, assetRoot, AaptGroupEntry(), String8(), mFullAssetPaths);
+ if (count < 0) {
+ /* failure; report error and remove archive */
+ totalCount = count;
+ goto bail;
+ }
+ totalCount += count;
+
+ if (bundle->getVerbose())
+ printf("Found %d asset file%s in %s\n",
+ count, (count==1) ? "" : "s", assetDir);
+ }
+
+ count = validate();
+ if (count != NO_ERROR) {
+ totalCount = count;
+ goto bail;
+ }
+
+ count = filter(bundle);
+ if (count != NO_ERROR) {
+ totalCount = count;
+ goto bail;
+ }
+
+bail:
+ return totalCount;
+}
+
+ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir,
+ const AaptGroupEntry& kind,
+ const String8& resType,
+ sp<FilePathStore>& fullResPaths)
+{
+ ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths);
+ if (res > 0) {
+ mGroupEntries.add(kind);
+ }
+
+ return res;
+}
+
+ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir)
+{
+ ssize_t err = 0;
+
+ DIR* dir = opendir(srcDir.string());
+ if (dir == NULL) {
+ fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+
+ status_t count = 0;
+
+ /*
+ * Run through the directory, looking for dirs that match the
+ * expected pattern.
+ */
+ while (1) {
+ struct dirent* entry = readdir(dir);
+ if (entry == NULL) {
+ break;
+ }
+
+ if (isHidden(srcDir.string(), entry->d_name)) {
+ continue;
+ }
+
+ String8 subdirName(srcDir);
+ subdirName.appendPath(entry->d_name);
+
+ AaptGroupEntry group;
+ String8 resType;
+ bool b = group.initFromDirName(entry->d_name, &resType);
+ if (!b) {
+ fprintf(stderr, "invalid resource directory name: %s/%s\n", srcDir.string(),
+ entry->d_name);
+ err = -1;
+ continue;
+ }
+
+ if (bundle->getMaxResVersion() != NULL && group.getVersionString().length() != 0) {
+ int maxResInt = atoi(bundle->getMaxResVersion());
+ const char *verString = group.getVersionString().string();
+ int dirVersionInt = atoi(verString + 1); // skip 'v' in version name
+ if (dirVersionInt > maxResInt) {
+ fprintf(stderr, "max res %d, skipping %s\n", maxResInt, entry->d_name);
+ continue;
+ }
+ }
+
+ FileType type = getFileType(subdirName.string());
+
+ if (type == kFileTypeDirectory) {
+ sp<AaptDir> dir = makeDir(resType);
+ ssize_t res = dir->slurpFullTree(bundle, subdirName, group,
+ resType, mFullResPaths);
+ if (res < 0) {
+ count = res;
+ goto bail;
+ }
+ if (res > 0) {
+ mGroupEntries.add(group);
+ count += res;
+ }
+
+ // Only add this directory if we don't already have a resource dir
+ // for the current type. This ensures that we only add the dir once
+ // for all configs.
+ sp<AaptDir> rdir = resDir(resType);
+ if (rdir == NULL) {
+ mResDirs.add(dir);
+ }
+ } else {
+ if (bundle->getVerbose()) {
+ fprintf(stderr, " (ignoring file '%s')\n", subdirName.string());
+ }
+ }
+ }
+
+bail:
+ closedir(dir);
+ dir = NULL;
+
+ if (err != 0) {
+ return err;
+ }
+ return count;
+}
+
+ssize_t
+AaptAssets::slurpResourceZip(Bundle* bundle, const char* filename)
+{
+ int count = 0;
+ SortedVector<AaptGroupEntry> entries;
+
+ ZipFile* zip = new ZipFile;
+ status_t err = zip->open(filename, ZipFile::kOpenReadOnly);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "error opening zip file %s\n", filename);
+ count = err;
+ delete zip;
+ return -1;
+ }
+
+ const int N = zip->getNumEntries();
+ for (int i=0; i<N; i++) {
+ ZipEntry* entry = zip->getEntryByIndex(i);
+ if (entry->getDeleted()) {
+ continue;
+ }
+
+ String8 entryName(entry->getFileName());
+
+ String8 dirName = entryName.getPathDir();
+ sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName);
+
+ String8 resType;
+ AaptGroupEntry kind;
+
+ String8 remain;
+ if (entryName.walkPath(&remain) == kResourceDir) {
+ // these are the resources, pull their type out of the directory name
+ kind.initFromDirName(remain.walkPath().string(), &resType);
+ } else {
+ // these are untyped and don't have an AaptGroupEntry
+ }
+ if (entries.indexOf(kind) < 0) {
+ entries.add(kind);
+ mGroupEntries.add(kind);
+ }
+
+ // use the one from the zip file if they both exist.
+ dir->removeFile(entryName.getPathLeaf());
+
+ sp<AaptFile> file = new AaptFile(entryName, kind, resType);
+ status_t err = dir->addLeafFile(entryName.getPathLeaf(), file);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "err=%s entryName=%s\n", strerror(err), entryName.string());
+ count = err;
+ goto bail;
+ }
+ file->setCompressionMethod(entry->getCompressionMethod());
+
+#if 0
+ if (entryName == "AndroidManifest.xml") {
+ printf("AndroidManifest.xml\n");
+ }
+ printf("\n\nfile: %s\n", entryName.string());
+#endif
+
+ size_t len = entry->getUncompressedLen();
+ void* data = zip->uncompress(entry);
+ void* buf = file->editData(len);
+ memcpy(buf, data, len);
+
+#if 0
+ const int OFF = 0;
+ const unsigned char* p = (unsigned char*)data;
+ const unsigned char* end = p+len;
+ p += OFF;
+ for (int i=0; i<32 && p < end; i++) {
+ printf("0x%03x ", i*0x10 + OFF);
+ for (int j=0; j<0x10 && p < end; j++) {
+ printf(" %02x", *p);
+ p++;
+ }
+ printf("\n");
+ }
+#endif
+
+ free(data);
+
+ count++;
+ }
+
+bail:
+ delete zip;
+ return count;
+}
+
+status_t AaptAssets::filter(Bundle* bundle)
+{
+ ResourceFilter reqFilter;
+ status_t err = reqFilter.parse(bundle->getConfigurations());
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ ResourceFilter prefFilter;
+ err = prefFilter.parse(bundle->getPreferredConfigurations());
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ if (reqFilter.isEmpty() && prefFilter.isEmpty()) {
+ return NO_ERROR;
+ }
+
+ if (bundle->getVerbose()) {
+ if (!reqFilter.isEmpty()) {
+ printf("Applying required filter: %s\n",
+ bundle->getConfigurations());
+ }
+ if (!prefFilter.isEmpty()) {
+ printf("Applying preferred filter: %s\n",
+ bundle->getPreferredConfigurations());
+ }
+ }
+
+ const Vector<sp<AaptDir> >& resdirs = mResDirs;
+ const size_t ND = resdirs.size();
+ for (size_t i=0; i<ND; i++) {
+ const sp<AaptDir>& dir = resdirs.itemAt(i);
+ if (dir->getLeaf() == kValuesDir) {
+ // The "value" dir is special since a single file defines
+ // multiple resources, so we can not do filtering on the
+ // files themselves.
+ continue;
+ }
+ if (dir->getLeaf() == kMipmapDir) {
+ // We also skip the "mipmap" directory, since the point of this
+ // is to include all densities without stripping. If you put
+ // other configurations in here as well they won't be stripped
+ // either... So don't do that. Seriously. What is wrong with you?
+ continue;
+ }
+
+ const size_t NG = dir->getFiles().size();
+ for (size_t j=0; j<NG; j++) {
+ sp<AaptGroup> grp = dir->getFiles().valueAt(j);
+
+ // First remove any configurations we know we don't need.
+ for (size_t k=0; k<grp->getFiles().size(); k++) {
+ sp<AaptFile> file = grp->getFiles().valueAt(k);
+ if (k == 0 && grp->getFiles().size() == 1) {
+ // If this is the only file left, we need to keep it.
+ // Otherwise the resource IDs we are using will be inconsistent
+ // with what we get when not stripping. Sucky, but at least
+ // for now we can rely on the back-end doing another filtering
+ // pass to take this out and leave us with this resource name
+ // containing no entries.
+ continue;
+ }
+ if (file->getPath().getPathExtension() == ".xml") {
+ // We can't remove .xml files at this point, because when
+ // we parse them they may add identifier resources, so
+ // removing them can cause our resource identifiers to
+ // become inconsistent.
+ continue;
+ }
+ const ResTable_config& config(file->getGroupEntry().toParams());
+ if (!reqFilter.match(config)) {
+ if (bundle->getVerbose()) {
+ printf("Pruning unneeded resource: %s\n",
+ file->getPrintableSource().string());
+ }
+ grp->removeFile(k);
+ k--;
+ }
+ }
+
+ // Quick check: no preferred filters, nothing more to do.
+ if (prefFilter.isEmpty()) {
+ continue;
+ }
+
+ // Get the preferred density if there is one. We do not match exactly for density.
+ // If our preferred density is hdpi but we only have mdpi and xhdpi resources, we
+ // pick xhdpi.
+ uint32_t preferredDensity = 0;
+ const SortedVector<uint32_t>* preferredConfigs = prefFilter.configsForAxis(AXIS_DENSITY);
+ if (preferredConfigs != NULL && preferredConfigs->size() > 0) {
+ preferredDensity = (*preferredConfigs)[0];
+ }
+
+ // Now deal with preferred configurations.
+ for (int axis=AXIS_START; axis<=AXIS_END; axis++) {
+ for (size_t k=0; k<grp->getFiles().size(); k++) {
+ sp<AaptFile> file = grp->getFiles().valueAt(k);
+ if (k == 0 && grp->getFiles().size() == 1) {
+ // If this is the only file left, we need to keep it.
+ // Otherwise the resource IDs we are using will be inconsistent
+ // with what we get when not stripping. Sucky, but at least
+ // for now we can rely on the back-end doing another filtering
+ // pass to take this out and leave us with this resource name
+ // containing no entries.
+ continue;
+ }
+ if (file->getPath().getPathExtension() == ".xml") {
+ // We can't remove .xml files at this point, because when
+ // we parse them they may add identifier resources, so
+ // removing them can cause our resource identifiers to
+ // become inconsistent.
+ continue;
+ }
+ const ResTable_config& config(file->getGroupEntry().toParams());
+ if (!prefFilter.match(axis, config)) {
+ // This is a resource we would prefer not to have. Check
+ // to see if have a similar variation that we would like
+ // to have and, if so, we can drop it.
+
+ uint32_t bestDensity = config.density;
+
+ for (size_t m=0; m<grp->getFiles().size(); m++) {
+ if (m == k) continue;
+ sp<AaptFile> mfile = grp->getFiles().valueAt(m);
+ const ResTable_config& mconfig(mfile->getGroupEntry().toParams());
+ if (AaptGroupEntry::configSameExcept(config, mconfig, axis)) {
+ if (axis == AXIS_DENSITY && preferredDensity > 0) {
+ // See if there is a better density resource
+ if (mconfig.density < bestDensity &&
+ mconfig.density > preferredDensity &&
+ bestDensity > preferredDensity) {
+ // This density is between our best density and
+ // the preferred density, therefore it is better.
+ bestDensity = mconfig.density;
+ } else if (mconfig.density > bestDensity &&
+ bestDensity < preferredDensity) {
+ // This density is better than our best density and
+ // our best density was smaller than our preferred
+ // density, so it is better.
+ bestDensity = mconfig.density;
+ }
+ } else if (prefFilter.match(axis, mconfig)) {
+ if (bundle->getVerbose()) {
+ printf("Pruning unneeded resource: %s\n",
+ file->getPrintableSource().string());
+ }
+ grp->removeFile(k);
+ k--;
+ break;
+ }
+ }
+ }
+
+ if (axis == AXIS_DENSITY && preferredDensity > 0 &&
+ bestDensity != config.density) {
+ if (bundle->getVerbose()) {
+ printf("Pruning unneeded resource: %s\n",
+ file->getPrintableSource().string());
+ }
+ grp->removeFile(k);
+ k--;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return NO_ERROR;
+}
+
+sp<AaptSymbols> AaptAssets::getSymbolsFor(const String8& name)
+{
+ sp<AaptSymbols> sym = mSymbols.valueFor(name);
+ if (sym == NULL) {
+ sym = new AaptSymbols();
+ mSymbols.add(name, sym);
+ }
+ return sym;
+}
+
+sp<AaptSymbols> AaptAssets::getJavaSymbolsFor(const String8& name)
+{
+ sp<AaptSymbols> sym = mJavaSymbols.valueFor(name);
+ if (sym == NULL) {
+ sym = new AaptSymbols();
+ mJavaSymbols.add(name, sym);
+ }
+ return sym;
+}
+
+status_t AaptAssets::applyJavaSymbols()
+{
+ size_t N = mJavaSymbols.size();
+ for (size_t i=0; i<N; i++) {
+ const String8& name = mJavaSymbols.keyAt(i);
+ const sp<AaptSymbols>& symbols = mJavaSymbols.valueAt(i);
+ ssize_t pos = mSymbols.indexOfKey(name);
+ if (pos < 0) {
+ SourcePos pos;
+ pos.error("Java symbol dir %s not defined\n", name.string());
+ return UNKNOWN_ERROR;
+ }
+ //printf("**** applying java symbols in dir %s\n", name.string());
+ status_t err = mSymbols.valueAt(pos)->applyJavaSymbols(symbols);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+bool AaptAssets::isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const {
+ //printf("isJavaSymbol %s: public=%d, includePrivate=%d, isJavaSymbol=%d\n",
+ // sym.name.string(), sym.isPublic ? 1 : 0, includePrivate ? 1 : 0,
+ // sym.isJavaSymbol ? 1 : 0);
+ if (!mHavePrivateSymbols) return true;
+ if (sym.isPublic) return true;
+ if (includePrivate && sym.isJavaSymbol) return true;
+ return false;
+}
+
+status_t AaptAssets::buildIncludedResources(Bundle* bundle)
+{
+ if (!mHaveIncludedAssets) {
+ // Add in all includes.
+ const Vector<const char*>& incl = bundle->getPackageIncludes();
+ const size_t N=incl.size();
+ for (size_t i=0; i<N; i++) {
+ if (bundle->getVerbose())
+ printf("Including resources from package: %s\n", incl[i]);
+ if (!mIncludedAssets.addAssetPath(String8(incl[i]), NULL)) {
+ fprintf(stderr, "ERROR: Asset package include '%s' not found.\n",
+ incl[i]);
+ return UNKNOWN_ERROR;
+ }
+ }
+ mHaveIncludedAssets = true;
+ }
+
+ return NO_ERROR;
+}
+
+status_t AaptAssets::addIncludedResources(const sp<AaptFile>& file)
+{
+ const ResTable& res = getIncludedResources();
+ // XXX dirty!
+ return const_cast<ResTable&>(res).add(file->getData(), file->getSize(), NULL);
+}
+
+const ResTable& AaptAssets::getIncludedResources() const
+{
+ return mIncludedAssets.getResources(false);
+}
+
+void AaptAssets::print(const String8& prefix) const
+{
+ String8 innerPrefix(prefix);
+ innerPrefix.append(" ");
+ String8 innerInnerPrefix(innerPrefix);
+ innerInnerPrefix.append(" ");
+ printf("%sConfigurations:\n", prefix.string());
+ const size_t N=mGroupEntries.size();
+ for (size_t i=0; i<N; i++) {
+ String8 cname = mGroupEntries.itemAt(i).toDirName(String8());
+ printf("%s %s\n", prefix.string(),
+ cname != "" ? cname.string() : "(default)");
+ }
+
+ printf("\n%sFiles:\n", prefix.string());
+ AaptDir::print(innerPrefix);
+
+ printf("\n%sResource Dirs:\n", prefix.string());
+ const Vector<sp<AaptDir> >& resdirs = mResDirs;
+ const size_t NR = resdirs.size();
+ for (size_t i=0; i<NR; i++) {
+ const sp<AaptDir>& d = resdirs.itemAt(i);
+ printf("%s Type %s\n", prefix.string(), d->getLeaf().string());
+ d->print(innerInnerPrefix);
+ }
+}
+
+sp<AaptDir> AaptAssets::resDir(const String8& name) const
+{
+ const Vector<sp<AaptDir> >& resdirs = mResDirs;
+ const size_t N = resdirs.size();
+ for (size_t i=0; i<N; i++) {
+ const sp<AaptDir>& d = resdirs.itemAt(i);
+ if (d->getLeaf() == name) {
+ return d;
+ }
+ }
+ return NULL;
+}
+
+bool
+valid_symbol_name(const String8& symbol)
+{
+ static char const * const KEYWORDS[] = {
+ "abstract", "assert", "boolean", "break",
+ "byte", "case", "catch", "char", "class", "const", "continue",
+ "default", "do", "double", "else", "enum", "extends", "final",
+ "finally", "float", "for", "goto", "if", "implements", "import",
+ "instanceof", "int", "interface", "long", "native", "new", "package",
+ "private", "protected", "public", "return", "short", "static",
+ "strictfp", "super", "switch", "synchronized", "this", "throw",
+ "throws", "transient", "try", "void", "volatile", "while",
+ "true", "false", "null",
+ NULL
+ };
+ const char*const* k = KEYWORDS;
+ const char*const s = symbol.string();
+ while (*k) {
+ if (0 == strcmp(s, *k)) {
+ return false;
+ }
+ k++;
+ }
+ return true;
+}
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
new file mode 100644
index 0000000..5cfa913
--- /dev/null
+++ b/tools/aapt/AaptAssets.h
@@ -0,0 +1,633 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Information about assets being operated on.
+//
+#ifndef __AAPT_ASSETS_H
+#define __AAPT_ASSETS_H
+
+#include <stdlib.h>
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+#include <utils/SortedVector.h>
+#include <utils/String8.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include "ZipFile.h"
+
+#include "Bundle.h"
+#include "SourcePos.h"
+
+using namespace android;
+
+
+extern const char * const gDefaultIgnoreAssets;
+extern const char * gUserIgnoreAssets;
+
+bool valid_symbol_name(const String8& str);
+
+class AaptAssets;
+
+enum {
+ AXIS_NONE = 0,
+ AXIS_MCC = 1,
+ AXIS_MNC,
+ AXIS_LANGUAGE,
+ AXIS_REGION,
+ AXIS_SCREENLAYOUTSIZE,
+ AXIS_SCREENLAYOUTLONG,
+ AXIS_ORIENTATION,
+ AXIS_UIMODETYPE,
+ AXIS_UIMODENIGHT,
+ AXIS_DENSITY,
+ AXIS_TOUCHSCREEN,
+ AXIS_KEYSHIDDEN,
+ AXIS_KEYBOARD,
+ AXIS_NAVHIDDEN,
+ AXIS_NAVIGATION,
+ AXIS_SCREENSIZE,
+ AXIS_SMALLESTSCREENWIDTHDP,
+ AXIS_SCREENWIDTHDP,
+ AXIS_SCREENHEIGHTDP,
+ AXIS_LAYOUTDIR,
+ AXIS_VERSION,
+
+ AXIS_START = AXIS_MCC,
+ AXIS_END = AXIS_VERSION,
+};
+
+/**
+ * This structure contains a specific variation of a single file out
+ * of all the variations it can have that we can have.
+ */
+struct AaptGroupEntry
+{
+public:
+ AaptGroupEntry() : mParamsChanged(true) { }
+ AaptGroupEntry(const String8& _locale, const String8& _vendor)
+ : locale(_locale), vendor(_vendor), mParamsChanged(true) { }
+
+ bool initFromDirName(const char* dir, String8* resType);
+
+ static status_t parseNamePart(const String8& part, int* axis, uint32_t* value);
+
+ static uint32_t getConfigValueForAxis(const ResTable_config& config, int axis);
+
+ static bool configSameExcept(const ResTable_config& config,
+ const ResTable_config& otherConfig, int axis);
+
+ static bool getMccName(const char* name, ResTable_config* out = NULL);
+ static bool getMncName(const char* name, ResTable_config* out = NULL);
+ static bool getLocaleName(const char* name, ResTable_config* out = NULL);
+ static bool getScreenLayoutSizeName(const char* name, ResTable_config* out = NULL);
+ static bool getScreenLayoutLongName(const char* name, ResTable_config* out = NULL);
+ static bool getOrientationName(const char* name, ResTable_config* out = NULL);
+ static bool getUiModeTypeName(const char* name, ResTable_config* out = NULL);
+ static bool getUiModeNightName(const char* name, ResTable_config* out = NULL);
+ static bool getDensityName(const char* name, ResTable_config* out = NULL);
+ static bool getTouchscreenName(const char* name, ResTable_config* out = NULL);
+ static bool getKeysHiddenName(const char* name, ResTable_config* out = NULL);
+ static bool getKeyboardName(const char* name, ResTable_config* out = NULL);
+ static bool getNavigationName(const char* name, ResTable_config* out = NULL);
+ static bool getNavHiddenName(const char* name, ResTable_config* out = NULL);
+ static bool getScreenSizeName(const char* name, ResTable_config* out = NULL);
+ static bool getSmallestScreenWidthDpName(const char* name, ResTable_config* out = NULL);
+ static bool getScreenWidthDpName(const char* name, ResTable_config* out = NULL);
+ static bool getScreenHeightDpName(const char* name, ResTable_config* out = NULL);
+ static bool getLayoutDirectionName(const char* name, ResTable_config* out = NULL);
+ static bool getVersionName(const char* name, ResTable_config* out = NULL);
+
+ int compare(const AaptGroupEntry& o) const;
+
+ const ResTable_config& toParams() const;
+
+ inline bool operator<(const AaptGroupEntry& o) const { return compare(o) < 0; }
+ inline bool operator<=(const AaptGroupEntry& o) const { return compare(o) <= 0; }
+ inline bool operator==(const AaptGroupEntry& o) const { return compare(o) == 0; }
+ inline bool operator!=(const AaptGroupEntry& o) const { return compare(o) != 0; }
+ inline bool operator>=(const AaptGroupEntry& o) const { return compare(o) >= 0; }
+ inline bool operator>(const AaptGroupEntry& o) const { return compare(o) > 0; }
+
+ String8 toString() const;
+ String8 toDirName(const String8& resType) const;
+
+ const String8& getVersionString() const { return version; }
+
+private:
+ String8 mcc;
+ String8 mnc;
+ String8 locale;
+ String8 vendor;
+ String8 smallestScreenWidthDp;
+ String8 screenWidthDp;
+ String8 screenHeightDp;
+ String8 screenLayoutSize;
+ String8 screenLayoutLong;
+ String8 orientation;
+ String8 uiModeType;
+ String8 uiModeNight;
+ String8 density;
+ String8 touchscreen;
+ String8 keysHidden;
+ String8 keyboard;
+ String8 navHidden;
+ String8 navigation;
+ String8 screenSize;
+ String8 layoutDirection;
+ String8 version;
+
+ mutable bool mParamsChanged;
+ mutable ResTable_config mParams;
+};
+
+inline int compare_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs)
+{
+ return lhs.compare(rhs);
+}
+
+inline int strictly_order_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs)
+{
+ return compare_type(lhs, rhs) < 0;
+}
+
+class AaptGroup;
+class FilePathStore;
+
+/**
+ * A single asset file we know about.
+ */
+class AaptFile : public RefBase
+{
+public:
+ AaptFile(const String8& sourceFile, const AaptGroupEntry& groupEntry,
+ const String8& resType)
+ : mGroupEntry(groupEntry)
+ , mResourceType(resType)
+ , mSourceFile(sourceFile)
+ , mData(NULL)
+ , mDataSize(0)
+ , mBufferSize(0)
+ , mCompression(ZipEntry::kCompressStored)
+ {
+ //printf("new AaptFile created %s\n", (const char*)sourceFile);
+ }
+ virtual ~AaptFile() {
+ free(mData);
+ }
+
+ const String8& getPath() const { return mPath; }
+ const AaptGroupEntry& getGroupEntry() const { return mGroupEntry; }
+
+ // Data API. If there is data attached to the file,
+ // getSourceFile() is not used.
+ bool hasData() const { return mData != NULL; }
+ const void* getData() const { return mData; }
+ size_t getSize() const { return mDataSize; }
+ void* editData(size_t size);
+ void* editData(size_t* outSize = NULL);
+ void* padData(size_t wordSize);
+ status_t writeData(const void* data, size_t size);
+ void clearData();
+
+ const String8& getResourceType() const { return mResourceType; }
+
+ // File API. If the file does not hold raw data, this is
+ // a full path to a file on the filesystem that holds its data.
+ const String8& getSourceFile() const { return mSourceFile; }
+
+ String8 getPrintableSource() const;
+
+ // Desired compression method, as per utils/ZipEntry.h. For example,
+ // no compression is ZipEntry::kCompressStored.
+ int getCompressionMethod() const { return mCompression; }
+ void setCompressionMethod(int c) { mCompression = c; }
+private:
+ friend class AaptGroup;
+
+ String8 mPath;
+ AaptGroupEntry mGroupEntry;
+ String8 mResourceType;
+ String8 mSourceFile;
+ void* mData;
+ size_t mDataSize;
+ size_t mBufferSize;
+ int mCompression;
+};
+
+/**
+ * A group of related files (the same file, with different
+ * vendor/locale variations).
+ */
+class AaptGroup : public RefBase
+{
+public:
+ AaptGroup(const String8& leaf, const String8& path)
+ : mLeaf(leaf), mPath(path) { }
+ virtual ~AaptGroup() { }
+
+ const String8& getLeaf() const { return mLeaf; }
+
+ // Returns the relative path after the AaptGroupEntry dirs.
+ const String8& getPath() const { return mPath; }
+
+ const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& getFiles() const
+ { return mFiles; }
+
+ status_t addFile(const sp<AaptFile>& file);
+ void removeFile(size_t index);
+
+ void print(const String8& prefix) const;
+
+ String8 getPrintableSource() const;
+
+private:
+ String8 mLeaf;
+ String8 mPath;
+
+ DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > mFiles;
+};
+
+/**
+ * A single directory of assets, which can contain files and other
+ * sub-directories.
+ */
+class AaptDir : public RefBase
+{
+public:
+ AaptDir(const String8& leaf, const String8& path)
+ : mLeaf(leaf), mPath(path) { }
+ virtual ~AaptDir() { }
+
+ const String8& getLeaf() const { return mLeaf; }
+
+ const String8& getPath() const { return mPath; }
+
+ const DefaultKeyedVector<String8, sp<AaptGroup> >& getFiles() const { return mFiles; }
+ const DefaultKeyedVector<String8, sp<AaptDir> >& getDirs() const { return mDirs; }
+
+ virtual status_t addFile(const String8& name, const sp<AaptGroup>& file);
+
+ void removeFile(const String8& name);
+ void removeDir(const String8& name);
+
+ /*
+ * Perform some sanity checks on the names of files and directories here.
+ * In particular:
+ * - Check for illegal chars in filenames.
+ * - Check filename length.
+ * - Check for presence of ".gz" and non-".gz" copies of same file.
+ * - Check for multiple files whose names match in a case-insensitive
+ * fashion (problematic for some systems).
+ *
+ * Comparing names against all other names is O(n^2). We could speed
+ * it up some by sorting the entries and being smarter about what we
+ * compare against, but I'm not expecting to have enough files in a
+ * single directory to make a noticeable difference in speed.
+ *
+ * Note that sorting here is not enough to guarantee that the package
+ * contents are sorted -- subsequent updates can rearrange things.
+ */
+ status_t validate() const;
+
+ void print(const String8& prefix) const;
+
+ String8 getPrintableSource() const;
+
+private:
+ friend class AaptAssets;
+
+ status_t addDir(const String8& name, const sp<AaptDir>& dir);
+ sp<AaptDir> makeDir(const String8& name);
+ status_t addLeafFile(const String8& leafName,
+ const sp<AaptFile>& file);
+ virtual ssize_t slurpFullTree(Bundle* bundle,
+ const String8& srcDir,
+ const AaptGroupEntry& kind,
+ const String8& resType,
+ sp<FilePathStore>& fullResPaths);
+
+ String8 mLeaf;
+ String8 mPath;
+
+ DefaultKeyedVector<String8, sp<AaptGroup> > mFiles;
+ DefaultKeyedVector<String8, sp<AaptDir> > mDirs;
+};
+
+/**
+ * All information we know about a particular symbol.
+ */
+class AaptSymbolEntry
+{
+public:
+ AaptSymbolEntry()
+ : isPublic(false), isJavaSymbol(false), typeCode(TYPE_UNKNOWN)
+ {
+ }
+ AaptSymbolEntry(const String8& _name)
+ : name(_name), isPublic(false), isJavaSymbol(false), typeCode(TYPE_UNKNOWN)
+ {
+ }
+ AaptSymbolEntry(const AaptSymbolEntry& o)
+ : name(o.name), sourcePos(o.sourcePos), isPublic(o.isPublic)
+ , isJavaSymbol(o.isJavaSymbol), comment(o.comment), typeComment(o.typeComment)
+ , typeCode(o.typeCode), int32Val(o.int32Val), stringVal(o.stringVal)
+ {
+ }
+ AaptSymbolEntry operator=(const AaptSymbolEntry& o)
+ {
+ sourcePos = o.sourcePos;
+ isPublic = o.isPublic;
+ isJavaSymbol = o.isJavaSymbol;
+ comment = o.comment;
+ typeComment = o.typeComment;
+ typeCode = o.typeCode;
+ int32Val = o.int32Val;
+ stringVal = o.stringVal;
+ return *this;
+ }
+
+ const String8 name;
+
+ SourcePos sourcePos;
+ bool isPublic;
+ bool isJavaSymbol;
+
+ String16 comment;
+ String16 typeComment;
+
+ enum {
+ TYPE_UNKNOWN = 0,
+ TYPE_INT32,
+ TYPE_STRING
+ };
+
+ int typeCode;
+
+ // Value. May be one of these.
+ int32_t int32Val;
+ String8 stringVal;
+};
+
+/**
+ * A group of related symbols (such as indices into a string block)
+ * that have been generated from the assets.
+ */
+class AaptSymbols : public RefBase
+{
+public:
+ AaptSymbols() { }
+ virtual ~AaptSymbols() { }
+
+ status_t addSymbol(const String8& name, int32_t value, const SourcePos& pos) {
+ if (!check_valid_symbol_name(name, pos, "symbol")) {
+ return BAD_VALUE;
+ }
+ AaptSymbolEntry& sym = edit_symbol(name, &pos);
+ sym.typeCode = AaptSymbolEntry::TYPE_INT32;
+ sym.int32Val = value;
+ return NO_ERROR;
+ }
+
+ status_t addStringSymbol(const String8& name, const String8& value,
+ const SourcePos& pos) {
+ if (!check_valid_symbol_name(name, pos, "symbol")) {
+ return BAD_VALUE;
+ }
+ AaptSymbolEntry& sym = edit_symbol(name, &pos);
+ sym.typeCode = AaptSymbolEntry::TYPE_STRING;
+ sym.stringVal = value;
+ return NO_ERROR;
+ }
+
+ status_t makeSymbolPublic(const String8& name, const SourcePos& pos) {
+ if (!check_valid_symbol_name(name, pos, "symbol")) {
+ return BAD_VALUE;
+ }
+ AaptSymbolEntry& sym = edit_symbol(name, &pos);
+ sym.isPublic = true;
+ return NO_ERROR;
+ }
+
+ status_t makeSymbolJavaSymbol(const String8& name, const SourcePos& pos) {
+ if (!check_valid_symbol_name(name, pos, "symbol")) {
+ return BAD_VALUE;
+ }
+ AaptSymbolEntry& sym = edit_symbol(name, &pos);
+ sym.isJavaSymbol = true;
+ return NO_ERROR;
+ }
+
+ void appendComment(const String8& name, const String16& comment, const SourcePos& pos) {
+ if (comment.size() <= 0) {
+ return;
+ }
+ AaptSymbolEntry& sym = edit_symbol(name, &pos);
+ if (sym.comment.size() == 0) {
+ sym.comment = comment;
+ } else {
+ sym.comment.append(String16("\n"));
+ sym.comment.append(comment);
+ }
+ }
+
+ void appendTypeComment(const String8& name, const String16& comment) {
+ if (comment.size() <= 0) {
+ return;
+ }
+ AaptSymbolEntry& sym = edit_symbol(name, NULL);
+ if (sym.typeComment.size() == 0) {
+ sym.typeComment = comment;
+ } else {
+ sym.typeComment.append(String16("\n"));
+ sym.typeComment.append(comment);
+ }
+ }
+
+ sp<AaptSymbols> addNestedSymbol(const String8& name, const SourcePos& pos) {
+ if (!check_valid_symbol_name(name, pos, "nested symbol")) {
+ return NULL;
+ }
+
+ sp<AaptSymbols> sym = mNestedSymbols.valueFor(name);
+ if (sym == NULL) {
+ sym = new AaptSymbols();
+ mNestedSymbols.add(name, sym);
+ }
+
+ return sym;
+ }
+
+ status_t applyJavaSymbols(const sp<AaptSymbols>& javaSymbols);
+
+ const KeyedVector<String8, AaptSymbolEntry>& getSymbols() const
+ { return mSymbols; }
+ const DefaultKeyedVector<String8, sp<AaptSymbols> >& getNestedSymbols() const
+ { return mNestedSymbols; }
+
+ const String16& getComment(const String8& name) const
+ { return get_symbol(name).comment; }
+ const String16& getTypeComment(const String8& name) const
+ { return get_symbol(name).typeComment; }
+
+private:
+ bool check_valid_symbol_name(const String8& symbol, const SourcePos& pos, const char* label) {
+ if (valid_symbol_name(symbol)) {
+ return true;
+ }
+ pos.error("invalid %s: '%s'\n", label, symbol.string());
+ return false;
+ }
+ AaptSymbolEntry& edit_symbol(const String8& symbol, const SourcePos* pos) {
+ ssize_t i = mSymbols.indexOfKey(symbol);
+ if (i < 0) {
+ i = mSymbols.add(symbol, AaptSymbolEntry(symbol));
+ }
+ AaptSymbolEntry& sym = mSymbols.editValueAt(i);
+ if (pos != NULL && sym.sourcePos.line < 0) {
+ sym.sourcePos = *pos;
+ }
+ return sym;
+ }
+ const AaptSymbolEntry& get_symbol(const String8& symbol) const {
+ ssize_t i = mSymbols.indexOfKey(symbol);
+ if (i >= 0) {
+ return mSymbols.valueAt(i);
+ }
+ return mDefSymbol;
+ }
+
+ KeyedVector<String8, AaptSymbolEntry> mSymbols;
+ DefaultKeyedVector<String8, sp<AaptSymbols> > mNestedSymbols;
+ AaptSymbolEntry mDefSymbol;
+};
+
+class ResourceTypeSet : public RefBase,
+ public KeyedVector<String8,sp<AaptGroup> >
+{
+public:
+ ResourceTypeSet();
+};
+
+// Storage for lists of fully qualified paths for
+// resources encountered during slurping.
+class FilePathStore : public RefBase,
+ public Vector<String8>
+{
+public:
+ FilePathStore();
+};
+
+/**
+ * Asset hierarchy being operated on.
+ */
+class AaptAssets : public AaptDir
+{
+public:
+ AaptAssets();
+ virtual ~AaptAssets() { delete mRes; }
+
+ const String8& getPackage() const { return mPackage; }
+ void setPackage(const String8& package) {
+ mPackage = package;
+ mSymbolsPrivatePackage = package;
+ mHavePrivateSymbols = false;
+ }
+
+ const SortedVector<AaptGroupEntry>& getGroupEntries() const;
+
+ virtual status_t addFile(const String8& name, const sp<AaptGroup>& file);
+
+ sp<AaptFile> addFile(const String8& filePath,
+ const AaptGroupEntry& entry,
+ const String8& srcDir,
+ sp<AaptGroup>* outGroup,
+ const String8& resType);
+
+ void addResource(const String8& leafName,
+ const String8& path,
+ const sp<AaptFile>& file,
+ const String8& resType);
+
+ void addGroupEntry(const AaptGroupEntry& entry) { mGroupEntries.add(entry); }
+
+ ssize_t slurpFromArgs(Bundle* bundle);
+
+ sp<AaptSymbols> getSymbolsFor(const String8& name);
+
+ sp<AaptSymbols> getJavaSymbolsFor(const String8& name);
+
+ status_t applyJavaSymbols();
+
+ const DefaultKeyedVector<String8, sp<AaptSymbols> >& getSymbols() const { return mSymbols; }
+
+ String8 getSymbolsPrivatePackage() const { return mSymbolsPrivatePackage; }
+ void setSymbolsPrivatePackage(const String8& pkg) {
+ mSymbolsPrivatePackage = pkg;
+ mHavePrivateSymbols = mSymbolsPrivatePackage != mPackage;
+ }
+
+ bool havePrivateSymbols() const { return mHavePrivateSymbols; }
+
+ bool isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const;
+
+ status_t buildIncludedResources(Bundle* bundle);
+ status_t addIncludedResources(const sp<AaptFile>& file);
+ const ResTable& getIncludedResources() const;
+
+ void print(const String8& prefix) const;
+
+ inline const Vector<sp<AaptDir> >& resDirs() const { return mResDirs; }
+ sp<AaptDir> resDir(const String8& name) const;
+
+ inline sp<AaptAssets> getOverlay() { return mOverlay; }
+ inline void setOverlay(sp<AaptAssets>& overlay) { mOverlay = overlay; }
+
+ inline KeyedVector<String8, sp<ResourceTypeSet> >* getResources() { return mRes; }
+ inline void
+ setResources(KeyedVector<String8, sp<ResourceTypeSet> >* res) { delete mRes; mRes = res; }
+
+ inline sp<FilePathStore>& getFullResPaths() { return mFullResPaths; }
+ inline void
+ setFullResPaths(sp<FilePathStore>& res) { mFullResPaths = res; }
+
+ inline sp<FilePathStore>& getFullAssetPaths() { return mFullAssetPaths; }
+ inline void
+ setFullAssetPaths(sp<FilePathStore>& res) { mFullAssetPaths = res; }
+
+private:
+ virtual ssize_t slurpFullTree(Bundle* bundle,
+ const String8& srcDir,
+ const AaptGroupEntry& kind,
+ const String8& resType,
+ sp<FilePathStore>& fullResPaths);
+
+ ssize_t slurpResourceTree(Bundle* bundle, const String8& srcDir);
+ ssize_t slurpResourceZip(Bundle* bundle, const char* filename);
+
+ status_t filter(Bundle* bundle);
+
+ String8 mPackage;
+ SortedVector<AaptGroupEntry> mGroupEntries;
+ DefaultKeyedVector<String8, sp<AaptSymbols> > mSymbols;
+ DefaultKeyedVector<String8, sp<AaptSymbols> > mJavaSymbols;
+ String8 mSymbolsPrivatePackage;
+ bool mHavePrivateSymbols;
+
+ Vector<sp<AaptDir> > mResDirs;
+
+ bool mChanged;
+
+ bool mHaveIncludedAssets;
+ AssetManager mIncludedAssets;
+
+ sp<AaptAssets> mOverlay;
+ KeyedVector<String8, sp<ResourceTypeSet> >* mRes;
+
+ sp<FilePathStore> mFullResPaths;
+ sp<FilePathStore> mFullAssetPaths;
+};
+
+#endif // __AAPT_ASSETS_H
+
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
new file mode 100644
index 0000000..2949f8e
--- /dev/null
+++ b/tools/aapt/Android.mk
@@ -0,0 +1,104 @@
+#
+# Copyright 2006 The Android Open Source Project
+#
+# Android Asset Packaging Tool
+#
+
+# This tool is prebuilt if we're doing an app-only build.
+ifeq ($(TARGET_BUILD_APPS),)
+
+
+aapt_src_files := \
+ AaptAssets.cpp \
+ Command.cpp \
+ CrunchCache.cpp \
+ FileFinder.cpp \
+ Main.cpp \
+ Package.cpp \
+ StringPool.cpp \
+ XMLNode.cpp \
+ ResourceFilter.cpp \
+ ResourceIdCache.cpp \
+ ResourceTable.cpp \
+ Images.cpp \
+ Resource.cpp \
+ pseudolocalize.cpp \
+ SourcePos.cpp \
+ WorkQueue.cpp \
+ ZipEntry.cpp \
+ ZipFile.cpp \
+ qsort_r_compat.c
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(aapt_src_files)
+
+LOCAL_CFLAGS += -Wno-format-y2k
+ifeq (darwin,$(HOST_OS))
+LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS
+endif
+
+LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS
+
+LOCAL_C_INCLUDES += external/libpng
+LOCAL_C_INCLUDES += external/zlib
+
+LOCAL_STATIC_LIBRARIES := \
+ libandroidfw \
+ libutils \
+ libcutils \
+ libexpat \
+ libpng \
+ liblog \
+ libziparchive-host
+
+ifeq ($(HOST_OS),linux)
+LOCAL_LDLIBS += -lrt -ldl -lpthread
+endif
+
+# Statically link libz for MinGW (Win SDK under Linux),
+# and dynamically link for all others.
+ifneq ($(strip $(USE_MINGW)),)
+ LOCAL_STATIC_LIBRARIES += libz
+else
+ LOCAL_LDLIBS += -lz
+endif
+
+LOCAL_MODULE := aapt
+
+include $(BUILD_HOST_EXECUTABLE)
+
+# aapt for running on the device
+# =========================================================
+ifneq ($(SDK_ONLY),true)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(aapt_src_files)
+
+LOCAL_MODULE := aapt
+
+LOCAL_C_INCLUDES += bionic
+LOCAL_C_INCLUDES += bionic/libstdc++/include
+LOCAL_C_INCLUDES += external/stlport/stlport
+LOCAL_C_INCLUDES += external/libpng
+LOCAL_C_INCLUDES += external/zlib
+
+LOCAL_CFLAGS += -Wno-non-virtual-dtor
+
+LOCAL_SHARED_LIBRARIES := \
+ libandroidfw \
+ libutils \
+ libcutils \
+ libpng \
+ liblog \
+ libz
+
+LOCAL_STATIC_LIBRARIES := \
+ libstlport_static \
+ libexpat_static
+
+include $(BUILD_EXECUTABLE)
+endif
+
+endif # TARGET_BUILD_APPS
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
new file mode 100644
index 0000000..52d9266
--- /dev/null
+++ b/tools/aapt/Bundle.h
@@ -0,0 +1,313 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// State bundle. Used to pass around stuff like command-line args.
+//
+#ifndef __BUNDLE_H
+#define __BUNDLE_H
+
+#include <stdlib.h>
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+enum {
+ SDK_CUPCAKE = 3,
+ SDK_DONUT = 4,
+ SDK_ECLAIR = 5,
+ SDK_ECLAIR_0_1 = 6,
+ SDK_MR1 = 7,
+ SDK_FROYO = 8,
+ SDK_HONEYCOMB_MR2 = 13,
+ SDK_ICE_CREAM_SANDWICH = 14,
+ SDK_ICE_CREAM_SANDWICH_MR1 = 15,
+};
+
+/*
+ * Things we can do.
+ */
+typedef enum Command {
+ kCommandUnknown = 0,
+ kCommandVersion,
+ kCommandList,
+ kCommandDump,
+ kCommandAdd,
+ kCommandRemove,
+ kCommandPackage,
+ kCommandCrunch,
+ kCommandSingleCrunch,
+} Command;
+
+/*
+ * Bundle of goodies, including everything specified on the command line.
+ */
+class Bundle {
+public:
+ Bundle(void)
+ : mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false),
+ mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false),
+ mUpdate(false), mExtending(false),
+ mRequireLocalization(false), mPseudolocalize(false),
+ mWantUTF16(false), mValues(false), mIncludeMetaData(false),
+ mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL),
+ mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL),
+ mAutoAddOverlay(false), mGenDependencies(false),
+ mAssetSourceDir(NULL),
+ mCrunchedOutputDir(NULL), mProguardFile(NULL),
+ mAndroidManifestFile(NULL), mPublicOutputFile(NULL),
+ mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL),
+ mMinSdkVersion(NULL), mTargetSdkVersion(NULL), mMaxSdkVersion(NULL),
+ mVersionCode(NULL), mVersionName(NULL), mCustomPackage(NULL), mExtraPackages(NULL),
+ mMaxResVersion(NULL), mDebugMode(false), mNonConstantId(false), mProduct(NULL),
+ mUseCrunchCache(false), mErrorOnFailedInsert(false), mErrorOnMissingConfigEntry(false),
+ mOutputTextSymbols(NULL),
+ mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL),
+ mArgc(0), mArgv(NULL)
+ {}
+ ~Bundle(void) {}
+
+ /*
+ * Set the command value. Returns "false" if it was previously set.
+ */
+ Command getCommand(void) const { return mCmd; }
+ void setCommand(Command cmd) { mCmd = cmd; }
+
+ /*
+ * Command modifiers. Not all modifiers are appropriate for all
+ * commands.
+ */
+ bool getVerbose(void) const { return mVerbose; }
+ void setVerbose(bool val) { mVerbose = val; }
+ bool getAndroidList(void) const { return mAndroidList; }
+ void setAndroidList(bool val) { mAndroidList = val; }
+ bool getForce(void) const { return mForce; }
+ void setForce(bool val) { mForce = val; }
+ void setGrayscaleTolerance(int val) { mGrayscaleTolerance = val; }
+ int getGrayscaleTolerance() const { return mGrayscaleTolerance; }
+ bool getMakePackageDirs(void) const { return mMakePackageDirs; }
+ void setMakePackageDirs(bool val) { mMakePackageDirs = val; }
+ bool getUpdate(void) const { return mUpdate; }
+ void setUpdate(bool val) { mUpdate = val; }
+ bool getExtending(void) const { return mExtending; }
+ void setExtending(bool val) { mExtending = val; }
+ bool getRequireLocalization(void) const { return mRequireLocalization; }
+ void setRequireLocalization(bool val) { mRequireLocalization = val; }
+ bool getPseudolocalize(void) const { return mPseudolocalize; }
+ void setPseudolocalize(bool val) { mPseudolocalize = val; }
+ void setWantUTF16(bool val) { mWantUTF16 = val; }
+ bool getValues(void) const { return mValues; }
+ void setValues(bool val) { mValues = val; }
+ bool getIncludeMetaData(void) const { return mIncludeMetaData; }
+ void setIncludeMetaData(bool val) { mIncludeMetaData = val; }
+ int getCompressionMethod(void) const { return mCompressionMethod; }
+ void setCompressionMethod(int val) { mCompressionMethod = val; }
+ bool getJunkPath(void) const { return mJunkPath; }
+ void setJunkPath(bool val) { mJunkPath = val; }
+ const char* getOutputAPKFile() const { return mOutputAPKFile; }
+ void setOutputAPKFile(const char* val) { mOutputAPKFile = val; }
+ const char* getManifestPackageNameOverride() const { return mManifestPackageNameOverride; }
+ void setManifestPackageNameOverride(const char * val) { mManifestPackageNameOverride = val; }
+ const char* getInstrumentationPackageNameOverride() const { return mInstrumentationPackageNameOverride; }
+ void setInstrumentationPackageNameOverride(const char * val) { mInstrumentationPackageNameOverride = val; }
+ bool getAutoAddOverlay() { return mAutoAddOverlay; }
+ void setAutoAddOverlay(bool val) { mAutoAddOverlay = val; }
+ bool getGenDependencies() { return mGenDependencies; }
+ void setGenDependencies(bool val) { mGenDependencies = val; }
+ bool getErrorOnFailedInsert() { return mErrorOnFailedInsert; }
+ void setErrorOnFailedInsert(bool val) { mErrorOnFailedInsert = val; }
+ bool getErrorOnMissingConfigEntry() { return mErrorOnMissingConfigEntry; }
+ void setErrorOnMissingConfigEntry(bool val) { mErrorOnMissingConfigEntry = val; }
+
+ bool getUTF16StringsOption() {
+ return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO);
+ }
+
+ /*
+ * Input options.
+ */
+ const char* getAssetSourceDir() const { return mAssetSourceDir; }
+ void setAssetSourceDir(const char* dir) { mAssetSourceDir = dir; }
+ const char* getCrunchedOutputDir() const { return mCrunchedOutputDir; }
+ void setCrunchedOutputDir(const char* dir) { mCrunchedOutputDir = dir; }
+ const char* getProguardFile() const { return mProguardFile; }
+ void setProguardFile(const char* file) { mProguardFile = file; }
+ const android::Vector<const char*>& getResourceSourceDirs() const { return mResourceSourceDirs; }
+ void addResourceSourceDir(const char* dir) { mResourceSourceDirs.insertAt(dir,0); }
+ const char* getAndroidManifestFile() const { return mAndroidManifestFile; }
+ void setAndroidManifestFile(const char* file) { mAndroidManifestFile = file; }
+ const char* getPublicOutputFile() const { return mPublicOutputFile; }
+ void setPublicOutputFile(const char* file) { mPublicOutputFile = file; }
+ const char* getRClassDir() const { return mRClassDir; }
+ void setRClassDir(const char* dir) { mRClassDir = dir; }
+ const char* getConfigurations() const { return mConfigurations.size() > 0 ? mConfigurations.string() : NULL; }
+ void addConfigurations(const char* val) { if (mConfigurations.size() > 0) { mConfigurations.append(","); mConfigurations.append(val); } else { mConfigurations = val; } }
+ const char* getPreferredConfigurations() const { return mPreferredConfigurations.size() > 0 ? mPreferredConfigurations.string() : NULL; }
+ void addPreferredConfigurations(const char* val) { if (mPreferredConfigurations.size() > 0) { mPreferredConfigurations.append(","); mPreferredConfigurations.append(val); } else { mPreferredConfigurations = val; } }
+ const char* getResourceIntermediatesDir() const { return mResourceIntermediatesDir; }
+ void setResourceIntermediatesDir(const char* dir) { mResourceIntermediatesDir = dir; }
+ const android::Vector<const char*>& getPackageIncludes() const { return mPackageIncludes; }
+ void addPackageInclude(const char* file) { mPackageIncludes.add(file); }
+ const android::Vector<const char*>& getJarFiles() const { return mJarFiles; }
+ void addJarFile(const char* file) { mJarFiles.add(file); }
+ const android::Vector<const char*>& getNoCompressExtensions() const { return mNoCompressExtensions; }
+ void addNoCompressExtension(const char* ext) { mNoCompressExtensions.add(ext); }
+
+ const char* getManifestMinSdkVersion() const { return mManifestMinSdkVersion; }
+ void setManifestMinSdkVersion(const char* val) { mManifestMinSdkVersion = val; }
+ const char* getMinSdkVersion() const { return mMinSdkVersion; }
+ void setMinSdkVersion(const char* val) { mMinSdkVersion = val; }
+ const char* getTargetSdkVersion() const { return mTargetSdkVersion; }
+ void setTargetSdkVersion(const char* val) { mTargetSdkVersion = val; }
+ const char* getMaxSdkVersion() const { return mMaxSdkVersion; }
+ void setMaxSdkVersion(const char* val) { mMaxSdkVersion = val; }
+ const char* getVersionCode() const { return mVersionCode; }
+ void setVersionCode(const char* val) { mVersionCode = val; }
+ const char* getVersionName() const { return mVersionName; }
+ void setVersionName(const char* val) { mVersionName = val; }
+ const char* getCustomPackage() const { return mCustomPackage; }
+ void setCustomPackage(const char* val) { mCustomPackage = val; }
+ const char* getExtraPackages() const { return mExtraPackages; }
+ void setExtraPackages(const char* val) { mExtraPackages = val; }
+ const char* getMaxResVersion() const { return mMaxResVersion; }
+ void setMaxResVersion(const char * val) { mMaxResVersion = val; }
+ bool getDebugMode() const { return mDebugMode; }
+ void setDebugMode(bool val) { mDebugMode = val; }
+ bool getNonConstantId() const { return mNonConstantId; }
+ void setNonConstantId(bool val) { mNonConstantId = val; }
+ const char* getProduct() const { return mProduct; }
+ void setProduct(const char * val) { mProduct = val; }
+ void setUseCrunchCache(bool val) { mUseCrunchCache = val; }
+ bool getUseCrunchCache() const { return mUseCrunchCache; }
+ const char* getOutputTextSymbols() const { return mOutputTextSymbols; }
+ void setOutputTextSymbols(const char* val) { mOutputTextSymbols = val; }
+ const char* getSingleCrunchInputFile() const { return mSingleCrunchInputFile; }
+ void setSingleCrunchInputFile(const char* val) { mSingleCrunchInputFile = val; }
+ const char* getSingleCrunchOutputFile() const { return mSingleCrunchOutputFile; }
+ void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; }
+
+ /*
+ * Set and get the file specification.
+ *
+ * Note this does NOT make a copy of argv.
+ */
+ void setFileSpec(char* const argv[], int argc) {
+ mArgc = argc;
+ mArgv = argv;
+ }
+ int getFileSpecCount(void) const { return mArgc; }
+ const char* getFileSpecEntry(int idx) const { return mArgv[idx]; }
+ void eatArgs(int n) {
+ if (n > mArgc) n = mArgc;
+ mArgv += n;
+ mArgc -= n;
+ }
+
+#if 0
+ /*
+ * Package count. Nothing to do with anything else here; this is
+ * just a convenient place to stuff it so we don't have to pass it
+ * around everywhere.
+ */
+ int getPackageCount(void) const { return mPackageCount; }
+ void setPackageCount(int val) { mPackageCount = val; }
+#endif
+
+ /* Certain features may only be available on a specific SDK level or
+ * above. SDK levels that have a non-numeric identifier are assumed
+ * to be newer than any SDK level that has a number designated.
+ */
+ bool isMinSdkAtLeast(int desired) {
+ /* If the application specifies a minSdkVersion in the manifest
+ * then use that. Otherwise, check what the user specified on
+ * the command line. If neither, it's not available since
+ * the minimum SDK version is assumed to be 1.
+ */
+ const char *minVer;
+ if (mManifestMinSdkVersion != NULL) {
+ minVer = mManifestMinSdkVersion;
+ } else if (mMinSdkVersion != NULL) {
+ minVer = mMinSdkVersion;
+ } else {
+ return false;
+ }
+
+ char *end;
+ int minSdkNum = (int)strtol(minVer, &end, 0);
+ if (*end == '\0') {
+ if (minSdkNum < desired) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+private:
+ /* commands & modifiers */
+ Command mCmd;
+ bool mVerbose;
+ bool mAndroidList;
+ bool mForce;
+ int mGrayscaleTolerance;
+ bool mMakePackageDirs;
+ bool mUpdate;
+ bool mExtending;
+ bool mRequireLocalization;
+ bool mPseudolocalize;
+ bool mWantUTF16;
+ bool mValues;
+ bool mIncludeMetaData;
+ int mCompressionMethod;
+ bool mJunkPath;
+ const char* mOutputAPKFile;
+ const char* mManifestPackageNameOverride;
+ const char* mInstrumentationPackageNameOverride;
+ bool mAutoAddOverlay;
+ bool mGenDependencies;
+ const char* mAssetSourceDir;
+ const char* mCrunchedOutputDir;
+ const char* mProguardFile;
+ const char* mAndroidManifestFile;
+ const char* mPublicOutputFile;
+ const char* mRClassDir;
+ const char* mResourceIntermediatesDir;
+ android::String8 mConfigurations;
+ android::String8 mPreferredConfigurations;
+ android::Vector<const char*> mPackageIncludes;
+ android::Vector<const char*> mJarFiles;
+ android::Vector<const char*> mNoCompressExtensions;
+ android::Vector<const char*> mResourceSourceDirs;
+
+ const char* mManifestMinSdkVersion;
+ const char* mMinSdkVersion;
+ const char* mTargetSdkVersion;
+ const char* mMaxSdkVersion;
+ const char* mVersionCode;
+ const char* mVersionName;
+ const char* mCustomPackage;
+ const char* mExtraPackages;
+ const char* mMaxResVersion;
+ bool mDebugMode;
+ bool mNonConstantId;
+ const char* mProduct;
+ bool mUseCrunchCache;
+ bool mErrorOnFailedInsert;
+ bool mErrorOnMissingConfigEntry;
+ const char* mOutputTextSymbols;
+ const char* mSingleCrunchInputFile;
+ const char* mSingleCrunchOutputFile;
+
+ /* file specification */
+ int mArgc;
+ char* const* mArgv;
+
+#if 0
+ /* misc stuff */
+ int mPackageCount;
+#endif
+
+};
+
+#endif // __BUNDLE_H
diff --git a/tools/aapt/CacheUpdater.h b/tools/aapt/CacheUpdater.h
new file mode 100644
index 0000000..0e65589
--- /dev/null
+++ b/tools/aapt/CacheUpdater.h
@@ -0,0 +1,107 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Abstraction of calls to system to make directories and delete files and
+// wrapper to image processing.
+
+#ifndef CACHE_UPDATER_H
+#define CACHE_UPDATER_H
+
+#include <utils/String8.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include "Images.h"
+
+using namespace android;
+
+/** CacheUpdater
+ * This is a pure virtual class that declares abstractions of functions useful
+ * for managing a cache files. This manager is set up to be used in a
+ * mirror cache where the source tree is duplicated and filled with processed
+ * images. This class is abstracted to allow for dependency injection during
+ * unit testing.
+ * Usage:
+ * To update/add a file to the cache, call processImage
+ * To remove a file from the cache, call deleteFile
+ */
+class CacheUpdater {
+public:
+ // Make sure all the directories along this path exist
+ virtual void ensureDirectoriesExist(String8 path) = 0;
+
+ // Delete a file
+ virtual void deleteFile(String8 path) = 0;
+
+ // Process an image from source out to dest
+ virtual void processImage(String8 source, String8 dest) = 0;
+private:
+};
+
+/** SystemCacheUpdater
+ * This is an implementation of the above virtual cache updater specification.
+ * This implementations hits the filesystem to manage a cache and calls out to
+ * the PNG crunching in images.h to process images out to its cache components.
+ */
+class SystemCacheUpdater : public CacheUpdater {
+public:
+ // Constructor to set bundle to pass to preProcessImage
+ SystemCacheUpdater (Bundle* b)
+ : bundle(b) { };
+
+ // Make sure all the directories along this path exist
+ virtual void ensureDirectoriesExist(String8 path)
+ {
+ // Check to see if we're dealing with a fully qualified path
+ String8 existsPath;
+ String8 toCreate;
+ String8 remains;
+ struct stat s;
+
+ // Check optomistically to see if all directories exist.
+ // If something in the path doesn't exist, then walk the path backwards
+ // and find the place to start creating directories forward.
+ if (stat(path.string(),&s) == -1) {
+ // Walk backwards to find place to start creating directories
+ existsPath = path;
+ do {
+ // As we remove the end of existsPath add it to
+ // the string of paths to create.
+ toCreate = existsPath.getPathLeaf().appendPath(toCreate);
+ existsPath = existsPath.getPathDir();
+ } while (stat(existsPath.string(),&s) == -1);
+
+ // Walk forwards and build directories as we go
+ do {
+ // Advance to the next segment of the path
+ existsPath.appendPath(toCreate.walkPath(&remains));
+ toCreate = remains;
+#ifdef HAVE_MS_C_RUNTIME
+ _mkdir(existsPath.string());
+#else
+ mkdir(existsPath.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
+#endif
+ } while (remains.length() > 0);
+ } //if
+ };
+
+ // Delete a file
+ virtual void deleteFile(String8 path)
+ {
+ if (remove(path.string()) != 0)
+ fprintf(stderr,"ERROR DELETING %s\n",path.string());
+ };
+
+ // Process an image from source out to dest
+ virtual void processImage(String8 source, String8 dest)
+ {
+ // Make sure we're trying to write to a directory that is extant
+ ensureDirectoriesExist(dest.getPathDir());
+
+ preProcessImageToCache(bundle, source, dest);
+ };
+private:
+ Bundle* bundle;
+};
+
+#endif // CACHE_UPDATER_H
\ No newline at end of file
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
new file mode 100644
index 0000000..c527388
--- /dev/null
+++ b/tools/aapt/Command.cpp
@@ -0,0 +1,2407 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Android Asset Packaging Tool main entry point.
+//
+#include "Main.h"
+#include "Bundle.h"
+#include "ResourceFilter.h"
+#include "ResourceTable.h"
+#include "Images.h"
+#include "XMLNode.h"
+
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+
+#include <fcntl.h>
+#include <errno.h>
+
+using namespace android;
+
+/*
+ * Show version info. All the cool kids do it.
+ */
+int doVersion(Bundle* bundle)
+{
+ if (bundle->getFileSpecCount() != 0) {
+ printf("(ignoring extra arguments)\n");
+ }
+ printf("Android Asset Packaging Tool, v0.2\n");
+
+ return 0;
+}
+
+
+/*
+ * Open the file read only. The call fails if the file doesn't exist.
+ *
+ * Returns NULL on failure.
+ */
+ZipFile* openReadOnly(const char* fileName)
+{
+ ZipFile* zip;
+ status_t result;
+
+ zip = new ZipFile;
+ result = zip->open(fileName, ZipFile::kOpenReadOnly);
+ if (result != NO_ERROR) {
+ if (result == NAME_NOT_FOUND) {
+ fprintf(stderr, "ERROR: '%s' not found\n", fileName);
+ } else if (result == PERMISSION_DENIED) {
+ fprintf(stderr, "ERROR: '%s' access denied\n", fileName);
+ } else {
+ fprintf(stderr, "ERROR: failed opening '%s' as Zip file\n",
+ fileName);
+ }
+ delete zip;
+ return NULL;
+ }
+
+ return zip;
+}
+
+/*
+ * Open the file read-write. The file will be created if it doesn't
+ * already exist and "okayToCreate" is set.
+ *
+ * Returns NULL on failure.
+ */
+ZipFile* openReadWrite(const char* fileName, bool okayToCreate)
+{
+ ZipFile* zip = NULL;
+ status_t result;
+ int flags;
+
+ flags = ZipFile::kOpenReadWrite;
+ if (okayToCreate) {
+ flags |= ZipFile::kOpenCreate;
+ }
+
+ zip = new ZipFile;
+ result = zip->open(fileName, flags);
+ if (result != NO_ERROR) {
+ delete zip;
+ zip = NULL;
+ goto bail;
+ }
+
+bail:
+ return zip;
+}
+
+
+/*
+ * Return a short string describing the compression method.
+ */
+const char* compressionName(int method)
+{
+ if (method == ZipEntry::kCompressStored) {
+ return "Stored";
+ } else if (method == ZipEntry::kCompressDeflated) {
+ return "Deflated";
+ } else {
+ return "Unknown";
+ }
+}
+
+/*
+ * Return the percent reduction in size (0% == no compression).
+ */
+int calcPercent(long uncompressedLen, long compressedLen)
+{
+ if (!uncompressedLen) {
+ return 0;
+ } else {
+ return (int) (100.0 - (compressedLen * 100.0) / uncompressedLen + 0.5);
+ }
+}
+
+/*
+ * Handle the "list" command, which can be a simple file dump or
+ * a verbose listing.
+ *
+ * The verbose listing closely matches the output of the Info-ZIP "unzip"
+ * command.
+ */
+int doList(Bundle* bundle)
+{
+ int result = 1;
+ ZipFile* zip = NULL;
+ const ZipEntry* entry;
+ long totalUncLen, totalCompLen;
+ const char* zipFileName;
+
+ if (bundle->getFileSpecCount() != 1) {
+ fprintf(stderr, "ERROR: specify zip file name (only)\n");
+ goto bail;
+ }
+ zipFileName = bundle->getFileSpecEntry(0);
+
+ zip = openReadOnly(zipFileName);
+ if (zip == NULL) {
+ goto bail;
+ }
+
+ int count, i;
+
+ if (bundle->getVerbose()) {
+ printf("Archive: %s\n", zipFileName);
+ printf(
+ " Length Method Size Ratio Offset Date Time CRC-32 Name\n");
+ printf(
+ "-------- ------ ------- ----- ------- ---- ---- ------ ----\n");
+ }
+
+ totalUncLen = totalCompLen = 0;
+
+ count = zip->getNumEntries();
+ for (i = 0; i < count; i++) {
+ entry = zip->getEntryByIndex(i);
+ if (bundle->getVerbose()) {
+ char dateBuf[32];
+ time_t when;
+
+ when = entry->getModWhen();
+ strftime(dateBuf, sizeof(dateBuf), "%m-%d-%y %H:%M",
+ localtime(&when));
+
+ printf("%8ld %-7.7s %7ld %3d%% %8zd %s %08lx %s\n",
+ (long) entry->getUncompressedLen(),
+ compressionName(entry->getCompressionMethod()),
+ (long) entry->getCompressedLen(),
+ calcPercent(entry->getUncompressedLen(),
+ entry->getCompressedLen()),
+ (size_t) entry->getLFHOffset(),
+ dateBuf,
+ entry->getCRC32(),
+ entry->getFileName());
+ } else {
+ printf("%s\n", entry->getFileName());
+ }
+
+ totalUncLen += entry->getUncompressedLen();
+ totalCompLen += entry->getCompressedLen();
+ }
+
+ if (bundle->getVerbose()) {
+ printf(
+ "-------- ------- --- -------\n");
+ printf("%8ld %7ld %2d%% %d files\n",
+ totalUncLen,
+ totalCompLen,
+ calcPercent(totalUncLen, totalCompLen),
+ zip->getNumEntries());
+ }
+
+ if (bundle->getAndroidList()) {
+ AssetManager assets;
+ if (!assets.addAssetPath(String8(zipFileName), NULL)) {
+ fprintf(stderr, "ERROR: list -a failed because assets could not be loaded\n");
+ goto bail;
+ }
+
+ const ResTable& res = assets.getResources(false);
+ if (&res == NULL) {
+ printf("\nNo resource table found.\n");
+ } else {
+#ifndef HAVE_ANDROID_OS
+ printf("\nResource table:\n");
+ res.print(false);
+#endif
+ }
+
+ Asset* manifestAsset = assets.openNonAsset("AndroidManifest.xml",
+ Asset::ACCESS_BUFFER);
+ if (manifestAsset == NULL) {
+ printf("\nNo AndroidManifest.xml found.\n");
+ } else {
+ printf("\nAndroid manifest:\n");
+ ResXMLTree tree;
+ tree.setTo(manifestAsset->getBuffer(true),
+ manifestAsset->getLength());
+ printXMLBlock(&tree);
+ }
+ delete manifestAsset;
+ }
+
+ result = 0;
+
+bail:
+ delete zip;
+ return result;
+}
+
+static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes)
+{
+ size_t N = tree.getAttributeCount();
+ for (size_t i=0; i<N; i++) {
+ if (tree.getAttributeNameResID(i) == attrRes) {
+ return (ssize_t)i;
+ }
+ }
+ return -1;
+}
+
+String8 getAttribute(const ResXMLTree& tree, const char* ns,
+ const char* attr, String8* outError)
+{
+ ssize_t idx = tree.indexOfAttribute(ns, attr);
+ if (idx < 0) {
+ return String8();
+ }
+ Res_value value;
+ if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+ if (value.dataType != Res_value::TYPE_STRING) {
+ if (outError != NULL) {
+ *outError = "attribute is not a string value";
+ }
+ return String8();
+ }
+ }
+ size_t len;
+ const uint16_t* str = tree.getAttributeStringValue(idx, &len);
+ return str ? String8(str, len) : String8();
+}
+
+static String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError)
+{
+ ssize_t idx = indexOfAttribute(tree, attrRes);
+ if (idx < 0) {
+ return String8();
+ }
+ Res_value value;
+ if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+ if (value.dataType != Res_value::TYPE_STRING) {
+ if (outError != NULL) {
+ *outError = "attribute is not a string value";
+ }
+ return String8();
+ }
+ }
+ size_t len;
+ const uint16_t* str = tree.getAttributeStringValue(idx, &len);
+ return str ? String8(str, len) : String8();
+}
+
+static int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes,
+ String8* outError, int32_t defValue = -1)
+{
+ ssize_t idx = indexOfAttribute(tree, attrRes);
+ if (idx < 0) {
+ return defValue;
+ }
+ Res_value value;
+ if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+ if (value.dataType < Res_value::TYPE_FIRST_INT
+ || value.dataType > Res_value::TYPE_LAST_INT) {
+ if (outError != NULL) {
+ *outError = "attribute is not an integer value";
+ }
+ return defValue;
+ }
+ }
+ return value.data;
+}
+
+static int32_t getResolvedIntegerAttribute(const ResTable* resTable, const ResXMLTree& tree,
+ uint32_t attrRes, String8* outError, int32_t defValue = -1)
+{
+ ssize_t idx = indexOfAttribute(tree, attrRes);
+ if (idx < 0) {
+ return defValue;
+ }
+ Res_value value;
+ if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+ if (value.dataType == Res_value::TYPE_REFERENCE) {
+ resTable->resolveReference(&value, 0);
+ }
+ if (value.dataType < Res_value::TYPE_FIRST_INT
+ || value.dataType > Res_value::TYPE_LAST_INT) {
+ if (outError != NULL) {
+ *outError = "attribute is not an integer value";
+ }
+ return defValue;
+ }
+ }
+ return value.data;
+}
+
+static String8 getResolvedAttribute(const ResTable* resTable, const ResXMLTree& tree,
+ uint32_t attrRes, String8* outError)
+{
+ ssize_t idx = indexOfAttribute(tree, attrRes);
+ if (idx < 0) {
+ return String8();
+ }
+ Res_value value;
+ if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
+ if (value.dataType == Res_value::TYPE_STRING) {
+ size_t len;
+ const uint16_t* str = tree.getAttributeStringValue(idx, &len);
+ return str ? String8(str, len) : String8();
+ }
+ resTable->resolveReference(&value, 0);
+ if (value.dataType != Res_value::TYPE_STRING) {
+ if (outError != NULL) {
+ *outError = "attribute is not a string value";
+ }
+ return String8();
+ }
+ }
+ size_t len;
+ const Res_value* value2 = &value;
+ const char16_t* str = const_cast<ResTable*>(resTable)->valueToString(value2, 0, NULL, &len);
+ return str ? String8(str, len) : String8();
+}
+
+static void getResolvedResourceAttribute(Res_value* value, const ResTable* resTable,
+ const ResXMLTree& tree, uint32_t attrRes, String8* outError)
+{
+ ssize_t idx = indexOfAttribute(tree, attrRes);
+ if (idx < 0) {
+ if (outError != NULL) {
+ *outError = "attribute could not be found";
+ }
+ return;
+ }
+ if (tree.getAttributeValue(idx, value) != NO_ERROR) {
+ if (value->dataType == Res_value::TYPE_REFERENCE) {
+ resTable->resolveReference(value, 0);
+ }
+ // The attribute was found and was resolved if need be.
+ return;
+ }
+ if (outError != NULL) {
+ *outError = "error getting resolved resource attribute";
+ }
+}
+
+static void printResolvedResourceAttribute(const ResTable* resTable, const ResXMLTree& tree,
+ uint32_t attrRes, String8 attrLabel, String8* outError)
+{
+ Res_value value;
+ getResolvedResourceAttribute(&value, resTable, tree, attrRes, outError);
+ if (*outError != "") {
+ *outError = "error print resolved resource attribute";
+ return;
+ }
+ if (value.dataType == Res_value::TYPE_STRING) {
+ String8 result = getResolvedAttribute(resTable, tree, attrRes, outError);
+ printf("%s='%s'", attrLabel.string(),
+ ResTable::normalizeForOutput(result.string()).string());
+ } else if (Res_value::TYPE_FIRST_INT <= value.dataType &&
+ value.dataType <= Res_value::TYPE_LAST_INT) {
+ printf("%s='%d'", attrLabel.string(), value.data);
+ } else {
+ printf("%s='0x%x'", attrLabel.string(), (int)value.data);
+ }
+}
+
+// These are attribute resource constants for the platform, as found
+// in android.R.attr
+enum {
+ LABEL_ATTR = 0x01010001,
+ ICON_ATTR = 0x01010002,
+ NAME_ATTR = 0x01010003,
+ PERMISSION_ATTR = 0x01010006,
+ RESOURCE_ATTR = 0x01010025,
+ DEBUGGABLE_ATTR = 0x0101000f,
+ VALUE_ATTR = 0x01010024,
+ VERSION_CODE_ATTR = 0x0101021b,
+ VERSION_NAME_ATTR = 0x0101021c,
+ SCREEN_ORIENTATION_ATTR = 0x0101001e,
+ MIN_SDK_VERSION_ATTR = 0x0101020c,
+ MAX_SDK_VERSION_ATTR = 0x01010271,
+ REQ_TOUCH_SCREEN_ATTR = 0x01010227,
+ REQ_KEYBOARD_TYPE_ATTR = 0x01010228,
+ REQ_HARD_KEYBOARD_ATTR = 0x01010229,
+ REQ_NAVIGATION_ATTR = 0x0101022a,
+ REQ_FIVE_WAY_NAV_ATTR = 0x01010232,
+ TARGET_SDK_VERSION_ATTR = 0x01010270,
+ TEST_ONLY_ATTR = 0x01010272,
+ ANY_DENSITY_ATTR = 0x0101026c,
+ GL_ES_VERSION_ATTR = 0x01010281,
+ SMALL_SCREEN_ATTR = 0x01010284,
+ NORMAL_SCREEN_ATTR = 0x01010285,
+ LARGE_SCREEN_ATTR = 0x01010286,
+ XLARGE_SCREEN_ATTR = 0x010102bf,
+ REQUIRED_ATTR = 0x0101028e,
+ SCREEN_SIZE_ATTR = 0x010102ca,
+ SCREEN_DENSITY_ATTR = 0x010102cb,
+ REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364,
+ COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365,
+ LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366,
+ PUBLIC_KEY_ATTR = 0x010103a6,
+ CATEGORY_ATTR = 0x010103e8,
+};
+
+String8 getComponentName(String8 &pkgName, String8 &componentName) {
+ ssize_t idx = componentName.find(".");
+ String8 retStr(pkgName);
+ if (idx == 0) {
+ retStr += componentName;
+ } else if (idx < 0) {
+ retStr += ".";
+ retStr += componentName;
+ } else {
+ return componentName;
+ }
+ return retStr;
+}
+
+static void printCompatibleScreens(ResXMLTree& tree) {
+ size_t len;
+ ResXMLTree::event_code_t code;
+ int depth = 0;
+ bool first = true;
+ printf("compatible-screens:");
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ depth--;
+ if (depth < 0) {
+ break;
+ }
+ continue;
+ }
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ depth++;
+ String8 tag(tree.getElementName(&len));
+ if (tag == "screen") {
+ int32_t screenSize = getIntegerAttribute(tree,
+ SCREEN_SIZE_ATTR, NULL, -1);
+ int32_t screenDensity = getIntegerAttribute(tree,
+ SCREEN_DENSITY_ATTR, NULL, -1);
+ if (screenSize > 0 && screenDensity > 0) {
+ if (!first) {
+ printf(",");
+ }
+ first = false;
+ printf("'%d/%d'", screenSize, screenDensity);
+ }
+ }
+ }
+ printf("\n");
+}
+
+static void printUsesPermission(const String8& name, bool optional=false, int maxSdkVersion=-1) {
+ printf("uses-permission: name='%s'", ResTable::normalizeForOutput(name.string()).string());
+ if (maxSdkVersion != -1) {
+ printf(" maxSdkVersion='%d'", maxSdkVersion);
+ }
+ printf("\n");
+
+ if (optional) {
+ printf("optional-permission: name='%s'",
+ ResTable::normalizeForOutput(name.string()).string());
+ if (maxSdkVersion != -1) {
+ printf(" maxSdkVersion='%d'", maxSdkVersion);
+ }
+ printf("\n");
+ }
+}
+
+static void printUsesImpliedPermission(const String8& name, const String8& reason) {
+ printf("uses-implied-permission: name='%s' reason='%s'\n",
+ ResTable::normalizeForOutput(name.string()).string(),
+ ResTable::normalizeForOutput(reason.string()).string());
+}
+
+Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool offHost,
+ String8 *outError = NULL)
+{
+ Asset* aidAsset = assets.openNonAsset(xmlPath, Asset::ACCESS_BUFFER);
+ if (aidAsset == NULL) {
+ if (outError != NULL) *outError = "xml resource does not exist";
+ return Vector<String8>();
+ }
+
+ const String8 serviceTagName(offHost ? "offhost-apdu-service" : "host-apdu-service");
+
+ bool withinApduService = false;
+ Vector<String8> categories;
+
+ String8 error;
+ ResXMLTree tree;
+ tree.setTo(aidAsset->getBuffer(true), aidAsset->getLength());
+
+ size_t len;
+ int depth = 0;
+ ResXMLTree::event_code_t code;
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ depth--;
+ String8 tag(tree.getElementName(&len));
+
+ if (depth == 0 && tag == serviceTagName) {
+ withinApduService = false;
+ }
+
+ } else if (code == ResXMLTree::START_TAG) {
+ depth++;
+ String8 tag(tree.getElementName(&len));
+
+ if (depth == 1) {
+ if (tag == serviceTagName) {
+ withinApduService = true;
+ }
+ } else if (depth == 2 && withinApduService) {
+ if (tag == "aid-group") {
+ String8 category = getAttribute(tree, CATEGORY_ATTR, &error);
+ if (error != "") {
+ if (outError != NULL) *outError = error;
+ return Vector<String8>();
+ }
+
+ categories.add(category);
+ }
+ }
+ }
+ }
+ aidAsset->close();
+ return categories;
+}
+
+/*
+ * Handle the "dump" command, to extract select data from an archive.
+ */
+extern char CONSOLE_DATA[2925]; // see EOF
+int doDump(Bundle* bundle)
+{
+ status_t result = UNKNOWN_ERROR;
+ Asset* asset = NULL;
+
+ if (bundle->getFileSpecCount() < 1) {
+ fprintf(stderr, "ERROR: no dump option specified\n");
+ return 1;
+ }
+
+ if (bundle->getFileSpecCount() < 2) {
+ fprintf(stderr, "ERROR: no dump file specified\n");
+ return 1;
+ }
+
+ const char* option = bundle->getFileSpecEntry(0);
+ const char* filename = bundle->getFileSpecEntry(1);
+
+ AssetManager assets;
+ int32_t assetsCookie;
+ if (!assets.addAssetPath(String8(filename), &assetsCookie)) {
+ fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
+ return 1;
+ }
+
+ // Make a dummy config for retrieving resources... we need to supply
+ // non-default values for some configs so that we can retrieve resources
+ // in the app that don't have a default. The most important of these is
+ // the API version because key resources like icons will have an implicit
+ // version if they are using newer config types like density.
+ ResTable_config config;
+ config.language[0] = 'e';
+ config.language[1] = 'n';
+ config.country[0] = 'U';
+ config.country[1] = 'S';
+ config.orientation = ResTable_config::ORIENTATION_PORT;
+ config.density = ResTable_config::DENSITY_MEDIUM;
+ config.sdkVersion = 10000; // Very high.
+ config.screenWidthDp = 320;
+ config.screenHeightDp = 480;
+ config.smallestScreenWidthDp = 320;
+ assets.setConfiguration(config);
+
+ const ResTable& res = assets.getResources(false);
+ if (&res == NULL) {
+ fprintf(stderr, "ERROR: dump failed because no resource table was found\n");
+ goto bail;
+ }
+
+ if (strcmp("resources", option) == 0) {
+#ifndef HAVE_ANDROID_OS
+ res.print(bundle->getValues());
+#endif
+
+ } else if (strcmp("strings", option) == 0) {
+ const ResStringPool* pool = res.getTableStringBlock(0);
+ printStringPool(pool);
+
+ } else if (strcmp("xmltree", option) == 0) {
+ if (bundle->getFileSpecCount() < 3) {
+ fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
+ goto bail;
+ }
+
+ for (int i=2; i<bundle->getFileSpecCount(); i++) {
+ const char* resname = bundle->getFileSpecEntry(i);
+ ResXMLTree tree;
+ asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
+ if (asset == NULL) {
+ fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
+ goto bail;
+ }
+
+ if (tree.setTo(asset->getBuffer(true),
+ asset->getLength()) != NO_ERROR) {
+ fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
+ goto bail;
+ }
+ tree.restart();
+ printXMLBlock(&tree);
+ tree.uninit();
+ delete asset;
+ asset = NULL;
+ }
+
+ } else if (strcmp("xmlstrings", option) == 0) {
+ if (bundle->getFileSpecCount() < 3) {
+ fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
+ goto bail;
+ }
+
+ for (int i=2; i<bundle->getFileSpecCount(); i++) {
+ const char* resname = bundle->getFileSpecEntry(i);
+ ResXMLTree tree;
+ asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER);
+ if (asset == NULL) {
+ fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname);
+ goto bail;
+ }
+
+ if (tree.setTo(asset->getBuffer(true),
+ asset->getLength()) != NO_ERROR) {
+ fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname);
+ goto bail;
+ }
+ printStringPool(&tree.getStrings());
+ delete asset;
+ asset = NULL;
+ }
+
+ } else {
+ ResXMLTree tree;
+ asset = assets.openNonAsset("AndroidManifest.xml",
+ Asset::ACCESS_BUFFER);
+ if (asset == NULL) {
+ fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n");
+ goto bail;
+ }
+
+ if (tree.setTo(asset->getBuffer(true),
+ asset->getLength()) != NO_ERROR) {
+ fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n");
+ goto bail;
+ }
+ tree.restart();
+
+ if (strcmp("permissions", option) == 0) {
+ size_t len;
+ ResXMLTree::event_code_t code;
+ int depth = 0;
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ depth--;
+ continue;
+ }
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ depth++;
+ String8 tag(tree.getElementName(&len));
+ //printf("Depth %d tag %s\n", depth, tag.string());
+ if (depth == 1) {
+ if (tag != "manifest") {
+ fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
+ goto bail;
+ }
+ String8 pkg = getAttribute(tree, NULL, "package", NULL);
+ printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string());
+ } else if (depth == 2 && tag == "permission") {
+ String8 error;
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ goto bail;
+ }
+ printf("permission: %s\n",
+ ResTable::normalizeForOutput(name.string()).string());
+ } else if (depth == 2 && tag == "uses-permission") {
+ String8 error;
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ goto bail;
+ }
+ printUsesPermission(name,
+ getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
+ getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
+ }
+ }
+ } else if (strcmp("badging", option) == 0) {
+ Vector<String8> locales;
+ res.getLocales(&locales);
+
+ Vector<ResTable_config> configs;
+ res.getConfigurations(&configs);
+ SortedVector<int> densities;
+ const size_t NC = configs.size();
+ for (size_t i=0; i<NC; i++) {
+ int dens = configs[i].density;
+ if (dens == 0) {
+ dens = 160;
+ }
+ densities.add(dens);
+ }
+
+ size_t len;
+ ResXMLTree::event_code_t code;
+ int depth = 0;
+ String8 error;
+ bool withinActivity = false;
+ bool isMainActivity = false;
+ bool isLauncherActivity = false;
+ bool isSearchable = false;
+ bool withinApplication = false;
+ bool withinSupportsInput = false;
+ bool withinReceiver = false;
+ bool withinService = false;
+ bool withinIntentFilter = false;
+ bool hasMainActivity = false;
+ bool hasOtherActivities = false;
+ bool hasOtherReceivers = false;
+ bool hasOtherServices = false;
+ bool hasWallpaperService = false;
+ bool hasImeService = false;
+ bool hasAccessibilityService = false;
+ bool hasPrintService = false;
+ bool hasWidgetReceivers = false;
+ bool hasDeviceAdminReceiver = false;
+ bool hasIntentFilter = false;
+ bool hasPaymentService = false;
+ bool actMainActivity = false;
+ bool actWidgetReceivers = false;
+ bool actDeviceAdminEnabled = false;
+ bool actImeService = false;
+ bool actWallpaperService = false;
+ bool actAccessibilityService = false;
+ bool actPrintService = false;
+ bool actHostApduService = false;
+ bool actOffHostApduService = false;
+ bool hasMetaHostPaymentCategory = false;
+ bool hasMetaOffHostPaymentCategory = false;
+
+ // These permissions are required by services implementing services
+ // the system binds to (IME, Accessibility, PrintServices, etc.)
+ bool hasBindDeviceAdminPermission = false;
+ bool hasBindInputMethodPermission = false;
+ bool hasBindAccessibilityServicePermission = false;
+ bool hasBindPrintServicePermission = false;
+ bool hasBindNfcServicePermission = false;
+
+ // These two implement the implicit permissions that are granted
+ // to pre-1.6 applications.
+ bool hasWriteExternalStoragePermission = false;
+ bool hasReadPhoneStatePermission = false;
+
+ // If an app requests write storage, they will also get read storage.
+ bool hasReadExternalStoragePermission = false;
+
+ // Implement transition to read and write call log.
+ bool hasReadContactsPermission = false;
+ bool hasWriteContactsPermission = false;
+ bool hasReadCallLogPermission = false;
+ bool hasWriteCallLogPermission = false;
+
+ // This next group of variables is used to implement a group of
+ // backward-compatibility heuristics necessitated by the addition of
+ // some new uses-feature constants in 2.1 and 2.2. In most cases, the
+ // heuristic is "if an app requests a permission but doesn't explicitly
+ // request the corresponding <uses-feature>, presume it's there anyway".
+ bool specCameraFeature = false; // camera-related
+ bool specCameraAutofocusFeature = false;
+ bool reqCameraAutofocusFeature = false;
+ bool reqCameraFlashFeature = false;
+ bool hasCameraPermission = false;
+ bool specLocationFeature = false; // location-related
+ bool specNetworkLocFeature = false;
+ bool reqNetworkLocFeature = false;
+ bool specGpsFeature = false;
+ bool reqGpsFeature = false;
+ bool hasMockLocPermission = false;
+ bool hasCoarseLocPermission = false;
+ bool hasGpsPermission = false;
+ bool hasGeneralLocPermission = false;
+ bool specBluetoothFeature = false; // Bluetooth API-related
+ bool hasBluetoothPermission = false;
+ bool specMicrophoneFeature = false; // microphone-related
+ bool hasRecordAudioPermission = false;
+ bool specWiFiFeature = false;
+ bool hasWiFiPermission = false;
+ bool specTelephonyFeature = false; // telephony-related
+ bool reqTelephonySubFeature = false;
+ bool hasTelephonyPermission = false;
+ bool specTouchscreenFeature = false; // touchscreen-related
+ bool specMultitouchFeature = false;
+ bool reqDistinctMultitouchFeature = false;
+ bool specScreenPortraitFeature = false;
+ bool specScreenLandscapeFeature = false;
+ bool reqScreenPortraitFeature = false;
+ bool reqScreenLandscapeFeature = false;
+ // 2.2 also added some other features that apps can request, but that
+ // have no corresponding permission, so we cannot implement any
+ // back-compatibility heuristic for them. The below are thus unnecessary
+ // (but are retained here for documentary purposes.)
+ //bool specCompassFeature = false;
+ //bool specAccelerometerFeature = false;
+ //bool specProximityFeature = false;
+ //bool specAmbientLightFeature = false;
+ //bool specLiveWallpaperFeature = false;
+
+ int targetSdk = 0;
+ int smallScreen = 1;
+ int normalScreen = 1;
+ int largeScreen = 1;
+ int xlargeScreen = 1;
+ int anyDensity = 1;
+ int requiresSmallestWidthDp = 0;
+ int compatibleWidthLimitDp = 0;
+ int largestWidthLimitDp = 0;
+ String8 pkg;
+ String8 activityName;
+ String8 activityLabel;
+ String8 activityIcon;
+ String8 receiverName;
+ String8 serviceName;
+ Vector<String8> supportedInput;
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ depth--;
+ if (depth < 2) {
+ if (withinSupportsInput && !supportedInput.isEmpty()) {
+ printf("supports-input: '");
+ const size_t N = supportedInput.size();
+ for (size_t i=0; i<N; i++) {
+ printf("%s", ResTable::normalizeForOutput(
+ supportedInput[i].string()).string());
+ if (i != N - 1) {
+ printf("' '");
+ } else {
+ printf("'\n");
+ }
+ }
+ supportedInput.clear();
+ }
+ withinApplication = false;
+ withinSupportsInput = false;
+ } else if (depth < 3) {
+ if (withinActivity && isMainActivity && isLauncherActivity) {
+ String8 aName(getComponentName(pkg, activityName));
+ printf("launchable-activity:");
+ if (aName.length() > 0) {
+ printf(" name='%s' ",
+ ResTable::normalizeForOutput(aName.string()).string());
+ }
+ printf(" label='%s' icon='%s'\n",
+ ResTable::normalizeForOutput(activityLabel.string()).string(),
+ ResTable::normalizeForOutput(activityIcon.string()).string());
+ }
+ if (!hasIntentFilter) {
+ hasOtherActivities |= withinActivity;
+ hasOtherReceivers |= withinReceiver;
+ hasOtherServices |= withinService;
+ } else {
+ if (withinService) {
+ hasPaymentService |= (actHostApduService && hasMetaHostPaymentCategory &&
+ hasBindNfcServicePermission);
+ hasPaymentService |= (actOffHostApduService && hasMetaOffHostPaymentCategory &&
+ hasBindNfcServicePermission);
+ }
+ }
+ withinActivity = false;
+ withinService = false;
+ withinReceiver = false;
+ hasIntentFilter = false;
+ isMainActivity = isLauncherActivity = false;
+ } else if (depth < 4) {
+ if (withinIntentFilter) {
+ if (withinActivity) {
+ hasMainActivity |= actMainActivity;
+ hasOtherActivities |= !actMainActivity;
+ } else if (withinReceiver) {
+ hasWidgetReceivers |= actWidgetReceivers;
+ hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
+ hasBindDeviceAdminPermission);
+ hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
+ } else if (withinService) {
+ hasImeService |= actImeService;
+ hasWallpaperService |= actWallpaperService;
+ hasAccessibilityService |= (actAccessibilityService &&
+ hasBindAccessibilityServicePermission);
+ hasPrintService |= (actPrintService && hasBindPrintServicePermission);
+ hasOtherServices |= (!actImeService && !actWallpaperService &&
+ !actAccessibilityService && !actPrintService &&
+ !actHostApduService && !actOffHostApduService);
+ }
+ }
+ withinIntentFilter = false;
+ }
+ continue;
+ }
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ depth++;
+ String8 tag(tree.getElementName(&len));
+ //printf("Depth %d, %s\n", depth, tag.string());
+ if (depth == 1) {
+ if (tag != "manifest") {
+ fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
+ goto bail;
+ }
+ pkg = getAttribute(tree, NULL, "package", NULL);
+ printf("package: name='%s' ",
+ ResTable::normalizeForOutput(pkg.string()).string());
+ int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string());
+ goto bail;
+ }
+ if (versionCode > 0) {
+ printf("versionCode='%d' ", versionCode);
+ } else {
+ printf("versionCode='' ");
+ }
+ String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string());
+ goto bail;
+ }
+ printf("versionName='%s'\n",
+ ResTable::normalizeForOutput(versionName.string()).string());
+ } else if (depth == 2) {
+ withinApplication = false;
+ if (tag == "application") {
+ withinApplication = true;
+
+ String8 label;
+ const size_t NL = locales.size();
+ for (size_t i=0; i<NL; i++) {
+ const char* localeStr = locales[i].string();
+ assets.setLocale(localeStr != NULL ? localeStr : "");
+ String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
+ if (llabel != "") {
+ if (localeStr == NULL || strlen(localeStr) == 0) {
+ label = llabel;
+ printf("application-label:'%s'\n",
+ ResTable::normalizeForOutput(llabel.string()).string());
+ } else {
+ if (label == "") {
+ label = llabel;
+ }
+ printf("application-label-%s:'%s'\n", localeStr,
+ ResTable::normalizeForOutput(llabel.string()).string());
+ }
+ }
+ }
+
+ ResTable_config tmpConfig = config;
+ const size_t ND = densities.size();
+ for (size_t i=0; i<ND; i++) {
+ tmpConfig.density = densities[i];
+ assets.setConfiguration(tmpConfig);
+ String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
+ if (icon != "") {
+ printf("application-icon-%d:'%s'\n", densities[i],
+ ResTable::normalizeForOutput(icon.string()).string());
+ }
+ }
+ assets.setConfiguration(config);
+
+ String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string());
+ goto bail;
+ }
+ int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string());
+ goto bail;
+ }
+ printf("application: label='%s' ",
+ ResTable::normalizeForOutput(label.string()).string());
+ printf("icon='%s'\n", ResTable::normalizeForOutput(icon.string()).string());
+ if (testOnly != 0) {
+ printf("testOnly='%d'\n", testOnly);
+ }
+
+ int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string());
+ goto bail;
+ }
+ if (debuggable != 0) {
+ printf("application-debuggable\n");
+ }
+ } else if (tag == "uses-sdk") {
+ int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
+ if (error != "") {
+ error = "";
+ String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+ if (name == "Donut") targetSdk = 4;
+ printf("sdkVersion:'%s'\n",
+ ResTable::normalizeForOutput(name.string()).string());
+ } else if (code != -1) {
+ targetSdk = code;
+ printf("sdkVersion:'%d'\n", code);
+ }
+ code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1);
+ if (code != -1) {
+ printf("maxSdkVersion:'%d'\n", code);
+ }
+ code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error);
+ if (error != "") {
+ error = "";
+ String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+ if (name == "Donut" && targetSdk < 4) targetSdk = 4;
+ printf("targetSdkVersion:'%s'\n",
+ ResTable::normalizeForOutput(name.string()).string());
+ } else if (code != -1) {
+ if (targetSdk < code) {
+ targetSdk = code;
+ }
+ printf("targetSdkVersion:'%d'\n", code);
+ }
+ } else if (tag == "uses-configuration") {
+ int32_t reqTouchScreen = getIntegerAttribute(tree,
+ REQ_TOUCH_SCREEN_ATTR, NULL, 0);
+ int32_t reqKeyboardType = getIntegerAttribute(tree,
+ REQ_KEYBOARD_TYPE_ATTR, NULL, 0);
+ int32_t reqHardKeyboard = getIntegerAttribute(tree,
+ REQ_HARD_KEYBOARD_ATTR, NULL, 0);
+ int32_t reqNavigation = getIntegerAttribute(tree,
+ REQ_NAVIGATION_ATTR, NULL, 0);
+ int32_t reqFiveWayNav = getIntegerAttribute(tree,
+ REQ_FIVE_WAY_NAV_ATTR, NULL, 0);
+ printf("uses-configuration:");
+ if (reqTouchScreen != 0) {
+ printf(" reqTouchScreen='%d'", reqTouchScreen);
+ }
+ if (reqKeyboardType != 0) {
+ printf(" reqKeyboardType='%d'", reqKeyboardType);
+ }
+ if (reqHardKeyboard != 0) {
+ printf(" reqHardKeyboard='%d'", reqHardKeyboard);
+ }
+ if (reqNavigation != 0) {
+ printf(" reqNavigation='%d'", reqNavigation);
+ }
+ if (reqFiveWayNav != 0) {
+ printf(" reqFiveWayNav='%d'", reqFiveWayNav);
+ }
+ printf("\n");
+ } else if (tag == "supports-input") {
+ withinSupportsInput = true;
+ } else if (tag == "supports-screens") {
+ smallScreen = getIntegerAttribute(tree,
+ SMALL_SCREEN_ATTR, NULL, 1);
+ normalScreen = getIntegerAttribute(tree,
+ NORMAL_SCREEN_ATTR, NULL, 1);
+ largeScreen = getIntegerAttribute(tree,
+ LARGE_SCREEN_ATTR, NULL, 1);
+ xlargeScreen = getIntegerAttribute(tree,
+ XLARGE_SCREEN_ATTR, NULL, 1);
+ anyDensity = getIntegerAttribute(tree,
+ ANY_DENSITY_ATTR, NULL, 1);
+ requiresSmallestWidthDp = getIntegerAttribute(tree,
+ REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0);
+ compatibleWidthLimitDp = getIntegerAttribute(tree,
+ COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
+ largestWidthLimitDp = getIntegerAttribute(tree,
+ LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
+ } else if (tag == "uses-feature") {
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+
+ if (name != "" && error == "") {
+ int req = getIntegerAttribute(tree,
+ REQUIRED_ATTR, NULL, 1);
+
+ if (name == "android.hardware.camera") {
+ specCameraFeature = true;
+ } else if (name == "android.hardware.camera.autofocus") {
+ // these have no corresponding permission to check for,
+ // but should imply the foundational camera permission
+ reqCameraAutofocusFeature = reqCameraAutofocusFeature || req;
+ specCameraAutofocusFeature = true;
+ } else if (req && (name == "android.hardware.camera.flash")) {
+ // these have no corresponding permission to check for,
+ // but should imply the foundational camera permission
+ reqCameraFlashFeature = true;
+ } else if (name == "android.hardware.location") {
+ specLocationFeature = true;
+ } else if (name == "android.hardware.location.network") {
+ specNetworkLocFeature = true;
+ reqNetworkLocFeature = reqNetworkLocFeature || req;
+ } else if (name == "android.hardware.location.gps") {
+ specGpsFeature = true;
+ reqGpsFeature = reqGpsFeature || req;
+ } else if (name == "android.hardware.bluetooth") {
+ specBluetoothFeature = true;
+ } else if (name == "android.hardware.touchscreen") {
+ specTouchscreenFeature = true;
+ } else if (name == "android.hardware.touchscreen.multitouch") {
+ specMultitouchFeature = true;
+ } else if (name == "android.hardware.touchscreen.multitouch.distinct") {
+ reqDistinctMultitouchFeature = reqDistinctMultitouchFeature || req;
+ } else if (name == "android.hardware.microphone") {
+ specMicrophoneFeature = true;
+ } else if (name == "android.hardware.wifi") {
+ specWiFiFeature = true;
+ } else if (name == "android.hardware.telephony") {
+ specTelephonyFeature = true;
+ } else if (req && (name == "android.hardware.telephony.gsm" ||
+ name == "android.hardware.telephony.cdma")) {
+ // these have no corresponding permission to check for,
+ // but should imply the foundational telephony permission
+ reqTelephonySubFeature = true;
+ } else if (name == "android.hardware.screen.portrait") {
+ specScreenPortraitFeature = true;
+ } else if (name == "android.hardware.screen.landscape") {
+ specScreenLandscapeFeature = true;
+ }
+ printf("uses-feature%s:'%s'\n",
+ req ? "" : "-not-required",
+ ResTable::normalizeForOutput(name.string()).string());
+ } else {
+ int vers = getIntegerAttribute(tree,
+ GL_ES_VERSION_ATTR, &error);
+ if (error == "") {
+ printf("uses-gl-es:'0x%x'\n", vers);
+ }
+ }
+ } else if (tag == "uses-permission") {
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (name != "" && error == "") {
+ if (name == "android.permission.CAMERA") {
+ hasCameraPermission = true;
+ } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
+ hasGpsPermission = true;
+ } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
+ hasMockLocPermission = true;
+ } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
+ hasCoarseLocPermission = true;
+ } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
+ name == "android.permission.INSTALL_LOCATION_PROVIDER") {
+ hasGeneralLocPermission = true;
+ } else if (name == "android.permission.BLUETOOTH" ||
+ name == "android.permission.BLUETOOTH_ADMIN") {
+ hasBluetoothPermission = true;
+ } else if (name == "android.permission.RECORD_AUDIO") {
+ hasRecordAudioPermission = true;
+ } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
+ name == "android.permission.CHANGE_WIFI_STATE" ||
+ name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
+ hasWiFiPermission = true;
+ } else if (name == "android.permission.CALL_PHONE" ||
+ name == "android.permission.CALL_PRIVILEGED" ||
+ name == "android.permission.MODIFY_PHONE_STATE" ||
+ name == "android.permission.PROCESS_OUTGOING_CALLS" ||
+ name == "android.permission.READ_SMS" ||
+ name == "android.permission.RECEIVE_SMS" ||
+ name == "android.permission.RECEIVE_MMS" ||
+ name == "android.permission.RECEIVE_WAP_PUSH" ||
+ name == "android.permission.SEND_SMS" ||
+ name == "android.permission.WRITE_APN_SETTINGS" ||
+ name == "android.permission.WRITE_SMS") {
+ hasTelephonyPermission = true;
+ } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
+ hasWriteExternalStoragePermission = true;
+ } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
+ hasReadExternalStoragePermission = true;
+ } else if (name == "android.permission.READ_PHONE_STATE") {
+ hasReadPhoneStatePermission = true;
+ } else if (name == "android.permission.READ_CONTACTS") {
+ hasReadContactsPermission = true;
+ } else if (name == "android.permission.WRITE_CONTACTS") {
+ hasWriteContactsPermission = true;
+ } else if (name == "android.permission.READ_CALL_LOG") {
+ hasReadCallLogPermission = true;
+ } else if (name == "android.permission.WRITE_CALL_LOG") {
+ hasWriteCallLogPermission = true;
+ }
+
+ printUsesPermission(name,
+ getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0,
+ getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1));
+ } else {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+ } else if (tag == "uses-package") {
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (name != "" && error == "") {
+ printf("uses-package:'%s'\n",
+ ResTable::normalizeForOutput(name.string()).string());
+ } else {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+ } else if (tag == "original-package") {
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (name != "" && error == "") {
+ printf("original-package:'%s'\n",
+ ResTable::normalizeForOutput(name.string()).string());
+ } else {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+ } else if (tag == "supports-gl-texture") {
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (name != "" && error == "") {
+ printf("supports-gl-texture:'%s'\n",
+ ResTable::normalizeForOutput(name.string()).string());
+ } else {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+ } else if (tag == "compatible-screens") {
+ printCompatibleScreens(tree);
+ depth--;
+ } else if (tag == "package-verifier") {
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (name != "" && error == "") {
+ String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error);
+ if (publicKey != "" && error == "") {
+ printf("package-verifier: name='%s' publicKey='%s'\n",
+ ResTable::normalizeForOutput(name.string()).string(),
+ ResTable::normalizeForOutput(publicKey.string()).string());
+ }
+ }
+ }
+ } else if (depth == 3) {
+ withinActivity = false;
+ withinReceiver = false;
+ withinService = false;
+ hasIntentFilter = false;
+ hasMetaHostPaymentCategory = false;
+ hasMetaOffHostPaymentCategory = false;
+ hasBindDeviceAdminPermission = false;
+ hasBindInputMethodPermission = false;
+ hasBindAccessibilityServicePermission = false;
+ hasBindPrintServicePermission = false;
+ hasBindNfcServicePermission = false;
+ if (withinApplication) {
+ if(tag == "activity") {
+ withinActivity = true;
+ activityName = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+
+ activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+
+ activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+
+ int32_t orien = getResolvedIntegerAttribute(&res, tree,
+ SCREEN_ORIENTATION_ATTR, &error);
+ if (error == "") {
+ if (orien == 0 || orien == 6 || orien == 8) {
+ // Requests landscape, sensorLandscape, or reverseLandscape.
+ reqScreenLandscapeFeature = true;
+ } else if (orien == 1 || orien == 7 || orien == 9) {
+ // Requests portrait, sensorPortrait, or reversePortrait.
+ reqScreenPortraitFeature = true;
+ }
+ }
+ } else if (tag == "uses-library") {
+ String8 libraryName = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr,
+ "ERROR getting 'android:name' attribute for uses-library"
+ " %s\n", error.string());
+ goto bail;
+ }
+ int req = getIntegerAttribute(tree,
+ REQUIRED_ATTR, NULL, 1);
+ printf("uses-library%s:'%s'\n",
+ req ? "" : "-not-required", ResTable::normalizeForOutput(
+ libraryName.string()).string());
+ } else if (tag == "receiver") {
+ withinReceiver = true;
+ receiverName = getAttribute(tree, NAME_ATTR, &error);
+
+ if (error != "") {
+ fprintf(stderr,
+ "ERROR getting 'android:name' attribute for receiver:"
+ " %s\n", error.string());
+ goto bail;
+ }
+
+ String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
+ if (error == "") {
+ if (permission == "android.permission.BIND_DEVICE_ADMIN") {
+ hasBindDeviceAdminPermission = true;
+ }
+ } else {
+ fprintf(stderr, "ERROR getting 'android:permission' attribute for"
+ " receiver '%s': %s\n", receiverName.string(), error.string());
+ }
+ } else if (tag == "service") {
+ withinService = true;
+ serviceName = getAttribute(tree, NAME_ATTR, &error);
+
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:name' attribute for "
+ "service:%s\n", error.string());
+ goto bail;
+ }
+
+ String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
+ if (error == "") {
+ if (permission == "android.permission.BIND_INPUT_METHOD") {
+ hasBindInputMethodPermission = true;
+ } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
+ hasBindAccessibilityServicePermission = true;
+ } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
+ hasBindPrintServicePermission = true;
+ } else if (permission == "android.permission.BIND_NFC_SERVICE") {
+ hasBindNfcServicePermission = true;
+ }
+ } else {
+ fprintf(stderr, "ERROR getting 'android:permission' attribute for"
+ " service '%s': %s\n", serviceName.string(), error.string());
+ }
+ } else if (bundle->getIncludeMetaData() && tag == "meta-data") {
+ String8 metaDataName = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:name' attribute for "
+ "meta-data:%s\n", error.string());
+ goto bail;
+ }
+ printf("meta-data: name='%s' ",
+ ResTable::normalizeForOutput(metaDataName.string()).string());
+ printResolvedResourceAttribute(&res, tree, VALUE_ATTR, String8("value"),
+ &error);
+ if (error != "") {
+ // Try looking for a RESOURCE_ATTR
+ error = "";
+ printResolvedResourceAttribute(&res, tree, RESOURCE_ATTR,
+ String8("resource"), &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:value' or "
+ "'android:resource' attribute for "
+ "meta-data:%s\n", error.string());
+ goto bail;
+ }
+ }
+ printf("\n");
+ } else if (withinSupportsInput && tag == "input-type") {
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (name != "" && error == "") {
+ supportedInput.add(name);
+ } else {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+ }
+ }
+ } else if (depth == 4) {
+ if (tag == "intent-filter") {
+ hasIntentFilter = true;
+ withinIntentFilter = true;
+ actMainActivity = false;
+ actWidgetReceivers = false;
+ actImeService = false;
+ actWallpaperService = false;
+ actAccessibilityService = false;
+ actPrintService = false;
+ actDeviceAdminEnabled = false;
+ actHostApduService = false;
+ actOffHostApduService = false;
+ } else if (withinService && tag == "meta-data") {
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:name' attribute for"
+ " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
+ goto bail;
+ }
+
+ if (name == "android.nfc.cardemulation.host_apdu_service" ||
+ name == "android.nfc.cardemulation.off_host_apdu_service") {
+ bool offHost = true;
+ if (name == "android.nfc.cardemulation.host_apdu_service") {
+ offHost = false;
+ }
+
+ String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:resource' attribute for"
+ " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
+ goto bail;
+ }
+
+ Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
+ offHost, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting AID category for service '%s'\n",
+ serviceName.string());
+ goto bail;
+ }
+
+ const size_t catLen = categories.size();
+ for (size_t i = 0; i < catLen; i++) {
+ bool paymentCategory = (categories[i] == "payment");
+ if (offHost) {
+ hasMetaOffHostPaymentCategory |= paymentCategory;
+ } else {
+ hasMetaHostPaymentCategory |= paymentCategory;
+ }
+ }
+ }
+ }
+ } else if ((depth == 5) && withinIntentFilter) {
+ String8 action;
+ if (tag == "action") {
+ action = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+
+ if (withinActivity) {
+ if (action == "android.intent.action.MAIN") {
+ isMainActivity = true;
+ actMainActivity = true;
+ }
+ } else if (withinReceiver) {
+ if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
+ actWidgetReceivers = true;
+ } else if (action == "android.app.action.DEVICE_ADMIN_ENABLED") {
+ actDeviceAdminEnabled = true;
+ }
+ } else if (withinService) {
+ if (action == "android.view.InputMethod") {
+ actImeService = true;
+ } else if (action == "android.service.wallpaper.WallpaperService") {
+ actWallpaperService = true;
+ } else if (action == "android.accessibilityservice.AccessibilityService") {
+ actAccessibilityService = true;
+ } else if (action == "android.printservice.PrintService") {
+ actPrintService = true;
+ } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
+ actHostApduService = true;
+ } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
+ actOffHostApduService = true;
+ }
+ }
+ if (action == "android.intent.action.SEARCH") {
+ isSearchable = true;
+ }
+ }
+
+ if (tag == "category") {
+ String8 category = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'name' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+ if (withinActivity) {
+ if (category == "android.intent.category.LAUNCHER") {
+ isLauncherActivity = true;
+ }
+ }
+ }
+ }
+ }
+
+ // Pre-1.6 implicitly granted permission compatibility logic
+ if (targetSdk < 4) {
+ if (!hasWriteExternalStoragePermission) {
+ printUsesPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"));
+ printUsesImpliedPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"),
+ String8("targetSdkVersion < 4"));
+ hasWriteExternalStoragePermission = true;
+ }
+ if (!hasReadPhoneStatePermission) {
+ printUsesPermission(String8("android.permission.READ_PHONE_STATE"));
+ printUsesImpliedPermission(String8("android.permission.READ_PHONE_STATE"),
+ String8("targetSdkVersion < 4"));
+ }
+ }
+
+ // If the application has requested WRITE_EXTERNAL_STORAGE, we will
+ // force them to always take READ_EXTERNAL_STORAGE as well. We always
+ // do this (regardless of target API version) because we can't have
+ // an app with write permission but not read permission.
+ if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) {
+ printUsesPermission(String8("android.permission.READ_EXTERNAL_STORAGE"));
+ printUsesImpliedPermission(String8("android.permission.READ_EXTERNAL_STORAGE"),
+ String8("requested WRITE_EXTERNAL_STORAGE"));
+ }
+
+ // Pre-JellyBean call log permission compatibility.
+ if (targetSdk < 16) {
+ if (!hasReadCallLogPermission && hasReadContactsPermission) {
+ printUsesPermission(String8("android.permission.READ_CALL_LOG"));
+ printUsesImpliedPermission(String8("android.permission.READ_CALL_LOG"),
+ String8("targetSdkVersion < 16 and requested READ_CONTACTS"));
+ }
+ if (!hasWriteCallLogPermission && hasWriteContactsPermission) {
+ printUsesPermission(String8("android.permission.WRITE_CALL_LOG"));
+ printUsesImpliedPermission(String8("android.permission.WRITE_CALL_LOG"),
+ String8("targetSdkVersion < 16 and requested WRITE_CONTACTS"));
+ }
+ }
+
+ /* The following blocks handle printing "inferred" uses-features, based
+ * on whether related features or permissions are used by the app.
+ * Note that the various spec*Feature variables denote whether the
+ * relevant tag was *present* in the AndroidManfest, not that it was
+ * present and set to true.
+ */
+ // Camera-related back-compatibility logic
+ if (!specCameraFeature) {
+ if (reqCameraFlashFeature) {
+ // if app requested a sub-feature (autofocus or flash) and didn't
+ // request the base camera feature, we infer that it meant to
+ printf("uses-feature:'android.hardware.camera'\n");
+ printf("uses-implied-feature:'android.hardware.camera'," \
+ "'requested android.hardware.camera.flash feature'\n");
+ } else if (reqCameraAutofocusFeature) {
+ // if app requested a sub-feature (autofocus or flash) and didn't
+ // request the base camera feature, we infer that it meant to
+ printf("uses-feature:'android.hardware.camera'\n");
+ printf("uses-implied-feature:'android.hardware.camera'," \
+ "'requested android.hardware.camera.autofocus feature'\n");
+ } else if (hasCameraPermission) {
+ // if app wants to use camera but didn't request the feature, we infer
+ // that it meant to, and further that it wants autofocus
+ // (which was the 1.0 - 1.5 behavior)
+ printf("uses-feature:'android.hardware.camera'\n");
+ if (!specCameraAutofocusFeature) {
+ printf("uses-feature:'android.hardware.camera.autofocus'\n");
+ printf("uses-implied-feature:'android.hardware.camera.autofocus'," \
+ "'requested android.permission.CAMERA permission'\n");
+ }
+ }
+ }
+
+ // Location-related back-compatibility logic
+ if (!specLocationFeature &&
+ (hasMockLocPermission || hasCoarseLocPermission || hasGpsPermission ||
+ hasGeneralLocPermission || reqNetworkLocFeature || reqGpsFeature)) {
+ // if app either takes a location-related permission or requests one of the
+ // sub-features, we infer that it also meant to request the base location feature
+ printf("uses-feature:'android.hardware.location'\n");
+ printf("uses-implied-feature:'android.hardware.location'," \
+ "'requested a location access permission'\n");
+ }
+ if (!specGpsFeature && hasGpsPermission) {
+ // if app takes GPS (FINE location) perm but does not request the GPS
+ // feature, we infer that it meant to
+ printf("uses-feature:'android.hardware.location.gps'\n");
+ printf("uses-implied-feature:'android.hardware.location.gps'," \
+ "'requested android.permission.ACCESS_FINE_LOCATION permission'\n");
+ }
+ if (!specNetworkLocFeature && hasCoarseLocPermission) {
+ // if app takes Network location (COARSE location) perm but does not request the
+ // network location feature, we infer that it meant to
+ printf("uses-feature:'android.hardware.location.network'\n");
+ printf("uses-implied-feature:'android.hardware.location.network'," \
+ "'requested android.permission.ACCESS_COARSE_LOCATION permission'\n");
+ }
+
+ // Bluetooth-related compatibility logic
+ if (!specBluetoothFeature && hasBluetoothPermission && (targetSdk > 4)) {
+ // if app takes a Bluetooth permission but does not request the Bluetooth
+ // feature, we infer that it meant to
+ printf("uses-feature:'android.hardware.bluetooth'\n");
+ printf("uses-implied-feature:'android.hardware.bluetooth'," \
+ "'requested android.permission.BLUETOOTH or android.permission.BLUETOOTH_ADMIN " \
+ "permission and targetSdkVersion > 4'\n");
+ }
+
+ // Microphone-related compatibility logic
+ if (!specMicrophoneFeature && hasRecordAudioPermission) {
+ // if app takes the record-audio permission but does not request the microphone
+ // feature, we infer that it meant to
+ printf("uses-feature:'android.hardware.microphone'\n");
+ printf("uses-implied-feature:'android.hardware.microphone'," \
+ "'requested android.permission.RECORD_AUDIO permission'\n");
+ }
+
+ // WiFi-related compatibility logic
+ if (!specWiFiFeature && hasWiFiPermission) {
+ // if app takes one of the WiFi permissions but does not request the WiFi
+ // feature, we infer that it meant to
+ printf("uses-feature:'android.hardware.wifi'\n");
+ printf("uses-implied-feature:'android.hardware.wifi'," \
+ "'requested android.permission.ACCESS_WIFI_STATE, " \
+ "android.permission.CHANGE_WIFI_STATE, or " \
+ "android.permission.CHANGE_WIFI_MULTICAST_STATE permission'\n");
+ }
+
+ // Telephony-related compatibility logic
+ if (!specTelephonyFeature && (hasTelephonyPermission || reqTelephonySubFeature)) {
+ // if app takes one of the telephony permissions or requests a sub-feature but
+ // does not request the base telephony feature, we infer that it meant to
+ printf("uses-feature:'android.hardware.telephony'\n");
+ printf("uses-implied-feature:'android.hardware.telephony'," \
+ "'requested a telephony-related permission or feature'\n");
+ }
+
+ // Touchscreen-related back-compatibility logic
+ if (!specTouchscreenFeature) { // not a typo!
+ // all apps are presumed to require a touchscreen, unless they explicitly say
+ // <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
+ // Note that specTouchscreenFeature is true if the tag is present, regardless
+ // of whether its value is true or false, so this is safe
+ printf("uses-feature:'android.hardware.touchscreen'\n");
+ printf("uses-implied-feature:'android.hardware.touchscreen'," \
+ "'assumed you require a touch screen unless explicitly made optional'\n");
+ }
+ if (!specMultitouchFeature && reqDistinctMultitouchFeature) {
+ // if app takes one of the telephony permissions or requests a sub-feature but
+ // does not request the base telephony feature, we infer that it meant to
+ printf("uses-feature:'android.hardware.touchscreen.multitouch'\n");
+ printf("uses-implied-feature:'android.hardware.touchscreen.multitouch'," \
+ "'requested android.hardware.touchscreen.multitouch.distinct feature'\n");
+ }
+
+ // Landscape/portrait-related compatibility logic
+ if (!specScreenLandscapeFeature && !specScreenPortraitFeature) {
+ // If the app has specified any activities in its manifest
+ // that request a specific orientation, then assume that
+ // orientation is required.
+ if (reqScreenLandscapeFeature) {
+ printf("uses-feature:'android.hardware.screen.landscape'\n");
+ printf("uses-implied-feature:'android.hardware.screen.landscape'," \
+ "'one or more activities have specified a landscape orientation'\n");
+ }
+ if (reqScreenPortraitFeature) {
+ printf("uses-feature:'android.hardware.screen.portrait'\n");
+ printf("uses-implied-feature:'android.hardware.screen.portrait'," \
+ "'one or more activities have specified a portrait orientation'\n");
+ }
+ }
+
+ if (hasMainActivity) {
+ printf("main\n");
+ }
+ if (hasWidgetReceivers) {
+ printf("app-widget\n");
+ }
+ if (hasDeviceAdminReceiver) {
+ printf("device-admin\n");
+ }
+ if (hasImeService) {
+ printf("ime\n");
+ }
+ if (hasWallpaperService) {
+ printf("wallpaper\n");
+ }
+ if (hasAccessibilityService) {
+ printf("accessibility\n");
+ }
+ if (hasPrintService) {
+ printf("print\n");
+ }
+ if (hasPaymentService) {
+ printf("payment\n");
+ }
+ if (hasOtherActivities) {
+ printf("other-activities\n");
+ }
+ if (isSearchable) {
+ printf("search\n");
+ }
+ if (hasOtherReceivers) {
+ printf("other-receivers\n");
+ }
+ if (hasOtherServices) {
+ printf("other-services\n");
+ }
+
+ // For modern apps, if screen size buckets haven't been specified
+ // but the new width ranges have, then infer the buckets from them.
+ if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0
+ && requiresSmallestWidthDp > 0) {
+ int compatWidth = compatibleWidthLimitDp;
+ if (compatWidth <= 0) {
+ compatWidth = requiresSmallestWidthDp;
+ }
+ if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) {
+ smallScreen = -1;
+ } else {
+ smallScreen = 0;
+ }
+ if (requiresSmallestWidthDp <= 320 && compatWidth >= 320) {
+ normalScreen = -1;
+ } else {
+ normalScreen = 0;
+ }
+ if (requiresSmallestWidthDp <= 480 && compatWidth >= 480) {
+ largeScreen = -1;
+ } else {
+ largeScreen = 0;
+ }
+ if (requiresSmallestWidthDp <= 720 && compatWidth >= 720) {
+ xlargeScreen = -1;
+ } else {
+ xlargeScreen = 0;
+ }
+ }
+
+ // Determine default values for any unspecified screen sizes,
+ // based on the target SDK of the package. As of 4 (donut)
+ // the screen size support was introduced, so all default to
+ // enabled.
+ if (smallScreen > 0) {
+ smallScreen = targetSdk >= 4 ? -1 : 0;
+ }
+ if (normalScreen > 0) {
+ normalScreen = -1;
+ }
+ if (largeScreen > 0) {
+ largeScreen = targetSdk >= 4 ? -1 : 0;
+ }
+ if (xlargeScreen > 0) {
+ // Introduced in Gingerbread.
+ xlargeScreen = targetSdk >= 9 ? -1 : 0;
+ }
+ if (anyDensity > 0) {
+ anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0
+ || compatibleWidthLimitDp > 0) ? -1 : 0;
+ }
+ printf("supports-screens:");
+ if (smallScreen != 0) {
+ printf(" 'small'");
+ }
+ if (normalScreen != 0) {
+ printf(" 'normal'");
+ }
+ if (largeScreen != 0) {
+ printf(" 'large'");
+ }
+ if (xlargeScreen != 0) {
+ printf(" 'xlarge'");
+ }
+ printf("\n");
+ printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false");
+ if (requiresSmallestWidthDp > 0) {
+ printf("requires-smallest-width:'%d'\n", requiresSmallestWidthDp);
+ }
+ if (compatibleWidthLimitDp > 0) {
+ printf("compatible-width-limit:'%d'\n", compatibleWidthLimitDp);
+ }
+ if (largestWidthLimitDp > 0) {
+ printf("largest-width-limit:'%d'\n", largestWidthLimitDp);
+ }
+
+ printf("locales:");
+ const size_t NL = locales.size();
+ for (size_t i=0; i<NL; i++) {
+ const char* localeStr = locales[i].string();
+ if (localeStr == NULL || strlen(localeStr) == 0) {
+ localeStr = "--_--";
+ }
+ printf(" '%s'", localeStr);
+ }
+ printf("\n");
+
+ printf("densities:");
+ const size_t ND = densities.size();
+ for (size_t i=0; i<ND; i++) {
+ printf(" '%d'", densities[i]);
+ }
+ printf("\n");
+
+ AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
+ if (dir != NULL) {
+ if (dir->getFileCount() > 0) {
+ printf("native-code:");
+ for (size_t i=0; i<dir->getFileCount(); i++) {
+ printf(" '%s'", ResTable::normalizeForOutput(
+ dir->getFileName(i).string()).string());
+ }
+ printf("\n");
+ }
+ delete dir;
+ }
+ } else if (strcmp("badger", option) == 0) {
+ printf("%s", CONSOLE_DATA);
+ } else if (strcmp("configurations", option) == 0) {
+ Vector<ResTable_config> configs;
+ res.getConfigurations(&configs);
+ const size_t N = configs.size();
+ for (size_t i=0; i<N; i++) {
+ printf("%s\n", configs[i].toString().string());
+ }
+ } else {
+ fprintf(stderr, "ERROR: unknown dump option '%s'\n", option);
+ goto bail;
+ }
+ }
+
+ result = NO_ERROR;
+
+bail:
+ if (asset) {
+ delete asset;
+ }
+ return (result != NO_ERROR);
+}
+
+
+/*
+ * Handle the "add" command, which wants to add files to a new or
+ * pre-existing archive.
+ */
+int doAdd(Bundle* bundle)
+{
+ ZipFile* zip = NULL;
+ status_t result = UNKNOWN_ERROR;
+ const char* zipFileName;
+
+ if (bundle->getUpdate()) {
+ /* avoid confusion */
+ fprintf(stderr, "ERROR: can't use '-u' with add\n");
+ goto bail;
+ }
+
+ if (bundle->getFileSpecCount() < 1) {
+ fprintf(stderr, "ERROR: must specify zip file name\n");
+ goto bail;
+ }
+ zipFileName = bundle->getFileSpecEntry(0);
+
+ if (bundle->getFileSpecCount() < 2) {
+ fprintf(stderr, "NOTE: nothing to do\n");
+ goto bail;
+ }
+
+ zip = openReadWrite(zipFileName, true);
+ if (zip == NULL) {
+ fprintf(stderr, "ERROR: failed opening/creating '%s' as Zip file\n", zipFileName);
+ goto bail;
+ }
+
+ for (int i = 1; i < bundle->getFileSpecCount(); i++) {
+ const char* fileName = bundle->getFileSpecEntry(i);
+
+ if (strcasecmp(String8(fileName).getPathExtension().string(), ".gz") == 0) {
+ printf(" '%s'... (from gzip)\n", fileName);
+ result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL);
+ } else {
+ if (bundle->getJunkPath()) {
+ String8 storageName = String8(fileName).getPathLeaf();
+ printf(" '%s' as '%s'...\n", fileName,
+ ResTable::normalizeForOutput(storageName.string()).string());
+ result = zip->add(fileName, storageName.string(),
+ bundle->getCompressionMethod(), NULL);
+ } else {
+ printf(" '%s'...\n", fileName);
+ result = zip->add(fileName, bundle->getCompressionMethod(), NULL);
+ }
+ }
+ if (result != NO_ERROR) {
+ fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName);
+ if (result == NAME_NOT_FOUND) {
+ fprintf(stderr, ": file not found\n");
+ } else if (result == ALREADY_EXISTS) {
+ fprintf(stderr, ": already exists in archive\n");
+ } else {
+ fprintf(stderr, "\n");
+ }
+ goto bail;
+ }
+ }
+
+ result = NO_ERROR;
+
+bail:
+ delete zip;
+ return (result != NO_ERROR);
+}
+
+
+/*
+ * Delete files from an existing archive.
+ */
+int doRemove(Bundle* bundle)
+{
+ ZipFile* zip = NULL;
+ status_t result = UNKNOWN_ERROR;
+ const char* zipFileName;
+
+ if (bundle->getFileSpecCount() < 1) {
+ fprintf(stderr, "ERROR: must specify zip file name\n");
+ goto bail;
+ }
+ zipFileName = bundle->getFileSpecEntry(0);
+
+ if (bundle->getFileSpecCount() < 2) {
+ fprintf(stderr, "NOTE: nothing to do\n");
+ goto bail;
+ }
+
+ zip = openReadWrite(zipFileName, false);
+ if (zip == NULL) {
+ fprintf(stderr, "ERROR: failed opening Zip archive '%s'\n",
+ zipFileName);
+ goto bail;
+ }
+
+ for (int i = 1; i < bundle->getFileSpecCount(); i++) {
+ const char* fileName = bundle->getFileSpecEntry(i);
+ ZipEntry* entry;
+
+ entry = zip->getEntryByName(fileName);
+ if (entry == NULL) {
+ printf(" '%s' NOT FOUND\n", fileName);
+ continue;
+ }
+
+ result = zip->remove(entry);
+
+ if (result != NO_ERROR) {
+ fprintf(stderr, "Unable to delete '%s' from '%s'\n",
+ bundle->getFileSpecEntry(i), zipFileName);
+ goto bail;
+ }
+ }
+
+ /* update the archive */
+ zip->flush();
+
+bail:
+ delete zip;
+ return (result != NO_ERROR);
+}
+
+
+/*
+ * Package up an asset directory and associated application files.
+ */
+int doPackage(Bundle* bundle)
+{
+ const char* outputAPKFile;
+ int retVal = 1;
+ status_t err;
+ sp<AaptAssets> assets;
+ int N;
+ FILE* fp;
+ String8 dependencyFile;
+
+ // -c zz_ZZ means do pseudolocalization
+ ResourceFilter filter;
+ err = filter.parse(bundle->getConfigurations());
+ if (err != NO_ERROR) {
+ goto bail;
+ }
+ if (filter.containsPseudo()) {
+ bundle->setPseudolocalize(true);
+ }
+
+ N = bundle->getFileSpecCount();
+ if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
+ && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDir() == NULL) {
+ fprintf(stderr, "ERROR: no input files\n");
+ goto bail;
+ }
+
+ outputAPKFile = bundle->getOutputAPKFile();
+
+ // Make sure the filenames provided exist and are of the appropriate type.
+ if (outputAPKFile) {
+ FileType type;
+ type = getFileType(outputAPKFile);
+ if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
+ fprintf(stderr,
+ "ERROR: output file '%s' exists but is not regular file\n",
+ outputAPKFile);
+ goto bail;
+ }
+ }
+
+ // Load the assets.
+ assets = new AaptAssets();
+
+ // Set up the resource gathering in assets if we're going to generate
+ // dependency files. Every time we encounter a resource while slurping
+ // the tree, we'll add it to these stores so we have full resource paths
+ // to write to a dependency file.
+ if (bundle->getGenDependencies()) {
+ sp<FilePathStore> resPathStore = new FilePathStore;
+ assets->setFullResPaths(resPathStore);
+ sp<FilePathStore> assetPathStore = new FilePathStore;
+ assets->setFullAssetPaths(assetPathStore);
+ }
+
+ err = assets->slurpFromArgs(bundle);
+ if (err < 0) {
+ goto bail;
+ }
+
+ if (bundle->getVerbose()) {
+ assets->print(String8());
+ }
+
+ // If they asked for any fileAs that need to be compiled, do so.
+ if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
+ err = buildResources(bundle, assets);
+ if (err != 0) {
+ goto bail;
+ }
+ }
+
+ // At this point we've read everything and processed everything. From here
+ // on out it's just writing output files.
+ if (SourcePos::hasErrors()) {
+ goto bail;
+ }
+
+ // Update symbols with information about which ones are needed as Java symbols.
+ assets->applyJavaSymbols();
+ if (SourcePos::hasErrors()) {
+ goto bail;
+ }
+
+ // If we've been asked to generate a dependency file, do that here
+ if (bundle->getGenDependencies()) {
+ // If this is the packaging step, generate the dependency file next to
+ // the output apk (e.g. bin/resources.ap_.d)
+ if (outputAPKFile) {
+ dependencyFile = String8(outputAPKFile);
+ // Add the .d extension to the dependency file.
+ dependencyFile.append(".d");
+ } else {
+ // Else if this is the R.java dependency generation step,
+ // generate the dependency file in the R.java package subdirectory
+ // e.g. gen/com/foo/app/R.java.d
+ dependencyFile = String8(bundle->getRClassDir());
+ dependencyFile.appendPath("R.java.d");
+ }
+ // Make sure we have a clean dependency file to start with
+ fp = fopen(dependencyFile, "w");
+ fclose(fp);
+ }
+
+ // Write out R.java constants
+ if (!assets->havePrivateSymbols()) {
+ if (bundle->getCustomPackage() == NULL) {
+ // Write the R.java file into the appropriate class directory
+ // e.g. gen/com/foo/app/R.java
+ err = writeResourceSymbols(bundle, assets, assets->getPackage(), true);
+ } else {
+ const String8 customPkg(bundle->getCustomPackage());
+ err = writeResourceSymbols(bundle, assets, customPkg, true);
+ }
+ if (err < 0) {
+ goto bail;
+ }
+ // If we have library files, we're going to write our R.java file into
+ // the appropriate class directory for those libraries as well.
+ // e.g. gen/com/foo/app/lib/R.java
+ if (bundle->getExtraPackages() != NULL) {
+ // Split on colon
+ String8 libs(bundle->getExtraPackages());
+ char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
+ while (packageString != NULL) {
+ // Write the R.java file out with the correct package name
+ err = writeResourceSymbols(bundle, assets, String8(packageString), true);
+ if (err < 0) {
+ goto bail;
+ }
+ packageString = strtok(NULL, ":");
+ }
+ libs.unlockBuffer();
+ }
+ } else {
+ err = writeResourceSymbols(bundle, assets, assets->getPackage(), false);
+ if (err < 0) {
+ goto bail;
+ }
+ err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true);
+ if (err < 0) {
+ goto bail;
+ }
+ }
+
+ // Write out the ProGuard file
+ err = writeProguardFile(bundle, assets);
+ if (err < 0) {
+ goto bail;
+ }
+
+ // Write the apk
+ if (outputAPKFile) {
+ err = writeAPK(bundle, assets, String8(outputAPKFile));
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile);
+ goto bail;
+ }
+ }
+
+ // If we've been asked to generate a dependency file, we need to finish up here.
+ // the writeResourceSymbols and writeAPK functions have already written the target
+ // half of the dependency file, now we need to write the prerequisites. (files that
+ // the R.java file or .ap_ file depend on)
+ if (bundle->getGenDependencies()) {
+ // Now that writeResourceSymbols or writeAPK has taken care of writing
+ // the targets to our dependency file, we'll write the prereqs
+ fp = fopen(dependencyFile, "a+");
+ fprintf(fp, " : ");
+ bool includeRaw = (outputAPKFile != NULL);
+ err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
+ // Also manually add the AndroidManifeset since it's not under res/ or assets/
+ // and therefore was not added to our pathstores during slurping
+ fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
+ fclose(fp);
+ }
+
+ retVal = 0;
+bail:
+ if (SourcePos::hasErrors()) {
+ SourcePos::printErrors(stderr);
+ }
+ return retVal;
+}
+
+/*
+ * Do PNG Crunching
+ * PRECONDITIONS
+ * -S flag points to a source directory containing drawable* folders
+ * -C flag points to destination directory. The folder structure in the
+ * source directory will be mirrored to the destination (cache) directory
+ *
+ * POSTCONDITIONS
+ * Destination directory will be updated to match the PNG files in
+ * the source directory.
+ */
+int doCrunch(Bundle* bundle)
+{
+ fprintf(stdout, "Crunching PNG Files in ");
+ fprintf(stdout, "source dir: %s\n", bundle->getResourceSourceDirs()[0]);
+ fprintf(stdout, "To destination dir: %s\n", bundle->getCrunchedOutputDir());
+
+ updatePreProcessedCache(bundle);
+
+ return NO_ERROR;
+}
+
+/*
+ * Do PNG Crunching on a single flag
+ * -i points to a single png file
+ * -o points to a single png output file
+ */
+int doSingleCrunch(Bundle* bundle)
+{
+ fprintf(stdout, "Crunching single PNG file: %s\n", bundle->getSingleCrunchInputFile());
+ fprintf(stdout, "\tOutput file: %s\n", bundle->getSingleCrunchOutputFile());
+
+ String8 input(bundle->getSingleCrunchInputFile());
+ String8 output(bundle->getSingleCrunchOutputFile());
+
+ if (preProcessImageToCache(bundle, input, output) != NO_ERROR) {
+ // we can't return the status_t as it gets truncate to the lower 8 bits.
+ return 42;
+ }
+
+ return NO_ERROR;
+}
+
+char CONSOLE_DATA[2925] = {
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 63,
+ 86, 35, 40, 46, 46, 95, 95, 95, 95, 97, 97, 44, 32, 46, 124, 42, 33, 83,
+ 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 46, 58, 59, 61, 59, 61, 81,
+ 81, 81, 81, 66, 96, 61, 61, 58, 46, 46, 46, 58, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 46, 61, 59, 59, 59, 58, 106, 81, 81, 81, 81, 102, 59, 61, 59,
+ 59, 61, 61, 61, 58, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59,
+ 59, 58, 109, 81, 81, 81, 81, 61, 59, 59, 59, 59, 59, 58, 59, 59, 46, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 60, 81, 81, 81, 81, 87,
+ 58, 59, 59, 59, 59, 59, 59, 61, 119, 44, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
+ 47, 61, 59, 59, 58, 100, 81, 81, 81, 81, 35, 58, 59, 59, 59, 59, 59, 58,
+ 121, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 109, 58, 59, 59, 61, 81, 81,
+ 81, 81, 81, 109, 58, 59, 59, 59, 59, 61, 109, 81, 81, 76, 46, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 41, 87, 59, 61, 59, 41, 81, 81, 81, 81, 81, 81, 59, 61, 59,
+ 59, 58, 109, 81, 81, 87, 39, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 60, 81, 91, 59,
+ 59, 61, 81, 81, 81, 81, 81, 87, 43, 59, 58, 59, 60, 81, 81, 81, 76, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 52, 91, 58, 45, 59, 87, 81, 81, 81, 81,
+ 70, 58, 58, 58, 59, 106, 81, 81, 81, 91, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 93, 40, 32, 46, 59, 100, 81, 81, 81, 81, 40, 58, 46, 46, 58, 100, 81,
+ 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 46, 46, 46, 32, 46, 46, 46, 32, 46, 32, 46, 45, 91, 59, 61, 58, 109,
+ 81, 81, 81, 87, 46, 58, 61, 59, 60, 81, 81, 80, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
+ 32, 32, 32, 32, 32, 32, 32, 46, 46, 61, 59, 61, 61, 61, 59, 61, 61, 59,
+ 59, 59, 58, 58, 46, 46, 41, 58, 59, 58, 81, 81, 81, 81, 69, 58, 59, 59,
+ 60, 81, 81, 68, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 58, 59,
+ 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 46,
+ 61, 59, 93, 81, 81, 81, 81, 107, 58, 59, 58, 109, 87, 68, 96, 32, 32, 32,
+ 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 10, 32, 32, 32, 46, 60, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59,
+ 59, 59, 59, 59, 59, 59, 59, 59, 59, 58, 58, 58, 115, 109, 68, 41, 36, 81,
+ 109, 46, 61, 61, 81, 69, 96, 46, 58, 58, 46, 58, 46, 46, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 46, 32, 95, 81,
+ 67, 61, 61, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
+ 59, 59, 59, 59, 58, 68, 39, 61, 105, 61, 63, 81, 119, 58, 106, 80, 32, 58,
+ 61, 59, 59, 61, 59, 61, 59, 61, 46, 95, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 10, 32, 32, 36, 81, 109, 105, 59, 61, 59, 59, 59,
+ 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 46, 58, 37,
+ 73, 108, 108, 62, 52, 81, 109, 34, 32, 61, 59, 59, 59, 59, 59, 59, 59, 59,
+ 59, 61, 59, 61, 61, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
+ 32, 46, 45, 57, 101, 43, 43, 61, 61, 59, 59, 59, 59, 59, 59, 61, 59, 59,
+ 59, 59, 59, 59, 59, 59, 59, 58, 97, 46, 61, 108, 62, 126, 58, 106, 80, 96,
+ 46, 61, 61, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61,
+ 97, 103, 97, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 45, 46, 32,
+ 46, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 58, 59, 59, 59, 59, 61,
+ 119, 81, 97, 124, 105, 124, 124, 39, 126, 95, 119, 58, 61, 58, 59, 59, 59,
+ 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 119, 81, 81, 99, 32, 32,
+ 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 58, 106, 81, 81, 81, 109, 119,
+ 119, 119, 109, 109, 81, 81, 122, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59,
+ 59, 59, 59, 59, 59, 58, 115, 81, 87, 81, 102, 32, 32, 32, 32, 32, 32, 10,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 61, 58, 59, 61, 81, 81, 81, 81, 81, 81, 87, 87, 81, 81, 81, 81,
+ 81, 58, 59, 59, 59, 59, 59, 59, 59, 59, 58, 45, 45, 45, 59, 59, 59, 41,
+ 87, 66, 33, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59, 93, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58,
+ 45, 32, 46, 32, 32, 32, 32, 32, 46, 32, 126, 96, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 58, 61, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 40, 58, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58,
+ 59, 59, 58, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 40, 58,
+ 59, 59, 59, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59, 60, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 59, 61, 59, 59, 61, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 58, 59, 59, 93, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 40, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 106,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 76, 58, 59, 59, 59,
+ 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 61, 58, 58, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 87, 58, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 58, 59, 61, 41, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 87, 59,
+ 61, 58, 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 58, 61, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 107, 58, 59, 59, 59, 59, 58, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 58, 59, 59, 58, 51, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 102, 94, 59, 59, 59, 59, 59, 61, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 58, 61, 59,
+ 59, 59, 43, 63, 36, 81, 81, 81, 87, 64, 86, 102, 58, 59, 59, 59, 59, 59,
+ 59, 59, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 59, 59, 43, 33,
+ 58, 126, 126, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46,
+ 61, 59, 59, 59, 58, 45, 58, 61, 59, 58, 58, 58, 61, 59, 59, 59, 59, 59,
+ 59, 59, 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 61, 59, 59, 59, 59, 59, 58, 95,
+ 32, 45, 61, 59, 61, 59, 59, 59, 59, 59, 59, 59, 45, 58, 59, 59, 59, 59,
+ 61, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 58, 61, 59, 59, 59, 59, 59, 61, 59, 61, 46, 46, 32, 45, 45, 45,
+ 59, 58, 45, 45, 46, 58, 59, 59, 59, 59, 59, 59, 61, 46, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 58, 59, 59, 59, 59,
+ 59, 59, 59, 59, 59, 61, 59, 46, 32, 32, 46, 32, 46, 32, 58, 61, 59, 59,
+ 59, 59, 59, 59, 59, 59, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 45, 59, 59, 59, 59, 59, 59, 59, 59, 58, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 58, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 46, 61, 59, 59, 59, 59, 59, 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 61,
+ 46, 61, 59, 59, 59, 59, 59, 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59,
+ 59, 59, 32, 46, 32, 32, 32, 32, 32, 32, 32, 46, 61, 58, 59, 59, 59, 59,
+ 59, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 58, 59, 59, 59, 59, 59, 59, 59, 59, 46, 46, 32, 32, 32,
+ 32, 32, 32, 32, 61, 59, 59, 59, 59, 59, 59, 59, 45, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 45, 61,
+ 59, 59, 59, 59, 59, 58, 32, 46, 32, 32, 32, 32, 32, 32, 32, 58, 59, 59,
+ 59, 59, 59, 58, 45, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 45, 32, 46, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 61, 59, 58, 45, 45, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10
+ };
diff --git a/tools/aapt/CrunchCache.cpp b/tools/aapt/CrunchCache.cpp
new file mode 100644
index 0000000..c4cf6bc
--- /dev/null
+++ b/tools/aapt/CrunchCache.cpp
@@ -0,0 +1,104 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Implementation file for CrunchCache
+// This file defines functions laid out and documented in
+// CrunchCache.h
+
+#include <utils/Vector.h>
+#include <utils/String8.h>
+
+#include "DirectoryWalker.h"
+#include "FileFinder.h"
+#include "CacheUpdater.h"
+#include "CrunchCache.h"
+
+using namespace android;
+
+CrunchCache::CrunchCache(String8 sourcePath, String8 destPath, FileFinder* ff)
+ : mSourcePath(sourcePath), mDestPath(destPath), mSourceFiles(0), mDestFiles(0), mFileFinder(ff)
+{
+ // We initialize the default value to return to 0 so if a file doesn't exist
+ // then all files are automatically "newer" than it.
+
+ // Set file extensions to look for. Right now just pngs.
+ mExtensions.push(String8(".png"));
+
+ // Load files into our data members
+ loadFiles();
+}
+
+size_t CrunchCache::crunch(CacheUpdater* cu, bool forceOverwrite)
+{
+ size_t numFilesUpdated = 0;
+
+ // Iterate through the source files and compare to cache.
+ // After processing a file, remove it from the source files and
+ // from the dest files.
+ // We're done when we're out of files in source.
+ String8 relativePath;
+ while (mSourceFiles.size() > 0) {
+ // Get the full path to the source file, then convert to a c-string
+ // and offset our beginning pointer to the length of the sourcePath
+ // This efficiently strips the source directory prefix from our path.
+ // Also, String8 doesn't have a substring method so this is what we've
+ // got to work with.
+ const char* rPathPtr = mSourceFiles.keyAt(0).string()+mSourcePath.length();
+ // Strip leading slash if present
+ int offset = 0;
+ if (rPathPtr[0] == OS_PATH_SEPARATOR)
+ offset = 1;
+ relativePath = String8(rPathPtr + offset);
+
+ if (forceOverwrite || needsUpdating(relativePath)) {
+ cu->processImage(mSourcePath.appendPathCopy(relativePath),
+ mDestPath.appendPathCopy(relativePath));
+ numFilesUpdated++;
+ // crunchFile(relativePath);
+ }
+ // Delete this file from the source files and (if it exists) from the
+ // dest files.
+ mSourceFiles.removeItemsAt(0);
+ mDestFiles.removeItem(mDestPath.appendPathCopy(relativePath));
+ }
+
+ // Iterate through what's left of destFiles and delete leftovers
+ while (mDestFiles.size() > 0) {
+ cu->deleteFile(mDestFiles.keyAt(0));
+ mDestFiles.removeItemsAt(0);
+ }
+
+ // Update our knowledge of the files cache
+ // both source and dest should be empty by now.
+ loadFiles();
+
+ return numFilesUpdated;
+}
+
+void CrunchCache::loadFiles()
+{
+ // Clear out our data structures to avoid putting in duplicates
+ mSourceFiles.clear();
+ mDestFiles.clear();
+
+ // Make a directory walker that points to the system.
+ DirectoryWalker* dw = new SystemDirectoryWalker();
+
+ // Load files in the source directory
+ mFileFinder->findFiles(mSourcePath, mExtensions, mSourceFiles,dw);
+
+ // Load files in the destination directory
+ mFileFinder->findFiles(mDestPath,mExtensions,mDestFiles,dw);
+
+ delete dw;
+}
+
+bool CrunchCache::needsUpdating(String8 relativePath) const
+{
+ // Retrieve modification dates for this file entry under the source and
+ // cache directory trees. The vectors will return a modification date of 0
+ // if the file doesn't exist.
+ time_t sourceDate = mSourceFiles.valueFor(mSourcePath.appendPathCopy(relativePath));
+ time_t destDate = mDestFiles.valueFor(mDestPath.appendPathCopy(relativePath));
+ return sourceDate > destDate;
+}
\ No newline at end of file
diff --git a/tools/aapt/CrunchCache.h b/tools/aapt/CrunchCache.h
new file mode 100644
index 0000000..be3da5c
--- /dev/null
+++ b/tools/aapt/CrunchCache.h
@@ -0,0 +1,102 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Cache manager for pre-processed PNG files.
+// Contains code for managing which PNG files get processed
+// at build time.
+//
+
+#ifndef CRUNCHCACHE_H
+#define CRUNCHCACHE_H
+
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include "FileFinder.h"
+#include "CacheUpdater.h"
+
+using namespace android;
+
+/** CrunchCache
+ * This class is a cache manager which can pre-process PNG files and store
+ * them in a mirror-cache. It's capable of doing incremental updates to its
+ * cache.
+ *
+ * Usage:
+ * Create an instance initialized with the root of the source tree, the
+ * root location to store the cache files, and an instance of a file finder.
+ * Then update the cache by calling crunch.
+ */
+class CrunchCache {
+public:
+ // Constructor
+ CrunchCache(String8 sourcePath, String8 destPath, FileFinder* ff);
+
+ // Nobody should be calling the default constructor
+ // So this space is intentionally left blank
+
+ // Default Copy Constructor and Destructor are fine
+
+ /** crunch is the workhorse of this class.
+ * It goes through all the files found in the sourcePath and compares
+ * them to the cached versions in the destPath. If the optional
+ * argument forceOverwrite is set to true, then all source files are
+ * re-crunched even if they have not been modified recently. Otherwise,
+ * source files are only crunched when they needUpdating. Afterwards,
+ * we delete any leftover files in the cache that are no longer present
+ * in source.
+ *
+ * PRECONDITIONS:
+ * No setup besides construction is needed
+ * POSTCONDITIONS:
+ * The cache is updated to fully reflect all changes in source.
+ * The function then returns the number of files changed in cache
+ * (counting deletions).
+ */
+ size_t crunch(CacheUpdater* cu, bool forceOverwrite=false);
+
+private:
+ /** loadFiles is a wrapper to the FileFinder that places matching
+ * files into mSourceFiles and mDestFiles.
+ *
+ * POSTCONDITIONS
+ * mDestFiles and mSourceFiles are refreshed to reflect the current
+ * state of the files in the source and dest directories.
+ * Any previous contents of mSourceFiles and mDestFiles are cleared.
+ */
+ void loadFiles();
+
+ /** needsUpdating takes a file path
+ * and returns true if the file represented by this path is newer in the
+ * sourceFiles than in the cache (mDestFiles).
+ *
+ * PRECONDITIONS:
+ * mSourceFiles and mDestFiles must be initialized and filled.
+ * POSTCONDITIONS:
+ * returns true if and only if source file's modification time
+ * is greater than the cached file's mod-time. Otherwise returns false.
+ *
+ * USAGE:
+ * Should be used something like the following:
+ * if (needsUpdating(filePath))
+ * // Recrunch sourceFile out to destFile.
+ *
+ */
+ bool needsUpdating(String8 relativePath) const;
+
+ // DATA MEMBERS ====================================================
+
+ String8 mSourcePath;
+ String8 mDestPath;
+
+ Vector<String8> mExtensions;
+
+ // Each vector of paths contains one entry per PNG file encountered.
+ // Each entry consists of a path pointing to that PNG.
+ DefaultKeyedVector<String8,time_t> mSourceFiles;
+ DefaultKeyedVector<String8,time_t> mDestFiles;
+
+ // Pointer to a FileFinder to use
+ FileFinder* mFileFinder;
+};
+
+#endif // CRUNCHCACHE_H
diff --git a/tools/aapt/DirectoryWalker.h b/tools/aapt/DirectoryWalker.h
new file mode 100644
index 0000000..88031d0
--- /dev/null
+++ b/tools/aapt/DirectoryWalker.h
@@ -0,0 +1,98 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Defines an abstraction for opening a directory on the filesystem and
+// iterating through it.
+
+#ifndef DIRECTORYWALKER_H
+#define DIRECTORYWALKER_H
+
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <utils/String8.h>
+
+#include <stdio.h>
+
+using namespace android;
+
+// Directory Walker
+// This is an abstraction for walking through a directory and getting files
+// and descriptions.
+
+class DirectoryWalker {
+public:
+ virtual ~DirectoryWalker() {};
+ virtual bool openDir(String8 path) = 0;
+ virtual bool openDir(const char* path) = 0;
+ // Advance to next directory entry
+ virtual struct dirent* nextEntry() = 0;
+ // Get the stats for the current entry
+ virtual struct stat* entryStats() = 0;
+ // Clean Up
+ virtual void closeDir() = 0;
+ // This class is able to replicate itself on the heap
+ virtual DirectoryWalker* clone() = 0;
+
+ // DATA MEMBERS
+ // Current directory entry
+ struct dirent mEntry;
+ // Stats for that directory entry
+ struct stat mStats;
+ // Base path
+ String8 mBasePath;
+};
+
+// System Directory Walker
+// This is an implementation of the above abstraction that calls
+// real system calls and is fully functional.
+// functions are inlined since they're very short and simple
+
+class SystemDirectoryWalker : public DirectoryWalker {
+
+ // Default constructor, copy constructor, and destructor are fine
+public:
+ virtual bool openDir(String8 path) {
+ mBasePath = path;
+ dir = NULL;
+ dir = opendir(mBasePath.string() );
+
+ if (dir == NULL)
+ return false;
+
+ return true;
+ };
+ virtual bool openDir(const char* path) {
+ String8 p(path);
+ openDir(p);
+ return true;
+ };
+ // Advance to next directory entry
+ virtual struct dirent* nextEntry() {
+ struct dirent* entryPtr = readdir(dir);
+ if (entryPtr == NULL)
+ return NULL;
+
+ mEntry = *entryPtr;
+ // Get stats
+ String8 fullPath = mBasePath.appendPathCopy(mEntry.d_name);
+ stat(fullPath.string(),&mStats);
+ return &mEntry;
+ };
+ // Get the stats for the current entry
+ virtual struct stat* entryStats() {
+ return &mStats;
+ };
+ virtual void closeDir() {
+ closedir(dir);
+ };
+ virtual DirectoryWalker* clone() {
+ return new SystemDirectoryWalker(*this);
+ };
+private:
+ DIR* dir;
+};
+
+#endif // DIRECTORYWALKER_H
diff --git a/tools/aapt/FileFinder.cpp b/tools/aapt/FileFinder.cpp
new file mode 100644
index 0000000..18775c0
--- /dev/null
+++ b/tools/aapt/FileFinder.cpp
@@ -0,0 +1,98 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+
+// File Finder implementation.
+// Implementation for the functions declared and documented in FileFinder.h
+
+#include <utils/Vector.h>
+#include <utils/String8.h>
+#include <utils/KeyedVector.h>
+
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include "DirectoryWalker.h"
+#include "FileFinder.h"
+
+//#define DEBUG
+
+using android::String8;
+
+// Private function to check whether a file is a directory or not
+bool isDirectory(const char* filename) {
+ struct stat fileStat;
+ if (stat(filename, &fileStat) == -1) {
+ return false;
+ }
+ return(S_ISDIR(fileStat.st_mode));
+}
+
+
+// Private function to check whether a file is a regular file or not
+bool isFile(const char* filename) {
+ struct stat fileStat;
+ if (stat(filename, &fileStat) == -1) {
+ return false;
+ }
+ return(S_ISREG(fileStat.st_mode));
+}
+
+bool SystemFileFinder::findFiles(String8 basePath, Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore,
+ DirectoryWalker* dw)
+{
+ // Scan the directory pointed to by basePath
+ // check files and recurse into subdirectories.
+ if (!dw->openDir(basePath)) {
+ return false;
+ }
+ /*
+ * Go through all directory entries. Check each file using checkAndAddFile
+ * and recurse into sub-directories.
+ */
+ struct dirent* entry;
+ while ((entry = dw->nextEntry()) != NULL) {
+ String8 entryName(entry->d_name);
+ if (entry->d_name[0] == '.') // Skip hidden files and directories
+ continue;
+
+ String8 fullPath = basePath.appendPathCopy(entryName);
+ // If this entry is a directory we'll recurse into it
+ if (isDirectory(fullPath.string()) ) {
+ DirectoryWalker* copy = dw->clone();
+ findFiles(fullPath, extensions, fileStore,copy);
+ delete copy;
+ }
+
+ // If this entry is a file, we'll pass it over to checkAndAddFile
+ if (isFile(fullPath.string()) ) {
+ checkAndAddFile(fullPath,dw->entryStats(),extensions,fileStore);
+ }
+ }
+
+ // Clean up
+ dw->closeDir();
+
+ return true;
+}
+
+void SystemFileFinder::checkAndAddFile(String8 path, const struct stat* stats,
+ Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore)
+{
+ // Loop over the extensions, checking for a match
+ bool done = false;
+ String8 ext(path.getPathExtension());
+ ext.toLower();
+ for (size_t i = 0; i < extensions.size() && !done; ++i) {
+ String8 ext2 = extensions[i].getPathExtension();
+ ext2.toLower();
+ // Compare the extensions. If a match is found, add to storage.
+ if (ext == ext2) {
+ done = true;
+ fileStore.add(path,stats->st_mtime);
+ }
+ }
+}
+
diff --git a/tools/aapt/FileFinder.h b/tools/aapt/FileFinder.h
new file mode 100644
index 0000000..6974aee
--- /dev/null
+++ b/tools/aapt/FileFinder.h
@@ -0,0 +1,80 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+
+// File Finder.
+// This is a collection of useful functions for finding paths and modification
+// times of files that match an extension pattern in a directory tree.
+// and finding files in it.
+
+#ifndef FILEFINDER_H
+#define FILEFINDER_H
+
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+
+#include "DirectoryWalker.h"
+
+using namespace android;
+
+// Abstraction to allow for dependency injection. See MockFileFinder.h
+// for the testing implementation.
+class FileFinder {
+public:
+ virtual bool findFiles(String8 basePath, Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore,
+ DirectoryWalker* dw) = 0;
+
+ virtual ~FileFinder() {};
+};
+
+class SystemFileFinder : public FileFinder {
+public:
+
+ /* findFiles takes a path, a Vector of extensions, and a destination KeyedVector
+ * and places path/modification date key/values pointing to
+ * all files with matching extensions found into the KeyedVector
+ * PRECONDITIONS
+ * path is a valid system path
+ * extensions should include leading "."
+ * This is not necessary, but the comparison directly
+ * compares the end of the path string so if the "."
+ * is excluded there is a small chance you could have
+ * a false positive match. (For example: extension "png"
+ * would match a file called "blahblahpng")
+ *
+ * POSTCONDITIONS
+ * fileStore contains (in no guaranteed order) paths to all
+ * matching files encountered in subdirectories of path
+ * as keys in the KeyedVector. Each key has the modification time
+ * of the file as its value.
+ *
+ * Calls checkAndAddFile on each file encountered in the directory tree
+ * Recursively descends into subdirectories.
+ */
+ virtual bool findFiles(String8 basePath, Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore,
+ DirectoryWalker* dw);
+
+private:
+ /**
+ * checkAndAddFile looks at a single file path and stat combo
+ * to determine whether it is a matching file (by looking at
+ * the extension)
+ *
+ * PRECONDITIONS
+ * no setup is needed
+ *
+ * POSTCONDITIONS
+ * If the given file has a matching extension then a new entry
+ * is added to the KeyedVector with the path as the key and the modification
+ * time as the value.
+ *
+ */
+ static void checkAndAddFile(String8 path, const struct stat* stats,
+ Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore);
+
+};
+#endif // FILEFINDER_H
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
new file mode 100644
index 0000000..b1a548e
--- /dev/null
+++ b/tools/aapt/Images.cpp
@@ -0,0 +1,1395 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#define PNG_INTERNAL
+
+#include "Images.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/ByteOrder.h>
+
+#include <png.h>
+#include <zlib.h>
+
+#define NOISY(x) //x
+
+static void
+png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ AaptFile* aaptfile = (AaptFile*) png_get_io_ptr(png_ptr);
+ status_t err = aaptfile->writeData(data, length);
+ if (err != NO_ERROR) {
+ png_error(png_ptr, "Write Error");
+ }
+}
+
+
+static void
+png_flush_aapt_file(png_structp png_ptr)
+{
+}
+
+// This holds an image as 8bpp RGBA.
+struct image_info
+{
+ image_info() : rows(NULL), is9Patch(false), allocRows(NULL) { }
+ ~image_info() {
+ if (rows && rows != allocRows) {
+ free(rows);
+ }
+ if (allocRows) {
+ for (int i=0; i<(int)allocHeight; i++) {
+ free(allocRows[i]);
+ }
+ free(allocRows);
+ }
+ free(info9Patch.xDivs);
+ free(info9Patch.yDivs);
+ free(info9Patch.colors);
+ }
+
+ png_uint_32 width;
+ png_uint_32 height;
+ png_bytepp rows;
+
+ // 9-patch info.
+ bool is9Patch;
+ Res_png_9patch info9Patch;
+
+ // Layout padding, if relevant
+ bool haveLayoutBounds;
+ int32_t layoutBoundsLeft;
+ int32_t layoutBoundsTop;
+ int32_t layoutBoundsRight;
+ int32_t layoutBoundsBottom;
+
+ png_uint_32 allocHeight;
+ png_bytepp allocRows;
+};
+
+static void log_warning(png_structp png_ptr, png_const_charp warning_message)
+{
+ const char* imageName = (const char*) png_get_error_ptr(png_ptr);
+ fprintf(stderr, "%s: libpng warning: %s\n", imageName, warning_message);
+}
+
+static void read_png(const char* imageName,
+ png_structp read_ptr, png_infop read_info,
+ image_info* outImageInfo)
+{
+ int color_type;
+ int bit_depth, interlace_type, compression_type;
+ int i;
+
+ png_set_error_fn(read_ptr, const_cast<char*>(imageName),
+ NULL /* use default errorfn */, log_warning);
+ png_read_info(read_ptr, read_info);
+
+ png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
+ &outImageInfo->height, &bit_depth, &color_type,
+ &interlace_type, &compression_type, NULL);
+
+ //printf("Image %s:\n", imageName);
+ //printf("color_type=%d, bit_depth=%d, interlace_type=%d, compression_type=%d\n",
+ // color_type, bit_depth, interlace_type, compression_type);
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE)
+ png_set_palette_to_rgb(read_ptr);
+
+ if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
+ png_set_expand_gray_1_2_4_to_8(read_ptr);
+
+ if (png_get_valid(read_ptr, read_info, PNG_INFO_tRNS)) {
+ //printf("Has PNG_INFO_tRNS!\n");
+ png_set_tRNS_to_alpha(read_ptr);
+ }
+
+ if (bit_depth == 16)
+ png_set_strip_16(read_ptr);
+
+ if ((color_type&PNG_COLOR_MASK_ALPHA) == 0)
+ png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
+
+ if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ png_set_gray_to_rgb(read_ptr);
+
+ png_set_interlace_handling(read_ptr);
+
+ png_read_update_info(read_ptr, read_info);
+
+ outImageInfo->rows = (png_bytepp)malloc(
+ outImageInfo->height * sizeof(png_bytep));
+ outImageInfo->allocHeight = outImageInfo->height;
+ outImageInfo->allocRows = outImageInfo->rows;
+
+ png_set_rows(read_ptr, read_info, outImageInfo->rows);
+
+ for (i = 0; i < (int)outImageInfo->height; i++)
+ {
+ outImageInfo->rows[i] = (png_bytep)
+ malloc(png_get_rowbytes(read_ptr, read_info));
+ }
+
+ png_read_image(read_ptr, outImageInfo->rows);
+
+ png_read_end(read_ptr, read_info);
+
+ NOISY(printf("Image %s: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
+ imageName,
+ (int)outImageInfo->width, (int)outImageInfo->height,
+ bit_depth, color_type,
+ interlace_type, compression_type));
+
+ png_get_IHDR(read_ptr, read_info, &outImageInfo->width,
+ &outImageInfo->height, &bit_depth, &color_type,
+ &interlace_type, &compression_type, NULL);
+}
+
+#define COLOR_TRANSPARENT 0
+#define COLOR_WHITE 0xFFFFFFFF
+#define COLOR_TICK 0xFF000000
+#define COLOR_LAYOUT_BOUNDS_TICK 0xFF0000FF
+
+enum {
+ TICK_TYPE_NONE,
+ TICK_TYPE_TICK,
+ TICK_TYPE_LAYOUT_BOUNDS,
+ TICK_TYPE_BOTH
+};
+
+static int tick_type(png_bytep p, bool transparent, const char** outError)
+{
+ png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+
+ if (transparent) {
+ if (p[3] == 0) {
+ return TICK_TYPE_NONE;
+ }
+ if (color == COLOR_LAYOUT_BOUNDS_TICK) {
+ return TICK_TYPE_LAYOUT_BOUNDS;
+ }
+ if (color == COLOR_TICK) {
+ return TICK_TYPE_TICK;
+ }
+
+ // Error cases
+ if (p[3] != 0xff) {
+ *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)";
+ return TICK_TYPE_NONE;
+ }
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in transparent frame must be black or red";
+ }
+ return TICK_TYPE_TICK;
+ }
+
+ if (p[3] != 0xFF) {
+ *outError = "White frame must be a solid color (no alpha)";
+ }
+ if (color == COLOR_WHITE) {
+ return TICK_TYPE_NONE;
+ }
+ if (color == COLOR_TICK) {
+ return TICK_TYPE_TICK;
+ }
+ if (color == COLOR_LAYOUT_BOUNDS_TICK) {
+ return TICK_TYPE_LAYOUT_BOUNDS;
+ }
+
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in white frame must be black or red";
+ return TICK_TYPE_NONE;
+ }
+ return TICK_TYPE_TICK;
+}
+
+enum {
+ TICK_START,
+ TICK_INSIDE_1,
+ TICK_OUTSIDE_1
+};
+
+static status_t get_horizontal_ticks(
+ png_bytep row, int width, bool transparent, bool required,
+ int32_t* outLeft, int32_t* outRight, const char** outError,
+ uint8_t* outDivs, bool multipleAllowed)
+{
+ int i;
+ *outLeft = *outRight = -1;
+ int state = TICK_START;
+ bool found = false;
+
+ for (i=1; i<width-1; i++) {
+ if (TICK_TYPE_TICK == tick_type(row+i*4, transparent, outError)) {
+ if (state == TICK_START ||
+ (state == TICK_OUTSIDE_1 && multipleAllowed)) {
+ *outLeft = i-1;
+ *outRight = width-2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
+ }
+ state = TICK_INSIDE_1;
+ } else if (state == TICK_OUTSIDE_1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outLeft = i;
+ return UNKNOWN_ERROR;
+ }
+ } else if (*outError == NULL) {
+ if (state == TICK_INSIDE_1) {
+ // We're done with this div. Move on to the next.
+ *outRight = i-1;
+ outRight += 2;
+ outLeft += 2;
+ state = TICK_OUTSIDE_1;
+ }
+ } else {
+ *outLeft = i;
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outLeft = -1;
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+static status_t get_vertical_ticks(
+ png_bytepp rows, int offset, int height, bool transparent, bool required,
+ int32_t* outTop, int32_t* outBottom, const char** outError,
+ uint8_t* outDivs, bool multipleAllowed)
+{
+ int i;
+ *outTop = *outBottom = -1;
+ int state = TICK_START;
+ bool found = false;
+
+ for (i=1; i<height-1; i++) {
+ if (TICK_TYPE_TICK == tick_type(rows[i]+offset, transparent, outError)) {
+ if (state == TICK_START ||
+ (state == TICK_OUTSIDE_1 && multipleAllowed)) {
+ *outTop = i-1;
+ *outBottom = height-2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
+ }
+ state = TICK_INSIDE_1;
+ } else if (state == TICK_OUTSIDE_1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outTop = i;
+ return UNKNOWN_ERROR;
+ }
+ } else if (*outError == NULL) {
+ if (state == TICK_INSIDE_1) {
+ // We're done with this div. Move on to the next.
+ *outBottom = i-1;
+ outTop += 2;
+ outBottom += 2;
+ state = TICK_OUTSIDE_1;
+ }
+ } else {
+ *outTop = i;
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outTop = -1;
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+static status_t get_horizontal_layout_bounds_ticks(
+ png_bytep row, int width, bool transparent, bool required,
+ int32_t* outLeft, int32_t* outRight, const char** outError)
+{
+ int i;
+ *outLeft = *outRight = 0;
+
+ // Look for left tick
+ if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + 4, transparent, outError)) {
+ // Starting with a layout padding tick
+ i = 1;
+ while (i < width - 1) {
+ (*outLeft)++;
+ i++;
+ int tick = tick_type(row + i * 4, transparent, outError);
+ if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+ break;
+ }
+ }
+ }
+
+ // Look for right tick
+ if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + (width - 2) * 4, transparent, outError)) {
+ // Ending with a layout padding tick
+ i = width - 2;
+ while (i > 1) {
+ (*outRight)++;
+ i--;
+ int tick = tick_type(row+i*4, transparent, outError);
+ if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+ break;
+ }
+ }
+ }
+
+ return NO_ERROR;
+}
+
+static status_t get_vertical_layout_bounds_ticks(
+ png_bytepp rows, int offset, int height, bool transparent, bool required,
+ int32_t* outTop, int32_t* outBottom, const char** outError)
+{
+ int i;
+ *outTop = *outBottom = 0;
+
+ // Look for top tick
+ if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[1] + offset, transparent, outError)) {
+ // Starting with a layout padding tick
+ i = 1;
+ while (i < height - 1) {
+ (*outTop)++;
+ i++;
+ int tick = tick_type(rows[i] + offset, transparent, outError);
+ if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+ break;
+ }
+ }
+ }
+
+ // Look for bottom tick
+ if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[height - 2] + offset, transparent, outError)) {
+ // Ending with a layout padding tick
+ i = height - 2;
+ while (i > 1) {
+ (*outBottom)++;
+ i--;
+ int tick = tick_type(rows[i] + offset, transparent, outError);
+ if (tick != TICK_TYPE_LAYOUT_BOUNDS) {
+ break;
+ }
+ }
+ }
+
+ return NO_ERROR;
+}
+
+
+static uint32_t get_color(
+ png_bytepp rows, int left, int top, int right, int bottom)
+{
+ png_bytep color = rows[top] + left*4;
+
+ if (left > right || top > bottom) {
+ return Res_png_9patch::TRANSPARENT_COLOR;
+ }
+
+ while (top <= bottom) {
+ for (int i = left; i <= right; i++) {
+ png_bytep p = rows[top]+i*4;
+ if (color[3] == 0) {
+ if (p[3] != 0) {
+ return Res_png_9patch::NO_COLOR;
+ }
+ } else if (p[0] != color[0] || p[1] != color[1]
+ || p[2] != color[2] || p[3] != color[3]) {
+ return Res_png_9patch::NO_COLOR;
+ }
+ }
+ top++;
+ }
+
+ if (color[3] == 0) {
+ return Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
+}
+
+static void select_patch(
+ int which, int front, int back, int size, int* start, int* end)
+{
+ switch (which) {
+ case 0:
+ *start = 0;
+ *end = front-1;
+ break;
+ case 1:
+ *start = front;
+ *end = back-1;
+ break;
+ case 2:
+ *start = back;
+ *end = size-1;
+ break;
+ }
+}
+
+static uint32_t get_color(image_info* image, int hpatch, int vpatch)
+{
+ int left, right, top, bottom;
+ select_patch(
+ hpatch, image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
+ image->width, &left, &right);
+ select_patch(
+ vpatch, image->info9Patch.yDivs[0], image->info9Patch.yDivs[1],
+ image->height, &top, &bottom);
+ //printf("Selecting h=%d v=%d: (%d,%d)-(%d,%d)\n",
+ // hpatch, vpatch, left, top, right, bottom);
+ const uint32_t c = get_color(image->rows, left, top, right, bottom);
+ NOISY(printf("Color in (%d,%d)-(%d,%d): #%08x\n", left, top, right, bottom, c));
+ return c;
+}
+
+static status_t do_9patch(const char* imageName, image_info* image)
+{
+ image->is9Patch = true;
+
+ int W = image->width;
+ int H = image->height;
+ int i, j;
+
+ int maxSizeXDivs = W * sizeof(int32_t);
+ int maxSizeYDivs = H * sizeof(int32_t);
+ int32_t* xDivs = image->info9Patch.xDivs = (int32_t*) malloc(maxSizeXDivs);
+ int32_t* yDivs = image->info9Patch.yDivs = (int32_t*) malloc(maxSizeYDivs);
+ uint8_t numXDivs = 0;
+ uint8_t numYDivs = 0;
+ int8_t numColors;
+ int numRows;
+ int numCols;
+ int top;
+ int left;
+ int right;
+ int bottom;
+ memset(xDivs, -1, maxSizeXDivs);
+ memset(yDivs, -1, maxSizeYDivs);
+ image->info9Patch.paddingLeft = image->info9Patch.paddingRight =
+ image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
+
+ image->layoutBoundsLeft = image->layoutBoundsRight =
+ image->layoutBoundsTop = image->layoutBoundsBottom = 0;
+
+ png_bytep p = image->rows[0];
+ bool transparent = p[3] == 0;
+ bool hasColor = false;
+
+ const char* errorMsg = NULL;
+ int errorPixel = -1;
+ const char* errorEdge = NULL;
+
+ int colorIndex = 0;
+
+ // Validate size...
+ if (W < 3 || H < 3) {
+ errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
+ goto getout;
+ }
+
+ // Validate frame...
+ if (!transparent &&
+ (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+ errorMsg = "Must have one-pixel frame that is either transparent or white";
+ goto getout;
+ }
+
+ // Find left and right of sizing areas...
+ if (get_horizontal_ticks(p, W, transparent, true, &xDivs[0],
+ &xDivs[1], &errorMsg, &numXDivs, true) != NO_ERROR) {
+ errorPixel = xDivs[0];
+ errorEdge = "top";
+ goto getout;
+ }
+
+ // Find top and bottom of sizing areas...
+ if (get_vertical_ticks(image->rows, 0, H, transparent, true, &yDivs[0],
+ &yDivs[1], &errorMsg, &numYDivs, true) != NO_ERROR) {
+ errorPixel = yDivs[0];
+ errorEdge = "left";
+ goto getout;
+ }
+
+ // Copy patch size data into image...
+ image->info9Patch.numXDivs = numXDivs;
+ image->info9Patch.numYDivs = numYDivs;
+
+ // Find left and right of padding area...
+ if (get_horizontal_ticks(image->rows[H-1], W, transparent, false, &image->info9Patch.paddingLeft,
+ &image->info9Patch.paddingRight, &errorMsg, NULL, false) != NO_ERROR) {
+ errorPixel = image->info9Patch.paddingLeft;
+ errorEdge = "bottom";
+ goto getout;
+ }
+
+ // Find top and bottom of padding area...
+ if (get_vertical_ticks(image->rows, (W-1)*4, H, transparent, false, &image->info9Patch.paddingTop,
+ &image->info9Patch.paddingBottom, &errorMsg, NULL, false) != NO_ERROR) {
+ errorPixel = image->info9Patch.paddingTop;
+ errorEdge = "right";
+ goto getout;
+ }
+
+ // Find left and right of layout padding...
+ get_horizontal_layout_bounds_ticks(image->rows[H-1], W, transparent, false,
+ &image->layoutBoundsLeft,
+ &image->layoutBoundsRight, &errorMsg);
+
+ get_vertical_layout_bounds_ticks(image->rows, (W-1)*4, H, transparent, false,
+ &image->layoutBoundsTop,
+ &image->layoutBoundsBottom, &errorMsg);
+
+ image->haveLayoutBounds = image->layoutBoundsLeft != 0
+ || image->layoutBoundsRight != 0
+ || image->layoutBoundsTop != 0
+ || image->layoutBoundsBottom != 0;
+
+ if (image->haveLayoutBounds) {
+ NOISY(printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
+ image->layoutBoundsRight, image->layoutBoundsBottom));
+ }
+
+ // If padding is not yet specified, take values from size.
+ if (image->info9Patch.paddingLeft < 0) {
+ image->info9Patch.paddingLeft = xDivs[0];
+ image->info9Patch.paddingRight = W - 2 - xDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+ }
+ if (image->info9Patch.paddingTop < 0) {
+ image->info9Patch.paddingTop = yDivs[0];
+ image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
+ }
+
+ NOISY(printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
+ image->info9Patch.xDivs[0], image->info9Patch.xDivs[1],
+ image->info9Patch.yDivs[0], image->info9Patch.yDivs[1]));
+ NOISY(printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
+ image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
+ image->info9Patch.paddingTop, image->info9Patch.paddingBottom));
+
+ // Remove frame from image.
+ image->rows = (png_bytepp)malloc((H-2) * sizeof(png_bytep));
+ for (i=0; i<(H-2); i++) {
+ image->rows[i] = image->allocRows[i+1];
+ memmove(image->rows[i], image->rows[i]+4, (W-2)*4);
+ }
+ image->width -= 2;
+ W = image->width;
+ image->height -= 2;
+ H = image->height;
+
+ // Figure out the number of rows and columns in the N-patch
+ numCols = numXDivs + 1;
+ if (xDivs[0] == 0) { // Column 1 is strechable
+ numCols--;
+ }
+ if (xDivs[numXDivs - 1] == W) {
+ numCols--;
+ }
+ numRows = numYDivs + 1;
+ if (yDivs[0] == 0) { // Row 1 is strechable
+ numRows--;
+ }
+ if (yDivs[numYDivs - 1] == H) {
+ numRows--;
+ }
+
+ // Make sure the amount of rows and columns will fit in the number of
+ // colors we can use in the 9-patch format.
+ if (numRows * numCols > 0x7F) {
+ errorMsg = "Too many rows and columns in 9-patch perimeter";
+ goto getout;
+ }
+
+ numColors = numRows * numCols;
+ image->info9Patch.numColors = numColors;
+ image->info9Patch.colors = (uint32_t*)malloc(numColors * sizeof(uint32_t));
+
+ // Fill in color information for each patch.
+
+ uint32_t c;
+ top = 0;
+
+ // The first row always starts with the top being at y=0 and the bottom
+ // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
+ // the first row is stretchable along the Y axis, otherwise it is fixed.
+ // The last row always ends with the bottom being bitmap.height and the top
+ // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+ // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+ // the Y axis, otherwise it is fixed.
+ //
+ // The first and last columns are similarly treated with respect to the X
+ // axis.
+ //
+ // The above is to help explain some of the special casing that goes on the
+ // code below.
+
+ // The initial yDiv and whether the first row is considered stretchable or
+ // not depends on whether yDiv[0] was zero or not.
+ for (j = (yDivs[0] == 0 ? 1 : 0);
+ j <= numYDivs && top < H;
+ j++) {
+ if (j == numYDivs) {
+ bottom = H;
+ } else {
+ bottom = yDivs[j];
+ }
+ left = 0;
+ // The initial xDiv and whether the first column is considered
+ // stretchable or not depends on whether xDiv[0] was zero or not.
+ for (i = xDivs[0] == 0 ? 1 : 0;
+ i <= numXDivs && left < W;
+ i++) {
+ if (i == numXDivs) {
+ right = W;
+ } else {
+ right = xDivs[i];
+ }
+ c = get_color(image->rows, left, top, right - 1, bottom - 1);
+ image->info9Patch.colors[colorIndex++] = c;
+ NOISY(if (c != Res_png_9patch::NO_COLOR) hasColor = true);
+ left = right;
+ }
+ top = bottom;
+ }
+
+ assert(colorIndex == numColors);
+
+ for (i=0; i<numColors; i++) {
+ if (hasColor) {
+ if (i == 0) printf("Colors in %s:\n ", imageName);
+ printf(" #%08x", image->info9Patch.colors[i]);
+ if (i == numColors - 1) printf("\n");
+ }
+ }
+
+ image->is9Patch = true;
+ image->info9Patch.deviceToFile();
+
+getout:
+ if (errorMsg) {
+ fprintf(stderr,
+ "ERROR: 9-patch image %s malformed.\n"
+ " %s.\n", imageName, errorMsg);
+ if (errorEdge != NULL) {
+ if (errorPixel >= 0) {
+ fprintf(stderr,
+ " Found at pixel #%d along %s edge.\n", errorPixel, errorEdge);
+ } else {
+ fprintf(stderr,
+ " Found along %s edge.\n", errorEdge);
+ }
+ }
+ return UNKNOWN_ERROR;
+ }
+ return NO_ERROR;
+}
+
+static void checkNinePatchSerialization(Res_png_9patch* inPatch, void * data)
+{
+ if (sizeof(void*) != sizeof(int32_t)) {
+ // can't deserialize on a non-32 bit system
+ return;
+ }
+ size_t patchSize = inPatch->serializedSize();
+ void * newData = malloc(patchSize);
+ memcpy(newData, data, patchSize);
+ Res_png_9patch* outPatch = inPatch->deserialize(newData);
+ // deserialization is done in place, so outPatch == newData
+ assert(outPatch == newData);
+ assert(outPatch->numXDivs == inPatch->numXDivs);
+ assert(outPatch->numYDivs == inPatch->numYDivs);
+ assert(outPatch->paddingLeft == inPatch->paddingLeft);
+ assert(outPatch->paddingRight == inPatch->paddingRight);
+ assert(outPatch->paddingTop == inPatch->paddingTop);
+ assert(outPatch->paddingBottom == inPatch->paddingBottom);
+ for (int i = 0; i < outPatch->numXDivs; i++) {
+ assert(outPatch->xDivs[i] == inPatch->xDivs[i]);
+ }
+ for (int i = 0; i < outPatch->numYDivs; i++) {
+ assert(outPatch->yDivs[i] == inPatch->yDivs[i]);
+ }
+ for (int i = 0; i < outPatch->numColors; i++) {
+ assert(outPatch->colors[i] == inPatch->colors[i]);
+ }
+ free(newData);
+}
+
+static bool patch_equals(Res_png_9patch& patch1, Res_png_9patch& patch2) {
+ if (!(patch1.numXDivs == patch2.numXDivs &&
+ patch1.numYDivs == patch2.numYDivs &&
+ patch1.numColors == patch2.numColors &&
+ patch1.paddingLeft == patch2.paddingLeft &&
+ patch1.paddingRight == patch2.paddingRight &&
+ patch1.paddingTop == patch2.paddingTop &&
+ patch1.paddingBottom == patch2.paddingBottom)) {
+ return false;
+ }
+ for (int i = 0; i < patch1.numColors; i++) {
+ if (patch1.colors[i] != patch2.colors[i]) {
+ return false;
+ }
+ }
+ for (int i = 0; i < patch1.numXDivs; i++) {
+ if (patch1.xDivs[i] != patch2.xDivs[i]) {
+ return false;
+ }
+ }
+ for (int i = 0; i < patch1.numYDivs; i++) {
+ if (patch1.yDivs[i] != patch2.yDivs[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+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++) {
+ rr = row[0];
+ gg = row[1];
+ bb = row[2];
+ aa = row[3];
+ row += bpp;
+
+ if (i == 0) {
+ printf("Row %d:", j);
+ }
+ switch (bpp) {
+ case 1:
+ printf(" (%d)", rr);
+ break;
+ case 2:
+ printf(" (%d %d", rr, gg);
+ break;
+ case 3:
+ printf(" (%d %d %d)", rr, gg, bb);
+ break;
+ case 4:
+ printf(" (%d %d %d %d)", rr, gg, bb, aa);
+ break;
+ }
+ if (i == (w - 1)) {
+ NOISY(printf("\n"));
+ }
+ }
+ }
+}
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define ABS(a) ((a)<0?-(a):(a))
+
+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)
+{
+ int w = imageInfo.width;
+ int h = imageInfo.height;
+ int i, j, rr, gg, bb, aa, idx;
+ uint32_t colors[256], col;
+ int num_colors = 0;
+ int maxGrayDeviation = 0;
+
+ bool isOpaque = true;
+ bool isPalette = true;
+ bool isGrayscale = true;
+
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors
+
+ // NOISY(printf("Initial image data:\n"));
+ // dump_image(w, h, imageInfo.rows, PNG_COLOR_TYPE_RGB_ALPHA);
+
+ for (j = 0; j < h; j++) {
+ png_bytep row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ int odev = maxGrayDeviation;
+ maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+ if (maxGrayDeviation > odev) {
+ NOISY(printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+ maxGrayDeviation, i, j, rr, gg, bb, aa));
+ }
+
+ // Check if image is really grayscale
+ if (isGrayscale) {
+ if (rr != gg || rr != bb) {
+ NOISY(printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
+ i, j, rr, gg, bb, aa));
+ isGrayscale = false;
+ }
+ }
+
+ // Check if image is really opaque
+ if (isOpaque) {
+ if (aa != 0xff) {
+ NOISY(printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
+ i, j, rr, gg, bb, aa));
+ isOpaque = false;
+ }
+ }
+
+ // Check if image is really <= 256 colors
+ if (isPalette) {
+ col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
+ bool match = false;
+ for (idx = 0; idx < num_colors; idx++) {
+ if (colors[idx] == col) {
+ match = true;
+ break;
+ }
+ }
+
+ // Write the palette index for the pixel to outRows optimistically
+ // We might overwrite it later if we decide to encode as gray or
+ // gray + alpha
+ *out++ = idx;
+ if (!match) {
+ if (num_colors == 256) {
+ NOISY(printf("Found 257th color at %d, %d\n", i, j));
+ isPalette = false;
+ } else {
+ colors[num_colors++] = col;
+ }
+ }
+ }
+ }
+ }
+
+ *paletteEntries = 0;
+ *hasTransparency = !isOpaque;
+ int bpp = isOpaque ? 3 : 4;
+ int paletteSize = w * h + bpp * num_colors;
+
+ NOISY(printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"));
+ NOISY(printf("isOpaque = %s\n", isOpaque ? "true" : "false"));
+ NOISY(printf("isPalette = %s\n", isPalette ? "true" : "false"));
+ NOISY(printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
+ paletteSize, 2 * w * h, bpp * w * h));
+ NOISY(printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance));
+
+ // Choose the best color type for the image.
+ // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+ // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
+ // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+ // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
+ // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+ if (isGrayscale) {
+ if (isOpaque) {
+ *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
+ } else {
+ // Use a simple heuristic to determine whether using a palette will
+ // save space versus using gray + alpha for each pixel.
+ // This doesn't take into account chunk overhead, filtering, LZ
+ // compression, etc.
+ if (isPalette && (paletteSize < 2 * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
+ } else {
+ *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
+ }
+ }
+ } else if (isPalette && (paletteSize < bpp * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE;
+ } else {
+ if (maxGrayDeviation <= grayscaleTolerance) {
+ 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;
+ }
+ }
+
+ // Perform postprocessing of the image or palette data based on the final
+ // color type chosen
+
+ if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+ // Create separate RGB and Alpha palettes and set the number of colors
+ *paletteEntries = num_colors;
+
+ // Create the RGB and alpha palettes
+ for (int idx = 0; idx < num_colors; idx++) {
+ col = colors[idx];
+ rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
+ rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
+ rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
+ alphaPalette[idx] = (png_byte) (col & 0xff);
+ }
+ } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ // If the image is gray or gray + alpha, compact the pixels into outRows
+ for (j = 0; j < h; j++) {
+ png_bytep row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ if (isGrayscale) {
+ *out++ = rr;
+ } else {
+ *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+ }
+ if (!isOpaque) {
+ *out++ = aa;
+ }
+ }
+ }
+ }
+}
+
+
+static void write_png(const char* imageName,
+ png_structp write_ptr, png_infop write_info,
+ image_info& imageInfo, int grayscaleTolerance)
+{
+ bool optimize = true;
+ png_uint_32 width, height;
+ int color_type;
+ int bit_depth, interlace_type, compression_type;
+ int i;
+
+ png_unknown_chunk unknowns[2];
+ unknowns[0].data = NULL;
+ unknowns[1].data = NULL;
+
+ png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * sizeof(png_bytep));
+ if (outRows == (png_bytepp) 0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ for (i = 0; i < (int) imageInfo.height; i++) {
+ outRows[i] = (png_bytep) malloc(2 * (int) imageInfo.width);
+ if (outRows[i] == (png_bytep) 0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ }
+
+ png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
+
+ NOISY(printf("Writing image %s: w = %d, h = %d\n", imageName,
+ (int) imageInfo.width, (int) imageInfo.height));
+
+ png_color rgbPalette[256];
+ png_byte alphaPalette[256];
+ bool hasTransparency;
+ int paletteEntries;
+
+ 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",
+ imageName, paletteEntries,
+ hasTransparency ? " (with alpha)" : ""));
+ break;
+ case PNG_COLOR_TYPE_GRAY:
+ NOISY(printf("Image %s is opaque gray, using PNG_COLOR_TYPE_GRAY\n", imageName));
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ NOISY(printf("Image %s is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA\n", imageName));
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ NOISY(printf("Image %s is opaque RGB, using PNG_COLOR_TYPE_RGB\n", imageName));
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ NOISY(printf("Image %s is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA\n", imageName));
+ break;
+ }
+
+ png_set_IHDR(write_ptr, write_info, imageInfo.width, imageInfo.height,
+ 8, color_type, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE) {
+ png_set_PLTE(write_ptr, write_info, rgbPalette, paletteEntries);
+ if (hasTransparency) {
+ png_set_tRNS(write_ptr, write_info, alphaPalette, paletteEntries, (png_color_16p) 0);
+ }
+ png_set_filter(write_ptr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(write_ptr, 0, PNG_ALL_FILTERS);
+ }
+
+ if (imageInfo.is9Patch) {
+ int chunk_count = 1 + (imageInfo.haveLayoutBounds ? 1 : 0);
+ int p_index = imageInfo.haveLayoutBounds ? 1 : 0;
+ int b_index = 0;
+ png_byte *chunk_names = imageInfo.haveLayoutBounds
+ ? (png_byte*)"npLb\0npTc\0"
+ : (png_byte*)"npTc";
+ NOISY(printf("Adding 9-patch info...\n"));
+ strcpy((char*)unknowns[p_index].name, "npTc");
+ unknowns[p_index].data = (png_byte*)imageInfo.info9Patch.serialize();
+ unknowns[p_index].size = imageInfo.info9Patch.serializedSize();
+ // TODO: remove the check below when everything works
+ checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data);
+
+ if (imageInfo.haveLayoutBounds) {
+ int chunk_size = sizeof(png_uint_32) * 4;
+ strcpy((char*)unknowns[b_index].name, "npLb");
+ unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1);
+ memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size);
+ unknowns[b_index].size = chunk_size;
+ }
+
+ for (int i = 0; i < chunk_count; i++) {
+ unknowns[i].location = PNG_HAVE_PLTE;
+ }
+ png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS,
+ chunk_names, chunk_count);
+ png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count);
+#if PNG_LIBPNG_VER < 10600
+ /* Deal with unknown chunk location bug in 1.5.x and earlier */
+ png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE);
+ if (imageInfo.haveLayoutBounds) {
+ png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE);
+ }
+#endif
+ }
+
+
+ png_write_info(write_ptr, write_info);
+
+ png_bytepp rows;
+ if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+ if (color_type == PNG_COLOR_TYPE_RGB) {
+ png_set_filler(write_ptr, 0, PNG_FILLER_AFTER);
+ }
+ rows = imageInfo.rows;
+ } else {
+ rows = outRows;
+ }
+ png_write_image(write_ptr, rows);
+
+// NOISY(printf("Final image data:\n"));
+// dump_image(imageInfo.width, imageInfo.height, rows, color_type);
+
+ png_write_end(write_ptr, write_info);
+
+ for (i = 0; i < (int) imageInfo.height; i++) {
+ free(outRows[i]);
+ }
+ free(outRows);
+ free(unknowns[0].data);
+ free(unknowns[1].data);
+
+ png_get_IHDR(write_ptr, write_info, &width, &height,
+ &bit_depth, &color_type, &interlace_type,
+ &compression_type, NULL);
+
+ NOISY(printf("Image written: w=%d, h=%d, d=%d, colors=%d, inter=%d, comp=%d\n",
+ (int)width, (int)height, bit_depth, color_type, interlace_type,
+ compression_type));
+}
+
+status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
+ const sp<AaptFile>& file, String8* outNewLeafName)
+{
+ String8 ext(file->getPath().getPathExtension());
+
+ // We currently only process PNG images.
+ if (strcmp(ext.string(), ".png") != 0) {
+ return NO_ERROR;
+ }
+
+ // Example of renaming a file:
+ //*outNewLeafName = file->getPath().getBasePath().getFileName();
+ //outNewLeafName->append(".nupng");
+
+ String8 printableName(file->getPrintableSource());
+
+ if (bundle->getVerbose()) {
+ printf("Processing image: %s\n", printableName.string());
+ }
+
+ png_structp read_ptr = NULL;
+ png_infop read_info = NULL;
+ FILE* fp;
+
+ image_info imageInfo;
+
+ png_structp write_ptr = NULL;
+ png_infop write_info = NULL;
+
+ status_t error = UNKNOWN_ERROR;
+
+ const size_t nameLen = file->getPath().length();
+
+ fp = fopen(file->getSourceFile().string(), "rb");
+ if (fp == NULL) {
+ fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string());
+ goto bail;
+ }
+
+ read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
+ (png_error_ptr)NULL);
+ if (!read_ptr) {
+ goto bail;
+ }
+
+ read_info = png_create_info_struct(read_ptr);
+ if (!read_info) {
+ goto bail;
+ }
+
+ if (setjmp(png_jmpbuf(read_ptr))) {
+ goto bail;
+ }
+
+ png_init_io(read_ptr, fp);
+
+ read_png(printableName.string(), read_ptr, read_info, &imageInfo);
+
+ if (nameLen > 6) {
+ const char* name = file->getPath().string();
+ if (name[nameLen-5] == '9' && name[nameLen-6] == '.') {
+ if (do_9patch(printableName.string(), &imageInfo) != NO_ERROR) {
+ goto bail;
+ }
+ }
+ }
+
+ write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL,
+ (png_error_ptr)NULL);
+ if (!write_ptr)
+ {
+ goto bail;
+ }
+
+ write_info = png_create_info_struct(write_ptr);
+ if (!write_info)
+ {
+ goto bail;
+ }
+
+ png_set_write_fn(write_ptr, (void*)file.get(),
+ png_write_aapt_file, png_flush_aapt_file);
+
+ if (setjmp(png_jmpbuf(write_ptr)))
+ {
+ goto bail;
+ }
+
+ write_png(printableName.string(), write_ptr, write_info, imageInfo,
+ bundle->getGrayscaleTolerance());
+
+ error = NO_ERROR;
+
+ if (bundle->getVerbose()) {
+ fseek(fp, 0, SEEK_END);
+ size_t oldSize = (size_t)ftell(fp);
+ size_t newSize = file->getSize();
+ float factor = ((float)newSize)/oldSize;
+ int percent = (int)(factor*100);
+ printf(" (processed image %s: %d%% size of source)\n", printableName.string(), percent);
+ }
+
+bail:
+ if (read_ptr) {
+ png_destroy_read_struct(&read_ptr, &read_info, (png_infopp)NULL);
+ }
+ if (fp) {
+ fclose(fp);
+ }
+ if (write_ptr) {
+ png_destroy_write_struct(&write_ptr, &write_info);
+ }
+
+ if (error != NO_ERROR) {
+ fprintf(stderr, "ERROR: Failure processing PNG image %s\n",
+ file->getPrintableSource().string());
+ }
+ return error;
+}
+
+status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest)
+{
+ png_structp read_ptr = NULL;
+ png_infop read_info = NULL;
+
+ FILE* fp;
+
+ image_info imageInfo;
+
+ png_structp write_ptr = NULL;
+ png_infop write_info = NULL;
+
+ status_t error = UNKNOWN_ERROR;
+
+ if (bundle->getVerbose()) {
+ printf("Processing image to cache: %s => %s\n", source.string(), dest.string());
+ }
+
+ // Get a file handler to read from
+ fp = fopen(source.string(),"rb");
+ if (fp == NULL) {
+ fprintf(stderr, "%s ERROR: Unable to open PNG file\n", source.string());
+ return error;
+ }
+
+ // Call libpng to get a struct to read image data into
+ read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!read_ptr) {
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+ return error;
+ }
+
+ // Call libpng to get a struct to read image info into
+ read_info = png_create_info_struct(read_ptr);
+ if (!read_info) {
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+ return error;
+ }
+
+ // Set a jump point for libpng to long jump back to on error
+ if (setjmp(png_jmpbuf(read_ptr))) {
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+ return error;
+ }
+
+ // Set up libpng to read from our file.
+ png_init_io(read_ptr,fp);
+
+ // Actually read data from the file
+ read_png(source.string(), read_ptr, read_info, &imageInfo);
+
+ // We're done reading so we can clean up
+ // Find old file size before releasing handle
+ fseek(fp, 0, SEEK_END);
+ size_t oldSize = (size_t)ftell(fp);
+ fclose(fp);
+ png_destroy_read_struct(&read_ptr, &read_info,NULL);
+
+ // Check to see if we're dealing with a 9-patch
+ // If we are, process appropriately
+ if (source.getBasePath().getPathExtension() == ".9") {
+ if (do_9patch(source.string(), &imageInfo) != NO_ERROR) {
+ return error;
+ }
+ }
+
+ // Call libpng to create a structure to hold the processed image data
+ // that can be written to disk
+ write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!write_ptr) {
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Call libpng to create a structure to hold processed image info that can
+ // be written to disk
+ write_info = png_create_info_struct(write_ptr);
+ if (!write_info) {
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Open up our destination file for writing
+ fp = fopen(dest.string(), "wb");
+ if (!fp) {
+ fprintf(stderr, "%s ERROR: Unable to open PNG file\n", dest.string());
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Set up libpng to write to our file
+ png_init_io(write_ptr, fp);
+
+ // Set up a jump for libpng to long jump back on on errors
+ if (setjmp(png_jmpbuf(write_ptr))) {
+ fclose(fp);
+ png_destroy_write_struct(&write_ptr, &write_info);
+ return error;
+ }
+
+ // Actually write out to the new png
+ write_png(dest.string(), write_ptr, write_info, imageInfo,
+ bundle->getGrayscaleTolerance());
+
+ if (bundle->getVerbose()) {
+ // Find the size of our new file
+ FILE* reader = fopen(dest.string(), "rb");
+ fseek(reader, 0, SEEK_END);
+ size_t newSize = (size_t)ftell(reader);
+ fclose(reader);
+
+ float factor = ((float)newSize)/oldSize;
+ int percent = (int)(factor*100);
+ printf(" (processed image to cache entry %s: %d%% size of source)\n",
+ dest.string(), percent);
+ }
+
+ //Clean up
+ fclose(fp);
+ png_destroy_write_struct(&write_ptr, &write_info);
+
+ return NO_ERROR;
+}
+
+status_t postProcessImage(const sp<AaptAssets>& assets,
+ ResourceTable* table, const sp<AaptFile>& file)
+{
+ String8 ext(file->getPath().getPathExtension());
+
+ // At this point, now that we have all the resource data, all we need to
+ // do is compile XML files.
+ if (strcmp(ext.string(), ".xml") == 0) {
+ return compileXmlFile(assets, file, table);
+ }
+
+ return NO_ERROR;
+}
diff --git a/tools/aapt/Images.h b/tools/aapt/Images.h
new file mode 100644
index 0000000..91b6554
--- /dev/null
+++ b/tools/aapt/Images.h
@@ -0,0 +1,26 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef IMAGES_H
+#define IMAGES_H
+
+#include "ResourceTable.h"
+#include "Bundle.h"
+
+#include <utils/String8.h>
+#include <utils/RefBase.h>
+
+using android::String8;
+
+status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
+ const sp<AaptFile>& file, String8* outNewLeafName);
+
+status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest);
+
+status_t postProcessImage(const sp<AaptAssets>& assets,
+ ResourceTable* table, const sp<AaptFile>& file);
+
+#endif
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
new file mode 100644
index 0000000..51a1248
--- /dev/null
+++ b/tools/aapt/Main.cpp
@@ -0,0 +1,659 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Android Asset Packaging Tool main entry point.
+//
+#include "Main.h"
+#include "Bundle.h"
+
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <getopt.h>
+#include <assert.h>
+
+using namespace android;
+
+static const char* gProgName = "aapt";
+
+/*
+ * When running under Cygwin on Windows, this will convert slash-based
+ * paths into back-slash-based ones. Otherwise the ApptAssets file comparisons
+ * fail later as they use back-slash separators under Windows.
+ *
+ * This operates in-place on the path string.
+ */
+void convertPath(char *path) {
+ if (path != NULL && OS_PATH_SEPARATOR != '/') {
+ for (; *path; path++) {
+ if (*path == '/') {
+ *path = OS_PATH_SEPARATOR;
+ }
+ }
+ }
+}
+
+/*
+ * Print usage info.
+ */
+void usage(void)
+{
+ fprintf(stderr, "Android Asset Packaging Tool\n\n");
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr,
+ " %s l[ist] [-v] [-a] file.{zip,jar,apk}\n"
+ " List contents of Zip-compatible archive.\n\n", gProgName);
+ fprintf(stderr,
+ " %s d[ump] [--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]]\n"
+ " strings Print the contents of the resource table string pool in the APK.\n"
+ " badging Print the label and icon for the app declared in APK.\n"
+ " permissions Print the permissions from the APK.\n"
+ " resources Print the resource table from the APK.\n"
+ " configurations Print the configurations in the APK.\n"
+ " xmltree Print the compiled xmls in the given assets.\n"
+ " xmlstrings Print the strings of the given compiled xml assets.\n\n", gProgName);
+ fprintf(stderr,
+ " %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\n"
+ " [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n"
+ " [--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL] \\\n"
+ " [--app-version VAL] [--app-version-name TEXT] [--custom-package VAL] \\\n"
+ " [--rename-manifest-package PACKAGE] \\\n"
+ " [--rename-instrumentation-target-package PACKAGE] \\\n"
+ " [--utf16] [--auto-add-overlay] \\\n"
+ " [--max-res-version VAL] \\\n"
+ " [-I base-package [-I base-package ...]] \\\n"
+ " [-A asset-source-dir] [-G class-list-file] [-P public-definitions-file] \\\n"
+ " [-S resource-sources [-S resource-sources ...]] \\\n"
+ " [-F apk-file] [-J R-file-dir] \\\n"
+ " [--product product1,product2,...] \\\n"
+ " [-c CONFIGS] [--preferred-configurations CONFIGS] \\\n"
+ " [raw-files-dir [raw-files-dir] ...] \\\n"
+ " [--output-text-symbols DIR]\n"
+ "\n"
+ " Package the android resources. It will read assets and resources that are\n"
+ " supplied with the -M -A -S or raw-files-dir arguments. The -J -P -F and -R\n"
+ " options control which files are output.\n\n"
+ , gProgName);
+ fprintf(stderr,
+ " %s r[emove] [-v] file.{zip,jar,apk} file1 [file2 ...]\n"
+ " Delete specified files from Zip-compatible archive.\n\n",
+ gProgName);
+ fprintf(stderr,
+ " %s a[dd] [-v] file.{zip,jar,apk} file1 [file2 ...]\n"
+ " Add specified files to Zip-compatible archive.\n\n", gProgName);
+ fprintf(stderr,
+ " %s c[runch] [-v] -S resource-sources ... -C output-folder ...\n"
+ " Do PNG preprocessing on one or several resource folders\n"
+ " and store the results in the output folder.\n\n", gProgName);
+ fprintf(stderr,
+ " %s s[ingleCrunch] [-v] -i input-file -o outputfile\n"
+ " Do PNG preprocessing on a single file.\n\n", gProgName);
+ fprintf(stderr,
+ " %s v[ersion]\n"
+ " Print program version.\n\n", gProgName);
+ fprintf(stderr,
+ " Modifiers:\n"
+ " -a print Android-specific data (resources, manifest) when listing\n"
+ " -c specify which configurations to include. The default is all\n"
+ " configurations. The value of the parameter should be a comma\n"
+ " separated list of configuration values. Locales should be specified\n"
+ " as either a language or language-region pair. Some examples:\n"
+ " en\n"
+ " port,en\n"
+ " port,land,en_US\n"
+ " If you put the special locale, zz_ZZ on the list, it will perform\n"
+ " pseudolocalization on the default locale, modifying all of the\n"
+ " strings so you can look for strings that missed the\n"
+ " internationalization process. For example:\n"
+ " port,land,zz_ZZ\n"
+ " -d one or more device assets to include, separated by commas\n"
+ " -f force overwrite of existing files\n"
+ " -g specify a pixel tolerance to force images to grayscale, default 0\n"
+ " -j specify a jar or zip file containing classes to include\n"
+ " -k junk path of file(s) added\n"
+ " -m make package directories under location specified by -J\n"
+#if 0
+ " -p pseudolocalize the default configuration\n"
+#endif
+ " -u update existing packages (add new, replace older, remove deleted files)\n"
+ " -v verbose output\n"
+ " -x create extending (non-application) resource IDs\n"
+ " -z require localization of resource attributes marked with\n"
+ " localization=\"suggested\"\n"
+ " -A additional directory in which to find raw asset files\n"
+ " -G A file to output proguard options into.\n"
+ " -F specify the apk file to output\n"
+ " -I add an existing package to base include set\n"
+ " -J specify where to output R.java resource constant definitions\n"
+ " -M specify full path to AndroidManifest.xml to include in zip\n"
+ " -P specify where to output public resource definitions\n"
+ " -S directory in which to find resources. Multiple directories will be scanned\n"
+ " and the first match found (left to right) will take precedence.\n"
+ " -0 specifies an additional extension for which such files will not\n"
+ " be stored compressed in the .apk. An empty string means to not\n"
+ " compress any files at all.\n"
+ " --debug-mode\n"
+ " inserts android:debuggable=\"true\" in to the application node of the\n"
+ " manifest, making the application debuggable even on production devices.\n"
+ " --include-meta-data\n"
+ " when used with \"dump badging\" also includes meta-data tags.\n"
+ " --min-sdk-version\n"
+ " inserts android:minSdkVersion in to manifest. If the version is 7 or\n"
+ " higher, the default encoding for resources will be in UTF-8.\n"
+ " --target-sdk-version\n"
+ " inserts android:targetSdkVersion in to manifest.\n"
+ " --max-res-version\n"
+ " ignores versioned resource directories above the given value.\n"
+ " --values\n"
+ " when used with \"dump resources\" also includes resource values.\n"
+ " --version-code\n"
+ " inserts android:versionCode in to manifest.\n"
+ " --version-name\n"
+ " inserts android:versionName in to manifest.\n"
+ " --custom-package\n"
+ " generates R.java into a different package.\n"
+ " --extra-packages\n"
+ " generate R.java for libraries. Separate libraries with ':'.\n"
+ " --generate-dependencies\n"
+ " generate dependency files in the same directories for R.java and resource package\n"
+ " --auto-add-overlay\n"
+ " Automatically add resources that are only in overlays.\n"
+ " --preferred-configurations\n"
+ " Like the -c option for filtering out unneeded configurations, but\n"
+ " only expresses a preference. If there is no resource available with\n"
+ " the preferred configuration then it will not be stripped.\n"
+ " --rename-manifest-package\n"
+ " Rewrite the manifest so that its package name is the package name\n"
+ " given here. Relative class names (for example .Foo) will be\n"
+ " changed to absolute names with the old package so that the code\n"
+ " does not need to change.\n"
+ " --rename-instrumentation-target-package\n"
+ " Rewrite the manifest so that all of its instrumentation\n"
+ " components target the given package. Useful when used in\n"
+ " conjunction with --rename-manifest-package to fix tests against\n"
+ " a package that has been renamed.\n"
+ " --product\n"
+ " Specifies which variant to choose for strings that have\n"
+ " product variants\n"
+ " --utf16\n"
+ " changes default encoding for resources to UTF-16. Only useful when API\n"
+ " level is set to 7 or higher where the default encoding is UTF-8.\n"
+ " --non-constant-id\n"
+ " Make the resources ID non constant. This is required to make an R java class\n"
+ " that does not contain the final value but is used to make reusable compiled\n"
+ " libraries that need to access resources.\n"
+ " --error-on-failed-insert\n"
+ " Forces aapt to return an error if it fails to insert values into the manifest\n"
+ " with --debug-mode, --min-sdk-version, --target-sdk-version --version-code\n"
+ " and --version-name.\n"
+ " Insertion typically fails if the manifest already defines the attribute.\n"
+ " --error-on-missing-config-entry\n"
+ " Forces aapt to return an error if it fails to find an entry for a configuration.\n"
+ " --output-text-symbols\n"
+ " Generates a text file containing the resource symbols of the R class in the\n"
+ " specified folder.\n"
+ " --ignore-assets\n"
+ " Assets to be ignored. Default pattern is:\n"
+ " %s\n",
+ gDefaultIgnoreAssets);
+}
+
+/*
+ * Dispatch the command.
+ */
+int handleCommand(Bundle* bundle)
+{
+ //printf("--- command %d (verbose=%d force=%d):\n",
+ // bundle->getCommand(), bundle->getVerbose(), bundle->getForce());
+ //for (int i = 0; i < bundle->getFileSpecCount(); i++)
+ // printf(" %d: '%s'\n", i, bundle->getFileSpecEntry(i));
+
+ switch (bundle->getCommand()) {
+ case kCommandVersion: return doVersion(bundle);
+ case kCommandList: return doList(bundle);
+ case kCommandDump: return doDump(bundle);
+ case kCommandAdd: return doAdd(bundle);
+ case kCommandRemove: return doRemove(bundle);
+ case kCommandPackage: return doPackage(bundle);
+ case kCommandCrunch: return doCrunch(bundle);
+ case kCommandSingleCrunch: return doSingleCrunch(bundle);
+ default:
+ fprintf(stderr, "%s: requested command not yet supported\n", gProgName);
+ return 1;
+ }
+}
+
+/*
+ * Parse args.
+ */
+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);
+
+ if (argc < 2) {
+ wantUsage = true;
+ goto bail;
+ }
+
+ if (argv[1][0] == 'v')
+ bundle.setCommand(kCommandVersion);
+ else if (argv[1][0] == 'd')
+ bundle.setCommand(kCommandDump);
+ else if (argv[1][0] == 'l')
+ bundle.setCommand(kCommandList);
+ else if (argv[1][0] == 'a')
+ bundle.setCommand(kCommandAdd);
+ else if (argv[1][0] == 'r')
+ bundle.setCommand(kCommandRemove);
+ else if (argv[1][0] == 'p')
+ bundle.setCommand(kCommandPackage);
+ else if (argv[1][0] == 'c')
+ bundle.setCommand(kCommandCrunch);
+ else if (argv[1][0] == 's')
+ bundle.setCommand(kCommandSingleCrunch);
+ else {
+ fprintf(stderr, "ERROR: Unknown command '%s'\n", argv[1]);
+ wantUsage = true;
+ goto bail;
+ }
+ argc -= 2;
+ argv += 2;
+
+ /*
+ * Pull out flags. We support "-fv" and "-f -v".
+ */
+ while (argc && argv[0][0] == '-') {
+ /* flag(s) found */
+ const char* cp = argv[0] +1;
+
+ while (*cp != '\0') {
+ switch (*cp) {
+ case 'v':
+ bundle.setVerbose(true);
+ break;
+ case 'a':
+ bundle.setAndroidList(true);
+ break;
+ case 'c':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-c' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.addConfigurations(argv[0]);
+ break;
+ case 'f':
+ bundle.setForce(true);
+ break;
+ case 'g':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-g' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ tolerance = atoi(argv[0]);
+ bundle.setGrayscaleTolerance(tolerance);
+ printf("%s: Images with deviation <= %d will be forced to grayscale.\n", prog, tolerance);
+ break;
+ case 'k':
+ bundle.setJunkPath(true);
+ break;
+ case 'm':
+ bundle.setMakePackageDirs(true);
+ break;
+#if 0
+ case 'p':
+ bundle.setPseudolocalize(true);
+ break;
+#endif
+ case 'u':
+ bundle.setUpdate(true);
+ break;
+ case 'x':
+ bundle.setExtending(true);
+ break;
+ case 'z':
+ bundle.setRequireLocalization(true);
+ break;
+ case 'j':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-j' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.addJarFile(argv[0]);
+ break;
+ case 'A':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-A' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setAssetSourceDir(argv[0]);
+ break;
+ case 'G':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-G' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setProguardFile(argv[0]);
+ break;
+ case 'I':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-I' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.addPackageInclude(argv[0]);
+ break;
+ case 'F':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-F' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setOutputAPKFile(argv[0]);
+ break;
+ case 'J':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-J' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setRClassDir(argv[0]);
+ break;
+ case 'M':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-M' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setAndroidManifestFile(argv[0]);
+ break;
+ case 'P':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-P' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setPublicOutputFile(argv[0]);
+ break;
+ case 'S':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-S' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.addResourceSourceDir(argv[0]);
+ break;
+ case 'C':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-C' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setCrunchedOutputDir(argv[0]);
+ break;
+ case 'i':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-i' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setSingleCrunchInputFile(argv[0]);
+ break;
+ case 'o':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-o' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ convertPath(argv[0]);
+ bundle.setSingleCrunchOutputFile(argv[0]);
+ break;
+ case '0':
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-e' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ if (argv[0][0] != 0) {
+ bundle.addNoCompressExtension(argv[0]);
+ } else {
+ bundle.setCompressionMethod(ZipEntry::kCompressStored);
+ }
+ break;
+ case '-':
+ if (strcmp(cp, "-debug-mode") == 0) {
+ bundle.setDebugMode(true);
+ } else if (strcmp(cp, "-min-sdk-version") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--min-sdk-version' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setMinSdkVersion(argv[0]);
+ } else if (strcmp(cp, "-target-sdk-version") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--target-sdk-version' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setTargetSdkVersion(argv[0]);
+ } else if (strcmp(cp, "-max-sdk-version") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--max-sdk-version' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setMaxSdkVersion(argv[0]);
+ } else if (strcmp(cp, "-max-res-version") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--max-res-version' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setMaxResVersion(argv[0]);
+ } else if (strcmp(cp, "-version-code") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--version-code' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setVersionCode(argv[0]);
+ } else if (strcmp(cp, "-version-name") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--version-name' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setVersionName(argv[0]);
+ } else if (strcmp(cp, "-values") == 0) {
+ bundle.setValues(true);
+ } else if (strcmp(cp, "-include-meta-data") == 0) {
+ bundle.setIncludeMetaData(true);
+ } else if (strcmp(cp, "-custom-package") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--custom-package' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setCustomPackage(argv[0]);
+ } else if (strcmp(cp, "-extra-packages") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--extra-packages' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setExtraPackages(argv[0]);
+ } else if (strcmp(cp, "-generate-dependencies") == 0) {
+ bundle.setGenDependencies(true);
+ } else if (strcmp(cp, "-utf16") == 0) {
+ bundle.setWantUTF16(true);
+ } else if (strcmp(cp, "-preferred-configurations") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--preferred-configurations' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.addPreferredConfigurations(argv[0]);
+ } else if (strcmp(cp, "-rename-manifest-package") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--rename-manifest-package' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setManifestPackageNameOverride(argv[0]);
+ } else if (strcmp(cp, "-rename-instrumentation-target-package") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--rename-instrumentation-target-package' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setInstrumentationPackageNameOverride(argv[0]);
+ } else if (strcmp(cp, "-auto-add-overlay") == 0) {
+ bundle.setAutoAddOverlay(true);
+ } else if (strcmp(cp, "-error-on-failed-insert") == 0) {
+ bundle.setErrorOnFailedInsert(true);
+ } else if (strcmp(cp, "-error-on-missing-config-entry") == 0) {
+ bundle.setErrorOnMissingConfigEntry(true);
+ } else if (strcmp(cp, "-output-text-symbols") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '-output-text-symbols' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setOutputTextSymbols(argv[0]);
+ } else if (strcmp(cp, "-product") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--product' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ bundle.setProduct(argv[0]);
+ } else if (strcmp(cp, "-non-constant-id") == 0) {
+ bundle.setNonConstantId(true);
+ } else if (strcmp(cp, "-no-crunch") == 0) {
+ bundle.setUseCrunchCache(true);
+ } else if (strcmp(cp, "-ignore-assets") == 0) {
+ argc--;
+ argv++;
+ if (!argc) {
+ fprintf(stderr, "ERROR: No argument supplied for '--ignore-assets' option\n");
+ wantUsage = true;
+ goto bail;
+ }
+ gUserIgnoreAssets = argv[0];
+ } else {
+ fprintf(stderr, "ERROR: Unknown option '-%s'\n", cp);
+ wantUsage = true;
+ goto bail;
+ }
+ cp += strlen(cp) - 1;
+ break;
+ default:
+ fprintf(stderr, "ERROR: Unknown flag '-%c'\n", *cp);
+ wantUsage = true;
+ goto bail;
+ }
+
+ cp++;
+ }
+ argc--;
+ argv++;
+ }
+
+ /*
+ * We're past the flags. The rest all goes straight in.
+ */
+ bundle.setFileSpec(argv, argc);
+
+ result = handleCommand(&bundle);
+
+bail:
+ if (wantUsage) {
+ usage();
+ result = 2;
+ }
+
+ //printf("--> returning %d\n", result);
+ return result;
+}
diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h
new file mode 100644
index 0000000..a6b39ac
--- /dev/null
+++ b/tools/aapt/Main.h
@@ -0,0 +1,63 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Some global defines that don't really merit their own header.
+//
+#ifndef __MAIN_H
+#define __MAIN_H
+
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+#include "Bundle.h"
+#include "AaptAssets.h"
+#include "ZipFile.h"
+
+
+/* Benchmarking Flag */
+//#define BENCHMARK 1
+
+#if BENCHMARK
+ #include <time.h>
+#endif /* BENCHMARK */
+
+extern int doVersion(Bundle* bundle);
+extern int doList(Bundle* bundle);
+extern int doDump(Bundle* bundle);
+extern int doAdd(Bundle* bundle);
+extern int doRemove(Bundle* bundle);
+extern int doPackage(Bundle* bundle);
+extern int doCrunch(Bundle* bundle);
+extern int doSingleCrunch(Bundle* bundle);
+
+extern int calcPercent(long uncompressedLen, long compressedLen);
+
+extern android::status_t writeAPK(Bundle* bundle,
+ const sp<AaptAssets>& assets,
+ const android::String8& outputFile);
+
+extern android::status_t updatePreProcessedCache(Bundle* bundle);
+
+extern android::status_t buildResources(Bundle* bundle,
+ const sp<AaptAssets>& assets);
+
+extern android::status_t writeResourceSymbols(Bundle* bundle,
+ const sp<AaptAssets>& assets, const String8& pkgName, bool includePrivate);
+
+extern android::status_t writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets);
+
+extern bool isValidResourceType(const String8& type);
+
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets);
+
+extern status_t filterResources(Bundle* bundle, const sp<AaptAssets>& assets);
+
+int dumpResources(Bundle* bundle);
+
+String8 getAttribute(const ResXMLTree& tree, const char* ns,
+ const char* attr, String8* outError);
+
+status_t writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets,
+ FILE* fp, bool includeRaw);
+#endif // __MAIN_H
diff --git a/tools/aapt/NOTICE b/tools/aapt/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/tools/aapt/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
new file mode 100644
index 0000000..872d95c
--- /dev/null
+++ b/tools/aapt/Package.cpp
@@ -0,0 +1,505 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Package assets into Zip files.
+//
+#include "Main.h"
+#include "AaptAssets.h"
+#include "ResourceTable.h"
+#include "ResourceFilter.h"
+
+#include <androidfw/misc.h>
+
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+#include <utils/misc.h>
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <errno.h>
+
+using namespace android;
+
+static const char* kExcludeExtension = ".EXCLUDE";
+
+/* these formats are already compressed, or don't compress well */
+static const char* kNoCompressExt[] = {
+ ".jpg", ".jpeg", ".png", ".gif",
+ ".wav", ".mp2", ".mp3", ".ogg", ".aac",
+ ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
+ ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
+ ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
+ ".amr", ".awb", ".wma", ".wmv"
+};
+
+/* fwd decls, so I can write this downward */
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets);
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir,
+ const AaptGroupEntry& ge, const ResourceFilter* filter);
+bool processFile(Bundle* bundle, ZipFile* zip,
+ const sp<AaptGroup>& group, const sp<AaptFile>& file);
+bool okayToCompress(Bundle* bundle, const String8& pathName);
+ssize_t processJarFiles(Bundle* bundle, ZipFile* zip);
+
+/*
+ * The directory hierarchy looks like this:
+ * "outputDir" and "assetRoot" are existing directories.
+ *
+ * On success, "bundle->numPackages" will be the number of Zip packages
+ * we created.
+ */
+status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets,
+ const String8& outputFile)
+{
+ #if BENCHMARK
+ fprintf(stdout, "BENCHMARK: Starting APK Bundling \n");
+ long startAPKTime = clock();
+ #endif /* BENCHMARK */
+
+ status_t result = NO_ERROR;
+ ZipFile* zip = NULL;
+ int count;
+
+ //bundle->setPackageCount(0);
+
+ /*
+ * Prep the Zip archive.
+ *
+ * If the file already exists, fail unless "update" or "force" is set.
+ * If "update" is set, update the contents of the existing archive.
+ * Else, if "force" is set, remove the existing archive.
+ */
+ FileType fileType = getFileType(outputFile.string());
+ if (fileType == kFileTypeNonexistent) {
+ // okay, create it below
+ } else if (fileType == kFileTypeRegular) {
+ if (bundle->getUpdate()) {
+ // okay, open it below
+ } else if (bundle->getForce()) {
+ if (unlink(outputFile.string()) != 0) {
+ fprintf(stderr, "ERROR: unable to remove '%s': %s\n", outputFile.string(),
+ strerror(errno));
+ goto bail;
+ }
+ } else {
+ fprintf(stderr, "ERROR: '%s' exists (use '-f' to force overwrite)\n",
+ outputFile.string());
+ goto bail;
+ }
+ } else {
+ fprintf(stderr, "ERROR: '%s' exists and is not a regular file\n", outputFile.string());
+ goto bail;
+ }
+
+ if (bundle->getVerbose()) {
+ printf("%s '%s'\n", (fileType == kFileTypeNonexistent) ? "Creating" : "Opening",
+ outputFile.string());
+ }
+
+ status_t status;
+ zip = new ZipFile;
+ status = zip->open(outputFile.string(), ZipFile::kOpenReadWrite | ZipFile::kOpenCreate);
+ if (status != NO_ERROR) {
+ fprintf(stderr, "ERROR: unable to open '%s' as Zip file for writing\n",
+ outputFile.string());
+ goto bail;
+ }
+
+ if (bundle->getVerbose()) {
+ printf("Writing all files...\n");
+ }
+
+ count = processAssets(bundle, zip, assets);
+ if (count < 0) {
+ fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n",
+ outputFile.string());
+ result = count;
+ goto bail;
+ }
+
+ if (bundle->getVerbose()) {
+ printf("Generated %d file%s\n", count, (count==1) ? "" : "s");
+ }
+
+ count = processJarFiles(bundle, zip);
+ if (count < 0) {
+ fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n",
+ outputFile.string());
+ result = count;
+ goto bail;
+ }
+
+ if (bundle->getVerbose())
+ printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s");
+
+ result = NO_ERROR;
+
+ /*
+ * Check for cruft. We set the "marked" flag on all entries we created
+ * or decided not to update. If the entry isn't already slated for
+ * deletion, remove it now.
+ */
+ {
+ if (bundle->getVerbose())
+ printf("Checking for deleted files\n");
+ int i, removed = 0;
+ for (i = 0; i < zip->getNumEntries(); i++) {
+ ZipEntry* entry = zip->getEntryByIndex(i);
+
+ if (!entry->getMarked() && entry->getDeleted()) {
+ if (bundle->getVerbose()) {
+ printf(" (removing crufty '%s')\n",
+ entry->getFileName());
+ }
+ zip->remove(entry);
+ removed++;
+ }
+ }
+ if (bundle->getVerbose() && removed > 0)
+ printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s");
+ }
+
+ /* tell Zip lib to process deletions and other pending changes */
+ result = zip->flush();
+ if (result != NO_ERROR) {
+ fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n");
+ goto bail;
+ }
+
+ /* anything here? */
+ if (zip->getNumEntries() == 0) {
+ if (bundle->getVerbose()) {
+ printf("Archive is empty -- removing %s\n", outputFile.getPathLeaf().string());
+ }
+ delete zip; // close the file so we can remove it in Win32
+ zip = NULL;
+ if (unlink(outputFile.string()) != 0) {
+ fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string());
+ }
+ }
+
+ // If we've been asked to generate a dependency file for the .ap_ package,
+ // do so here
+ if (bundle->getGenDependencies()) {
+ // The dependency file gets output to the same directory
+ // as the specified output file with an additional .d extension.
+ // e.g. bin/resources.ap_.d
+ String8 dependencyFile = outputFile;
+ dependencyFile.append(".d");
+
+ FILE* fp = fopen(dependencyFile.string(), "a");
+ // Add this file to the dependency file
+ fprintf(fp, "%s \\\n", outputFile.string());
+ fclose(fp);
+ }
+
+ assert(result == NO_ERROR);
+
+bail:
+ delete zip; // must close before remove in Win32
+ if (result != NO_ERROR) {
+ if (bundle->getVerbose()) {
+ printf("Removing %s due to earlier failures\n", outputFile.string());
+ }
+ if (unlink(outputFile.string()) != 0) {
+ fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string());
+ }
+ }
+
+ if (result == NO_ERROR && bundle->getVerbose())
+ printf("Done!\n");
+
+ #if BENCHMARK
+ fprintf(stdout, "BENCHMARK: End APK Bundling. Time Elapsed: %f ms \n",(clock() - startAPKTime)/1000.0);
+ #endif /* BENCHMARK */
+ return result;
+}
+
+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];
+
+ ssize_t res = processAssets(bundle, zip, assets, ge, &filter);
+ if (res < 0) {
+ return res;
+ }
+
+ count += res;
+ }
+
+ return count;
+}
+
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir,
+ const AaptGroupEntry& ge, const ResourceFilter* filter)
+{
+ ssize_t count = 0;
+
+ const size_t ND = dir->getDirs().size();
+ size_t i;
+ for (i=0; i<ND; i++) {
+ const sp<AaptDir>& subDir = dir->getDirs().valueAt(i);
+
+ const bool filterable = filter != NULL && subDir->getLeaf().find("mipmap-") != 0;
+
+ if (filterable && subDir->getLeaf() != subDir->getPath() && !filter->match(ge.toParams())) {
+ continue;
+ }
+
+ ssize_t res = processAssets(bundle, zip, subDir, ge, filterable ? filter : NULL);
+ if (res < 0) {
+ return res;
+ }
+ count += res;
+ }
+
+ if (filter != NULL && !filter->match(ge.toParams())) {
+ return count;
+ }
+
+ const size_t NF = dir->getFiles().size();
+ for (i=0; i<NF; i++) {
+ sp<AaptGroup> gp = dir->getFiles().valueAt(i);
+ ssize_t fi = gp->getFiles().indexOfKey(ge);
+ if (fi >= 0) {
+ sp<AaptFile> fl = gp->getFiles().valueAt(fi);
+ if (!processFile(bundle, zip, gp, fl)) {
+ return UNKNOWN_ERROR;
+ }
+ count++;
+ }
+ }
+
+ return count;
+}
+
+/*
+ * Process a regular file, adding it to the archive if appropriate.
+ *
+ * If we're in "update" mode, and the file already exists in the archive,
+ * delete the existing entry before adding the new one.
+ */
+bool processFile(Bundle* bundle, ZipFile* zip,
+ const sp<AaptGroup>& group, const sp<AaptFile>& file)
+{
+ const bool hasData = file->hasData();
+
+ String8 storageName(group->getPath());
+ storageName.convertToResPath();
+ ZipEntry* entry;
+ bool fromGzip = false;
+ status_t result;
+
+ /*
+ * See if the filename ends in ".EXCLUDE". We can't use
+ * String8::getPathExtension() because the length of what it considers
+ * to be an extension is capped.
+ *
+ * The Asset Manager doesn't check for ".EXCLUDE" in Zip archives,
+ * so there's no value in adding them (and it makes life easier on
+ * the AssetManager lib if we don't).
+ *
+ * NOTE: this restriction has been removed. If you're in this code, you
+ * should clean this up, but I'm in here getting rid of Path Name, and I
+ * don't want to make other potentially breaking changes --joeo
+ */
+ int fileNameLen = storageName.length();
+ int excludeExtensionLen = strlen(kExcludeExtension);
+ if (fileNameLen > excludeExtensionLen
+ && (0 == strcmp(storageName.string() + (fileNameLen - excludeExtensionLen),
+ kExcludeExtension))) {
+ fprintf(stderr, "warning: '%s' not added to Zip\n", storageName.string());
+ return true;
+ }
+
+ if (strcasecmp(storageName.getPathExtension().string(), ".gz") == 0) {
+ fromGzip = true;
+ storageName = storageName.getBasePath();
+ }
+
+ if (bundle->getUpdate()) {
+ entry = zip->getEntryByName(storageName.string());
+ if (entry != NULL) {
+ /* file already exists in archive; there can be only one */
+ if (entry->getMarked()) {
+ fprintf(stderr,
+ "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n",
+ file->getPrintableSource().string());
+ return false;
+ }
+ if (!hasData) {
+ const String8& srcName = file->getSourceFile();
+ time_t fileModWhen;
+ fileModWhen = getFileModDate(srcName.string());
+ if (fileModWhen == (time_t) -1) { // file existence tested earlier,
+ return false; // not expecting an error here
+ }
+
+ if (fileModWhen > entry->getModWhen()) {
+ // mark as deleted so add() will succeed
+ if (bundle->getVerbose()) {
+ printf(" (removing old '%s')\n", storageName.string());
+ }
+
+ zip->remove(entry);
+ } else {
+ // version in archive is newer
+ if (bundle->getVerbose()) {
+ printf(" (not updating '%s')\n", storageName.string());
+ }
+ entry->setMarked(true);
+ return true;
+ }
+ } else {
+ // Generated files are always replaced.
+ zip->remove(entry);
+ }
+ }
+ }
+
+ //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE);
+
+ if (fromGzip) {
+ result = zip->addGzip(file->getSourceFile().string(), storageName.string(), &entry);
+ } else if (!hasData) {
+ /* don't compress certain files, e.g. PNGs */
+ int compressionMethod = bundle->getCompressionMethod();
+ if (!okayToCompress(bundle, storageName)) {
+ compressionMethod = ZipEntry::kCompressStored;
+ }
+ result = zip->add(file->getSourceFile().string(), storageName.string(), compressionMethod,
+ &entry);
+ } else {
+ result = zip->add(file->getData(), file->getSize(), storageName.string(),
+ file->getCompressionMethod(), &entry);
+ }
+ if (result == NO_ERROR) {
+ if (bundle->getVerbose()) {
+ printf(" '%s'%s", storageName.string(), fromGzip ? " (from .gz)" : "");
+ if (entry->getCompressionMethod() == ZipEntry::kCompressStored) {
+ printf(" (not compressed)\n");
+ } else {
+ printf(" (compressed %d%%)\n", calcPercent(entry->getUncompressedLen(),
+ entry->getCompressedLen()));
+ }
+ }
+ entry->setMarked(true);
+ } else {
+ if (result == ALREADY_EXISTS) {
+ fprintf(stderr, " Unable to add '%s': file already in archive (try '-u'?)\n",
+ file->getPrintableSource().string());
+ } else {
+ fprintf(stderr, " Unable to add '%s': Zip add failed\n",
+ file->getPrintableSource().string());
+ }
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Determine whether or not we want to try to compress this file based
+ * on the file extension.
+ */
+bool okayToCompress(Bundle* bundle, const String8& pathName)
+{
+ String8 ext = pathName.getPathExtension();
+ int i;
+
+ if (ext.length() == 0)
+ return true;
+
+ for (i = 0; i < NELEM(kNoCompressExt); i++) {
+ if (strcasecmp(ext.string(), kNoCompressExt[i]) == 0)
+ return false;
+ }
+
+ const android::Vector<const char*>& others(bundle->getNoCompressExtensions());
+ for (i = 0; i < (int)others.size(); i++) {
+ const char* str = others[i];
+ int pos = pathName.length() - strlen(str);
+ if (pos < 0) {
+ continue;
+ }
+ const char* path = pathName.string();
+ if (strcasecmp(path + pos, str) == 0) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool endsWith(const char* haystack, const char* needle)
+{
+ size_t a = strlen(haystack);
+ size_t b = strlen(needle);
+ if (a < b) return false;
+ return strcasecmp(haystack+(a-b), needle) == 0;
+}
+
+ssize_t processJarFile(ZipFile* jar, ZipFile* out)
+{
+ status_t err;
+ size_t N = jar->getNumEntries();
+ size_t count = 0;
+ for (size_t i=0; i<N; i++) {
+ ZipEntry* entry = jar->getEntryByIndex(i);
+ const char* storageName = entry->getFileName();
+ if (endsWith(storageName, ".class")) {
+ int compressionMethod = entry->getCompressionMethod();
+ size_t size = entry->getUncompressedLen();
+ const void* data = jar->uncompress(entry);
+ if (data == NULL) {
+ fprintf(stderr, "ERROR: unable to uncompress entry '%s'\n",
+ storageName);
+ return -1;
+ }
+ out->add(data, size, storageName, compressionMethod, NULL);
+ free((void*)data);
+ }
+ count++;
+ }
+ return count;
+}
+
+ssize_t processJarFiles(Bundle* bundle, ZipFile* zip)
+{
+ status_t err;
+ ssize_t count = 0;
+ const android::Vector<const char*>& jars = bundle->getJarFiles();
+
+ size_t N = jars.size();
+ for (size_t i=0; i<N; i++) {
+ ZipFile jar;
+ err = jar.open(jars[i], ZipFile::kOpenReadOnly);
+ if (err != 0) {
+ fprintf(stderr, "ERROR: unable to open '%s' as a zip file: %d\n",
+ jars[i], err);
+ return err;
+ }
+ err += processJarFile(&jar, zip);
+ if (err < 0) {
+ fprintf(stderr, "ERROR: unable to process '%s'\n", jars[i]);
+ return err;
+ }
+ count += err;
+ }
+
+ return count;
+}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
new file mode 100644
index 0000000..8f43661
--- /dev/null
+++ b/tools/aapt/Resource.cpp
@@ -0,0 +1,2651 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+#include "Main.h"
+#include "AaptAssets.h"
+#include "StringPool.h"
+#include "XMLNode.h"
+#include "ResourceTable.h"
+#include "Images.h"
+
+#include "CrunchCache.h"
+#include "FileFinder.h"
+#include "CacheUpdater.h"
+
+#include "WorkQueue.h"
+
+#if HAVE_PRINTF_ZD
+# define ZD "%zd"
+# define ZD_TYPE ssize_t
+#else
+# define ZD "%ld"
+# define ZD_TYPE long
+#endif
+
+#define NOISY(x) // x
+
+// Number of threads to use for preprocessing images.
+static const size_t MAX_THREADS = 4;
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+class PackageInfo
+{
+public:
+ PackageInfo()
+ {
+ }
+ ~PackageInfo()
+ {
+ }
+
+ status_t parsePackage(const sp<AaptGroup>& grp);
+};
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+static String8 parseResourceName(const String8& leaf)
+{
+ const char* firstDot = strchr(leaf.string(), '.');
+ const char* str = leaf.string();
+
+ if (firstDot) {
+ return String8(str, firstDot-str);
+ } else {
+ return String8(str);
+ }
+}
+
+ResourceTypeSet::ResourceTypeSet()
+ :RefBase(),
+ KeyedVector<String8,sp<AaptGroup> >()
+{
+}
+
+FilePathStore::FilePathStore()
+ :RefBase(),
+ Vector<String8>()
+{
+}
+
+class ResourceDirIterator
+{
+public:
+ ResourceDirIterator(const sp<ResourceTypeSet>& set, const String8& resType)
+ : mResType(resType), mSet(set), mSetPos(0), mGroupPos(0)
+ {
+ }
+
+ inline const sp<AaptGroup>& getGroup() const { return mGroup; }
+ inline const sp<AaptFile>& getFile() const { return mFile; }
+
+ inline const String8& getBaseName() const { return mBaseName; }
+ inline const String8& getLeafName() const { return mLeafName; }
+ inline String8 getPath() const { return mPath; }
+ inline const ResTable_config& getParams() const { return mParams; }
+
+ enum {
+ EOD = 1
+ };
+
+ ssize_t next()
+ {
+ while (true) {
+ sp<AaptGroup> group;
+ sp<AaptFile> file;
+
+ // Try to get next file in this current group.
+ if (mGroup != NULL && mGroupPos < mGroup->getFiles().size()) {
+ group = mGroup;
+ file = group->getFiles().valueAt(mGroupPos++);
+
+ // Try to get the next group/file in this directory
+ } else if (mSetPos < mSet->size()) {
+ mGroup = group = mSet->valueAt(mSetPos++);
+ if (group->getFiles().size() < 1) {
+ continue;
+ }
+ file = group->getFiles().valueAt(0);
+ mGroupPos = 1;
+
+ // All done!
+ } else {
+ return EOD;
+ }
+
+ mFile = file;
+
+ String8 leaf(group->getLeaf());
+ mLeafName = String8(leaf);
+ mParams = file->getGroupEntry().toParams();
+ NOISY(printf("Dir %s: mcc=%d mnc=%d lang=%c%c cnt=%c%c orient=%d ui=%d density=%d touch=%d key=%d inp=%d nav=%d\n",
+ group->getPath().string(), mParams.mcc, mParams.mnc,
+ mParams.language[0] ? mParams.language[0] : '-',
+ mParams.language[1] ? mParams.language[1] : '-',
+ mParams.country[0] ? mParams.country[0] : '-',
+ mParams.country[1] ? mParams.country[1] : '-',
+ mParams.orientation, mParams.uiMode,
+ mParams.density, mParams.touchscreen, mParams.keyboard,
+ mParams.inputFlags, mParams.navigation));
+ mPath = "res";
+ mPath.appendPath(file->getGroupEntry().toDirName(mResType));
+ mPath.appendPath(leaf);
+ mBaseName = parseResourceName(leaf);
+ if (mBaseName == "") {
+ fprintf(stderr, "Error: malformed resource filename %s\n",
+ file->getPrintableSource().string());
+ return UNKNOWN_ERROR;
+ }
+
+ NOISY(printf("file name=%s\n", mBaseName.string()));
+
+ return NO_ERROR;
+ }
+ }
+
+private:
+ String8 mResType;
+
+ const sp<ResourceTypeSet> mSet;
+ size_t mSetPos;
+
+ sp<AaptGroup> mGroup;
+ size_t mGroupPos;
+
+ sp<AaptFile> mFile;
+ String8 mBaseName;
+ String8 mLeafName;
+ String8 mPath;
+ ResTable_config mParams;
+};
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+bool isValidResourceType(const String8& type)
+{
+ return type == "anim" || type == "animator" || type == "interpolator"
+ || type == "transition"
+ || type == "drawable" || type == "layout"
+ || type == "values" || type == "xml" || type == "raw"
+ || type == "color" || type == "menu" || type == "mipmap";
+}
+
+static sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true)
+{
+ sp<AaptGroup> group = assets->getFiles().valueFor(String8("resources.arsc"));
+ sp<AaptFile> file;
+ if (group != NULL) {
+ file = group->getFiles().valueFor(AaptGroupEntry());
+ if (file != NULL) {
+ return file;
+ }
+ }
+
+ if (!makeIfNecessary) {
+ return NULL;
+ }
+ return assets->addFile(String8("resources.arsc"), AaptGroupEntry(), String8(),
+ NULL, String8());
+}
+
+static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets,
+ const sp<AaptGroup>& grp)
+{
+ if (grp->getFiles().size() != 1) {
+ fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
+ grp->getFiles().valueAt(0)->getPrintableSource().string());
+ }
+
+ sp<AaptFile> file = grp->getFiles().valueAt(0);
+
+ ResXMLTree block;
+ status_t err = parseXMLResource(file, &block);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ //printXMLBlock(&block);
+
+ ResXMLTree::event_code_t code;
+ while ((code=block.next()) != ResXMLTree::START_TAG
+ && code != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+ }
+
+ size_t len;
+ if (code != ResXMLTree::START_TAG) {
+ fprintf(stderr, "%s:%d: No start tag found\n",
+ file->getPrintableSource().string(), block.getLineNumber());
+ return UNKNOWN_ERROR;
+ }
+ if (strcmp16(block.getElementName(&len), String16("manifest").string()) != 0) {
+ fprintf(stderr, "%s:%d: Invalid start tag %s, expected <manifest>\n",
+ file->getPrintableSource().string(), block.getLineNumber(),
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+
+ ssize_t nameIndex = block.indexOfAttribute(NULL, "package");
+ if (nameIndex < 0) {
+ fprintf(stderr, "%s:%d: <manifest> does not have package attribute.\n",
+ file->getPrintableSource().string(), block.getLineNumber());
+ return UNKNOWN_ERROR;
+ }
+
+ assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len)));
+
+ String16 uses_sdk16("uses-sdk");
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ if (strcmp16(block.getElementName(&len), uses_sdk16.string()) == 0) {
+ ssize_t minSdkIndex = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE,
+ "minSdkVersion");
+ if (minSdkIndex >= 0) {
+ const uint16_t* minSdk16 = block.getAttributeStringValue(minSdkIndex, &len);
+ const char* minSdk8 = strdup(String8(minSdk16).string());
+ bundle->setManifestMinSdkVersion(minSdk8);
+ }
+ }
+ }
+ }
+
+ return NO_ERROR;
+}
+
+// ==========================================================================
+// ==========================================================================
+// ==========================================================================
+
+static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets,
+ ResourceTable* table,
+ const sp<ResourceTypeSet>& set,
+ const char* resType)
+{
+ String8 type8(resType);
+ String16 type16(resType);
+
+ bool hasErrors = false;
+
+ ResourceDirIterator it(set, String8(resType));
+ ssize_t res;
+ while ((res=it.next()) == NO_ERROR) {
+ if (bundle->getVerbose()) {
+ printf(" (new resource id %s from %s)\n",
+ it.getBaseName().string(), it.getFile()->getPrintableSource().string());
+ }
+ String16 baseName(it.getBaseName());
+ const char16_t* str = baseName.string();
+ const char16_t* const end = str + baseName.size();
+ while (str < end) {
+ if (!((*str >= 'a' && *str <= 'z')
+ || (*str >= '0' && *str <= '9')
+ || *str == '_' || *str == '.')) {
+ fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n",
+ it.getPath().string());
+ hasErrors = true;
+ }
+ str++;
+ }
+ String8 resPath = it.getPath();
+ resPath.convertToResPath();
+ table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()),
+ type16,
+ baseName,
+ String16(resPath),
+ NULL,
+ &it.getParams());
+ assets->addResource(it.getLeafName(), resPath, it.getFile(), type8);
+ }
+
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+class PreProcessImageWorkUnit : public WorkQueue::WorkUnit {
+public:
+ PreProcessImageWorkUnit(const Bundle* bundle, const sp<AaptAssets>& assets,
+ const sp<AaptFile>& file, volatile bool* hasErrors) :
+ mBundle(bundle), mAssets(assets), mFile(file), mHasErrors(hasErrors) {
+ }
+
+ virtual bool run() {
+ status_t status = preProcessImage(mBundle, mAssets, mFile, NULL);
+ if (status) {
+ *mHasErrors = true;
+ }
+ return true; // continue even if there are errors
+ }
+
+private:
+ const Bundle* mBundle;
+ sp<AaptAssets> mAssets;
+ sp<AaptFile> mFile;
+ volatile bool* mHasErrors;
+};
+
+static status_t preProcessImages(const Bundle* bundle, const sp<AaptAssets>& assets,
+ const sp<ResourceTypeSet>& set, const char* type)
+{
+ volatile bool hasErrors = false;
+ ssize_t res = NO_ERROR;
+ if (bundle->getUseCrunchCache() == false) {
+ WorkQueue wq(MAX_THREADS, false);
+ ResourceDirIterator it(set, String8(type));
+ while ((res=it.next()) == NO_ERROR) {
+ PreProcessImageWorkUnit* w = new PreProcessImageWorkUnit(
+ bundle, assets, it.getFile(), &hasErrors);
+ status_t status = wq.schedule(w);
+ if (status) {
+ fprintf(stderr, "preProcessImages failed: schedule() returned %d\n", status);
+ hasErrors = true;
+ delete w;
+ break;
+ }
+ }
+ status_t status = wq.finish();
+ if (status) {
+ fprintf(stderr, "preProcessImages failed: finish() returned %d\n", status);
+ hasErrors = true;
+ }
+ }
+ return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+status_t postProcessImages(const sp<AaptAssets>& assets,
+ ResourceTable* table,
+ const sp<ResourceTypeSet>& set)
+{
+ ResourceDirIterator it(set, String8("drawable"));
+ bool hasErrors = false;
+ ssize_t res;
+ while ((res=it.next()) == NO_ERROR) {
+ res = postProcessImage(assets, table, it.getFile());
+ if (res < NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+static void collect_files(const sp<AaptDir>& dir,
+ KeyedVector<String8, sp<ResourceTypeSet> >* resources)
+{
+ const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
+ int N = groups.size();
+ for (int i=0; i<N; i++) {
+ String8 leafName = groups.keyAt(i);
+ const sp<AaptGroup>& group = groups.valueAt(i);
+
+ const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
+ = group->getFiles();
+
+ if (files.size() == 0) {
+ continue;
+ }
+
+ String8 resType = files.valueAt(0)->getResourceType();
+
+ ssize_t index = resources->indexOfKey(resType);
+
+ if (index < 0) {
+ sp<ResourceTypeSet> set = new ResourceTypeSet();
+ NOISY(printf("Creating new resource type set for leaf %s with group %s (%p)\n",
+ leafName.string(), group->getPath().string(), group.get()));
+ set->add(leafName, group);
+ resources->add(resType, set);
+ } else {
+ sp<ResourceTypeSet> set = resources->valueAt(index);
+ index = set->indexOfKey(leafName);
+ if (index < 0) {
+ NOISY(printf("Adding to resource type set for leaf %s group %s (%p)\n",
+ leafName.string(), group->getPath().string(), group.get()));
+ set->add(leafName, group);
+ } else {
+ sp<AaptGroup> existingGroup = set->valueAt(index);
+ NOISY(printf("Extending to resource type set for leaf %s group %s (%p)\n",
+ leafName.string(), group->getPath().string(), group.get()));
+ for (size_t j=0; j<files.size(); j++) {
+ NOISY(printf("Adding file %s in group %s resType %s\n",
+ files.valueAt(j)->getSourceFile().string(),
+ files.keyAt(j).toDirName(String8()).string(),
+ resType.string()));
+ status_t err = existingGroup->addFile(files.valueAt(j));
+ }
+ }
+ }
+ }
+}
+
+static void collect_files(const sp<AaptAssets>& ass,
+ KeyedVector<String8, sp<ResourceTypeSet> >* resources)
+{
+ const Vector<sp<AaptDir> >& dirs = ass->resDirs();
+ int N = dirs.size();
+
+ for (int i=0; i<N; i++) {
+ sp<AaptDir> d = dirs.itemAt(i);
+ NOISY(printf("Collecting dir #%d %p: %s, leaf %s\n", i, d.get(), d->getPath().string(),
+ d->getLeaf().string()));
+ collect_files(d, resources);
+
+ // don't try to include the res dir
+ NOISY(printf("Removing dir leaf %s\n", d->getLeaf().string()));
+ ass->removeDir(d->getLeaf());
+ }
+}
+
+enum {
+ ATTR_OKAY = -1,
+ ATTR_NOT_FOUND = -2,
+ ATTR_LEADING_SPACES = -3,
+ ATTR_TRAILING_SPACES = -4
+};
+static int validateAttr(const String8& path, const ResTable& table,
+ const ResXMLParser& parser,
+ const char* ns, const char* attr, const char* validChars, bool required)
+{
+ size_t len;
+
+ ssize_t index = parser.indexOfAttribute(ns, attr);
+ const uint16_t* str;
+ Res_value value;
+ if (index >= 0 && parser.getAttributeValue(index, &value) >= 0) {
+ const ResStringPool* pool = &parser.getStrings();
+ if (value.dataType == Res_value::TYPE_REFERENCE) {
+ uint32_t specFlags = 0;
+ int strIdx;
+ if ((strIdx=table.resolveReference(&value, 0x10000000, NULL, &specFlags)) < 0) {
+ fprintf(stderr, "%s:%d: Tag <%s> attribute %s references unknown resid 0x%08x.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr,
+ value.data);
+ return ATTR_NOT_FOUND;
+ }
+
+ pool = table.getTableStringBlock(strIdx);
+ #if 0
+ if (pool != NULL) {
+ str = pool->stringAt(value.data, &len);
+ }
+ printf("***** RES ATTR: %s specFlags=0x%x strIdx=%d: %s\n", attr,
+ specFlags, strIdx, str != NULL ? String8(str).string() : "???");
+ #endif
+ if ((specFlags&~ResTable_typeSpec::SPEC_PUBLIC) != 0 && false) {
+ fprintf(stderr, "%s:%d: Tag <%s> attribute %s varies by configurations 0x%x.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr,
+ specFlags);
+ return ATTR_NOT_FOUND;
+ }
+ }
+ if (value.dataType == Res_value::TYPE_STRING) {
+ if (pool == NULL) {
+ fprintf(stderr, "%s:%d: Tag <%s> attribute %s has no string block.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr);
+ return ATTR_NOT_FOUND;
+ }
+ if ((str=pool->stringAt(value.data, &len)) == NULL) {
+ fprintf(stderr, "%s:%d: Tag <%s> attribute %s has corrupt string value.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr);
+ return ATTR_NOT_FOUND;
+ }
+ } else {
+ fprintf(stderr, "%s:%d: Tag <%s> attribute %s has invalid type %d.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr,
+ value.dataType);
+ return ATTR_NOT_FOUND;
+ }
+ if (validChars) {
+ for (size_t i=0; i<len; i++) {
+ uint16_t c = str[i];
+ const char* p = validChars;
+ bool okay = false;
+ while (*p) {
+ if (c == *p) {
+ okay = true;
+ break;
+ }
+ p++;
+ }
+ if (!okay) {
+ fprintf(stderr, "%s:%d: Tag <%s> attribute %s has invalid character '%c'.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr, (char)str[i]);
+ return (int)i;
+ }
+ }
+ }
+ if (*str == ' ') {
+ fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not start with a space.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr);
+ return ATTR_LEADING_SPACES;
+ }
+ if (str[len-1] == ' ') {
+ fprintf(stderr, "%s:%d: Tag <%s> attribute %s can not end with a space.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr);
+ return ATTR_TRAILING_SPACES;
+ }
+ return ATTR_OKAY;
+ }
+ if (required) {
+ fprintf(stderr, "%s:%d: Tag <%s> missing required attribute %s.\n",
+ path.string(), parser.getLineNumber(),
+ String8(parser.getElementName(&len)).string(), attr);
+ return ATTR_NOT_FOUND;
+ }
+ return ATTR_OKAY;
+}
+
+static void checkForIds(const String8& path, ResXMLParser& parser)
+{
+ ResXMLTree::event_code_t code;
+ while ((code=parser.next()) != ResXMLTree::END_DOCUMENT
+ && code > ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ ssize_t index = parser.indexOfAttribute(NULL, "id");
+ if (index >= 0) {
+ fprintf(stderr, "%s:%d: warning: found plain 'id' attribute; did you mean the new 'android:id' name?\n",
+ path.string(), parser.getLineNumber());
+ }
+ }
+ }
+}
+
+static bool applyFileOverlay(Bundle *bundle,
+ const sp<AaptAssets>& assets,
+ sp<ResourceTypeSet> *baseSet,
+ const char *resType)
+{
+ if (bundle->getVerbose()) {
+ printf("applyFileOverlay for %s\n", resType);
+ }
+
+ // Replace any base level files in this category with any found from the overlay
+ // Also add any found only in the overlay.
+ sp<AaptAssets> overlay = assets->getOverlay();
+ String8 resTypeString(resType);
+
+ // work through the linked list of overlays
+ while (overlay.get()) {
+ KeyedVector<String8, sp<ResourceTypeSet> >* overlayRes = overlay->getResources();
+
+ // get the overlay resources of the requested type
+ ssize_t index = overlayRes->indexOfKey(resTypeString);
+ if (index >= 0) {
+ sp<ResourceTypeSet> overlaySet = overlayRes->valueAt(index);
+
+ // for each of the resources, check for a match in the previously built
+ // non-overlay "baseset".
+ size_t overlayCount = overlaySet->size();
+ for (size_t overlayIndex=0; overlayIndex<overlayCount; overlayIndex++) {
+ if (bundle->getVerbose()) {
+ printf("trying overlaySet Key=%s\n",overlaySet->keyAt(overlayIndex).string());
+ }
+ size_t baseIndex = UNKNOWN_ERROR;
+ if (baseSet->get() != NULL) {
+ baseIndex = (*baseSet)->indexOfKey(overlaySet->keyAt(overlayIndex));
+ }
+ if (baseIndex < UNKNOWN_ERROR) {
+ // look for same flavor. For a given file (strings.xml, for example)
+ // there may be a locale specific or other flavors - we want to match
+ // the same flavor.
+ sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
+ sp<AaptGroup> baseGroup = (*baseSet)->valueAt(baseIndex);
+
+ DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
+ overlayGroup->getFiles();
+ if (bundle->getVerbose()) {
+ DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > baseFiles =
+ baseGroup->getFiles();
+ for (size_t i=0; i < baseFiles.size(); i++) {
+ printf("baseFile " ZD " has flavor %s\n", (ZD_TYPE) i,
+ baseFiles.keyAt(i).toString().string());
+ }
+ for (size_t i=0; i < overlayFiles.size(); i++) {
+ printf("overlayFile " ZD " has flavor %s\n", (ZD_TYPE) i,
+ overlayFiles.keyAt(i).toString().string());
+ }
+ }
+
+ size_t overlayGroupSize = overlayFiles.size();
+ for (size_t overlayGroupIndex = 0;
+ overlayGroupIndex<overlayGroupSize;
+ overlayGroupIndex++) {
+ size_t baseFileIndex =
+ baseGroup->getFiles().indexOfKey(overlayFiles.
+ keyAt(overlayGroupIndex));
+ if (baseFileIndex < UNKNOWN_ERROR) {
+ if (bundle->getVerbose()) {
+ printf("found a match (" ZD ") for overlay file %s, for flavor %s\n",
+ (ZD_TYPE) baseFileIndex,
+ overlayGroup->getLeaf().string(),
+ overlayFiles.keyAt(overlayGroupIndex).toString().string());
+ }
+ baseGroup->removeFile(baseFileIndex);
+ } else {
+ // didn't find a match fall through and add it..
+ if (true || bundle->getVerbose()) {
+ printf("nothing matches overlay file %s, for flavor %s\n",
+ overlayGroup->getLeaf().string(),
+ overlayFiles.keyAt(overlayGroupIndex).toString().string());
+ }
+ }
+ baseGroup->addFile(overlayFiles.valueAt(overlayGroupIndex));
+ assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex));
+ }
+ } else {
+ if (baseSet->get() == NULL) {
+ *baseSet = new ResourceTypeSet();
+ assets->getResources()->add(String8(resType), *baseSet);
+ }
+ // this group doesn't exist (a file that's only in the overlay)
+ (*baseSet)->add(overlaySet->keyAt(overlayIndex),
+ overlaySet->valueAt(overlayIndex));
+ // make sure all flavors are defined in the resources.
+ sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
+ DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
+ overlayGroup->getFiles();
+ size_t overlayGroupSize = overlayFiles.size();
+ for (size_t overlayGroupIndex = 0;
+ overlayGroupIndex<overlayGroupSize;
+ overlayGroupIndex++) {
+ assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex));
+ }
+ }
+ }
+ // this overlay didn't have resources for this type
+ }
+ // try next overlay
+ overlay = overlay->getOverlay();
+ }
+ return true;
+}
+
+/*
+ * Inserts an attribute in a given node, only if the attribute does not
+ * exist.
+ * If errorOnFailedInsert is true, and the attribute already exists, returns false.
+ * Returns true otherwise, even if the attribute already exists.
+ */
+bool addTagAttribute(const sp<XMLNode>& node, const char* ns8,
+ const char* attr8, const char* value, bool errorOnFailedInsert)
+{
+ if (value == NULL) {
+ return true;
+ }
+
+ const String16 ns(ns8);
+ const String16 attr(attr8);
+
+ if (node->getAttribute(ns, attr) != NULL) {
+ if (errorOnFailedInsert) {
+ fprintf(stderr, "Error: AndroidManifest.xml already defines %s (in %s);"
+ " cannot insert new value %s.\n",
+ String8(attr).string(), String8(ns).string(), value);
+ return false;
+ }
+
+ fprintf(stderr, "Warning: AndroidManifest.xml already defines %s (in %s);"
+ " using existing value in manifest.\n",
+ String8(attr).string(), String8(ns).string());
+
+ // don't stop the build.
+ return true;
+ }
+
+ node->addAttribute(ns, attr, String16(value));
+ return true;
+}
+
+static void fullyQualifyClassName(const String8& package, sp<XMLNode> node,
+ const String16& attrName) {
+ XMLNode::attribute_entry* attr = node->editAttribute(
+ String16("http://schemas.android.com/apk/res/android"), attrName);
+ if (attr != NULL) {
+ String8 name(attr->string);
+
+ // asdf --> package.asdf
+ // .asdf .a.b --> package.asdf package.a.b
+ // asdf.adsf --> asdf.asdf
+ String8 className;
+ const char* p = name.string();
+ const char* q = strchr(p, '.');
+ if (p == q) {
+ className += package;
+ className += name;
+ } else if (q == NULL) {
+ className += package;
+ className += ".";
+ className += name;
+ } else {
+ className += name;
+ }
+ NOISY(printf("Qualifying class '%s' to '%s'", name.string(), className.string()));
+ attr->string.setTo(String16(className));
+ }
+}
+
+status_t massageManifest(Bundle* bundle, sp<XMLNode> root)
+{
+ root = root->searchElement(String16(), String16("manifest"));
+ if (root == NULL) {
+ fprintf(stderr, "No <manifest> tag.\n");
+ return UNKNOWN_ERROR;
+ }
+
+ bool errorOnFailedInsert = bundle->getErrorOnFailedInsert();
+
+ if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionCode",
+ bundle->getVersionCode(), errorOnFailedInsert)) {
+ return UNKNOWN_ERROR;
+ }
+ if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionName",
+ bundle->getVersionName(), errorOnFailedInsert)) {
+ return UNKNOWN_ERROR;
+ }
+
+ if (bundle->getMinSdkVersion() != NULL
+ || bundle->getTargetSdkVersion() != NULL
+ || bundle->getMaxSdkVersion() != NULL) {
+ sp<XMLNode> vers = root->getChildElement(String16(), String16("uses-sdk"));
+ if (vers == NULL) {
+ vers = XMLNode::newElement(root->getFilename(), String16(), String16("uses-sdk"));
+ root->insertChildAt(vers, 0);
+ }
+
+ if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "minSdkVersion",
+ bundle->getMinSdkVersion(), errorOnFailedInsert)) {
+ return UNKNOWN_ERROR;
+ }
+ if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "targetSdkVersion",
+ bundle->getTargetSdkVersion(), errorOnFailedInsert)) {
+ return UNKNOWN_ERROR;
+ }
+ if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "maxSdkVersion",
+ bundle->getMaxSdkVersion(), errorOnFailedInsert)) {
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ if (bundle->getDebugMode()) {
+ sp<XMLNode> application = root->getChildElement(String16(), String16("application"));
+ if (application != NULL) {
+ if (!addTagAttribute(application, RESOURCES_ANDROID_NAMESPACE, "debuggable", "true",
+ errorOnFailedInsert)) {
+ return UNKNOWN_ERROR;
+ }
+ }
+ }
+
+ // Deal with manifest package name overrides
+ const char* manifestPackageNameOverride = bundle->getManifestPackageNameOverride();
+ if (manifestPackageNameOverride != NULL) {
+ // Update the actual package name
+ XMLNode::attribute_entry* attr = root->editAttribute(String16(), String16("package"));
+ if (attr == NULL) {
+ fprintf(stderr, "package name is required with --rename-manifest-package.\n");
+ return UNKNOWN_ERROR;
+ }
+ String8 origPackage(attr->string);
+ attr->string.setTo(String16(manifestPackageNameOverride));
+ NOISY(printf("Overriding package '%s' to be '%s'\n", origPackage.string(), manifestPackageNameOverride));
+
+ // Make class names fully qualified
+ sp<XMLNode> application = root->getChildElement(String16(), String16("application"));
+ if (application != NULL) {
+ fullyQualifyClassName(origPackage, application, String16("name"));
+ fullyQualifyClassName(origPackage, application, String16("backupAgent"));
+
+ Vector<sp<XMLNode> >& children = const_cast<Vector<sp<XMLNode> >&>(application->getChildren());
+ for (size_t i = 0; i < children.size(); i++) {
+ sp<XMLNode> child = children.editItemAt(i);
+ String8 tag(child->getElementName());
+ if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") {
+ fullyQualifyClassName(origPackage, child, String16("name"));
+ } else if (tag == "activity-alias") {
+ fullyQualifyClassName(origPackage, child, String16("name"));
+ fullyQualifyClassName(origPackage, child, String16("targetActivity"));
+ }
+ }
+ }
+ }
+
+ // Deal with manifest package name overrides
+ const char* instrumentationPackageNameOverride = bundle->getInstrumentationPackageNameOverride();
+ if (instrumentationPackageNameOverride != NULL) {
+ // Fix up instrumentation targets.
+ Vector<sp<XMLNode> >& children = const_cast<Vector<sp<XMLNode> >&>(root->getChildren());
+ for (size_t i = 0; i < children.size(); i++) {
+ sp<XMLNode> child = children.editItemAt(i);
+ String8 tag(child->getElementName());
+ if (tag == "instrumentation") {
+ XMLNode::attribute_entry* attr = child->editAttribute(
+ String16("http://schemas.android.com/apk/res/android"), String16("targetPackage"));
+ if (attr != NULL) {
+ attr->string.setTo(String16(instrumentationPackageNameOverride));
+ }
+ }
+ }
+ }
+
+ return NO_ERROR;
+}
+
+#define ASSIGN_IT(n) \
+ do { \
+ ssize_t index = resources->indexOfKey(String8(#n)); \
+ if (index >= 0) { \
+ n ## s = resources->valueAt(index); \
+ } \
+ } while (0)
+
+status_t updatePreProcessedCache(Bundle* bundle)
+{
+ #if BENCHMARK
+ fprintf(stdout, "BENCHMARK: Starting PNG PreProcessing \n");
+ long startPNGTime = clock();
+ #endif /* BENCHMARK */
+
+ String8 source(bundle->getResourceSourceDirs()[0]);
+ String8 dest(bundle->getCrunchedOutputDir());
+
+ FileFinder* ff = new SystemFileFinder();
+ CrunchCache cc(source,dest,ff);
+
+ CacheUpdater* cu = new SystemCacheUpdater(bundle);
+ size_t numFiles = cc.crunch(cu);
+
+ if (bundle->getVerbose())
+ fprintf(stdout, "Crunched %d PNG files to update cache\n", (int)numFiles);
+
+ delete ff;
+ delete cu;
+
+ #if BENCHMARK
+ fprintf(stdout, "BENCHMARK: End PNG PreProcessing. Time Elapsed: %f ms \n"
+ ,(clock() - startPNGTime)/1000.0);
+ #endif /* BENCHMARK */
+ return 0;
+}
+
+status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets)
+{
+ // First, look for a package file to parse. This is required to
+ // be able to generate the resource information.
+ sp<AaptGroup> androidManifestFile =
+ assets->getFiles().valueFor(String8("AndroidManifest.xml"));
+ if (androidManifestFile == NULL) {
+ fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
+ return UNKNOWN_ERROR;
+ }
+
+ status_t err = parsePackage(bundle, assets, androidManifestFile);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ NOISY(printf("Creating resources for package %s\n",
+ assets->getPackage().string()));
+
+ ResourceTable table(bundle, String16(assets->getPackage()));
+ err = table.addIncludedResources(bundle, assets);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ NOISY(printf("Found %d included resource packages\n", (int)table.size()));
+
+ // Standard flags for compiled XML and optional UTF-8 encoding
+ int xmlFlags = XML_COMPILE_STANDARD_RESOURCE;
+
+ /* Only enable UTF-8 if the caller of aapt didn't specifically
+ * request UTF-16 encoding and the parameters of this package
+ * allow UTF-8 to be used.
+ */
+ if (!bundle->getUTF16StringsOption()) {
+ xmlFlags |= XML_COMPILE_UTF8;
+ }
+
+ // --------------------------------------------------------------
+ // First, gather all resource information.
+ // --------------------------------------------------------------
+
+ // resType -> leafName -> group
+ KeyedVector<String8, sp<ResourceTypeSet> > *resources =
+ new KeyedVector<String8, sp<ResourceTypeSet> >;
+ collect_files(assets, resources);
+
+ sp<ResourceTypeSet> drawables;
+ sp<ResourceTypeSet> layouts;
+ sp<ResourceTypeSet> anims;
+ sp<ResourceTypeSet> animators;
+ sp<ResourceTypeSet> interpolators;
+ sp<ResourceTypeSet> transitions;
+ sp<ResourceTypeSet> xmls;
+ sp<ResourceTypeSet> raws;
+ sp<ResourceTypeSet> colors;
+ sp<ResourceTypeSet> menus;
+ sp<ResourceTypeSet> mipmaps;
+
+ ASSIGN_IT(drawable);
+ ASSIGN_IT(layout);
+ ASSIGN_IT(anim);
+ ASSIGN_IT(animator);
+ ASSIGN_IT(interpolator);
+ ASSIGN_IT(transition);
+ ASSIGN_IT(xml);
+ ASSIGN_IT(raw);
+ ASSIGN_IT(color);
+ ASSIGN_IT(menu);
+ ASSIGN_IT(mipmap);
+
+ assets->setResources(resources);
+ // now go through any resource overlays and collect their files
+ sp<AaptAssets> current = assets->getOverlay();
+ while(current.get()) {
+ KeyedVector<String8, sp<ResourceTypeSet> > *resources =
+ new KeyedVector<String8, sp<ResourceTypeSet> >;
+ current->setResources(resources);
+ collect_files(current, resources);
+ current = current->getOverlay();
+ }
+ // apply the overlay files to the base set
+ if (!applyFileOverlay(bundle, assets, &drawables, "drawable") ||
+ !applyFileOverlay(bundle, assets, &layouts, "layout") ||
+ !applyFileOverlay(bundle, assets, &anims, "anim") ||
+ !applyFileOverlay(bundle, assets, &animators, "animator") ||
+ !applyFileOverlay(bundle, assets, &interpolators, "interpolator") ||
+ !applyFileOverlay(bundle, assets, &transitions, "transition") ||
+ !applyFileOverlay(bundle, assets, &xmls, "xml") ||
+ !applyFileOverlay(bundle, assets, &raws, "raw") ||
+ !applyFileOverlay(bundle, assets, &colors, "color") ||
+ !applyFileOverlay(bundle, assets, &menus, "menu") ||
+ !applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) {
+ return UNKNOWN_ERROR;
+ }
+
+ bool hasErrors = false;
+
+ if (drawables != NULL) {
+ if (bundle->getOutputAPKFile() != NULL) {
+ err = preProcessImages(bundle, assets, drawables, "drawable");
+ }
+ if (err == NO_ERROR) {
+ err = makeFileResources(bundle, assets, &table, drawables, "drawable");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ } else {
+ hasErrors = true;
+ }
+ }
+
+ if (mipmaps != NULL) {
+ if (bundle->getOutputAPKFile() != NULL) {
+ err = preProcessImages(bundle, assets, mipmaps, "mipmap");
+ }
+ if (err == NO_ERROR) {
+ err = makeFileResources(bundle, assets, &table, mipmaps, "mipmap");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ } else {
+ hasErrors = true;
+ }
+ }
+
+ if (layouts != NULL) {
+ err = makeFileResources(bundle, assets, &table, layouts, "layout");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (anims != NULL) {
+ err = makeFileResources(bundle, assets, &table, anims, "anim");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (animators != NULL) {
+ err = makeFileResources(bundle, assets, &table, animators, "animator");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (transitions != NULL) {
+ err = makeFileResources(bundle, assets, &table, transitions, "transition");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (interpolators != NULL) {
+ err = makeFileResources(bundle, assets, &table, interpolators, "interpolator");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (xmls != NULL) {
+ err = makeFileResources(bundle, assets, &table, xmls, "xml");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (raws != NULL) {
+ err = makeFileResources(bundle, assets, &table, raws, "raw");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ // compile resources
+ current = assets;
+ while(current.get()) {
+ KeyedVector<String8, sp<ResourceTypeSet> > *resources =
+ current->getResources();
+
+ ssize_t index = resources->indexOfKey(String8("values"));
+ if (index >= 0) {
+ ResourceDirIterator it(resources->valueAt(index), String8("values"));
+ ssize_t res;
+ while ((res=it.next()) == NO_ERROR) {
+ sp<AaptFile> file = it.getFile();
+ res = compileResourceFile(bundle, assets, file, it.getParams(),
+ (current!=assets), &table);
+ if (res != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+ }
+ current = current->getOverlay();
+ }
+
+ if (colors != NULL) {
+ err = makeFileResources(bundle, assets, &table, colors, "color");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (menus != NULL) {
+ err = makeFileResources(bundle, assets, &table, menus, "menu");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ // --------------------------------------------------------------------
+ // Assignment of resource IDs and initial generation of resource table.
+ // --------------------------------------------------------------------
+
+ if (table.hasResources()) {
+ sp<AaptFile> resFile(getResourceFile(assets));
+ if (resFile == NULL) {
+ fprintf(stderr, "Error: unable to generate entry for resource data\n");
+ return UNKNOWN_ERROR;
+ }
+
+ err = table.assignResourceIds();
+ if (err < NO_ERROR) {
+ return err;
+ }
+ }
+
+ // --------------------------------------------------------------
+ // Finally, we can now we can compile XML files, which may reference
+ // resources.
+ // --------------------------------------------------------------
+
+ if (layouts != NULL) {
+ ResourceDirIterator it(layouts, String8("layout"));
+ while ((err=it.next()) == NO_ERROR) {
+ String8 src = it.getFile()->getPrintableSource();
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+ if (err == NO_ERROR) {
+ ResXMLTree block;
+ block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
+ checkForIds(src, block);
+ } else {
+ hasErrors = true;
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (anims != NULL) {
+ ResourceDirIterator it(anims, String8("anim"));
+ while ((err=it.next()) == NO_ERROR) {
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (animators != NULL) {
+ ResourceDirIterator it(animators, String8("animator"));
+ while ((err=it.next()) == NO_ERROR) {
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (interpolators != NULL) {
+ ResourceDirIterator it(interpolators, String8("interpolator"));
+ while ((err=it.next()) == NO_ERROR) {
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (transitions != NULL) {
+ ResourceDirIterator it(transitions, String8("transition"));
+ while ((err=it.next()) == NO_ERROR) {
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (xmls != NULL) {
+ ResourceDirIterator it(xmls, String8("xml"));
+ while ((err=it.next()) == NO_ERROR) {
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (drawables != NULL) {
+ err = postProcessImages(assets, &table, drawables);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (colors != NULL) {
+ ResourceDirIterator it(colors, String8("color"));
+ while ((err=it.next()) == NO_ERROR) {
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (menus != NULL) {
+ ResourceDirIterator it(menus, String8("menu"));
+ while ((err=it.next()) == NO_ERROR) {
+ String8 src = it.getFile()->getPrintableSource();
+ err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ ResXMLTree block;
+ block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
+ checkForIds(src, block);
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
+ if (table.validateLocalizations()) {
+ hasErrors = true;
+ }
+
+ if (hasErrors) {
+ return UNKNOWN_ERROR;
+ }
+
+ const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0));
+ String8 manifestPath(manifestFile->getPrintableSource());
+
+ // Generate final compiled manifest file.
+ manifestFile->clearData();
+ sp<XMLNode> manifestTree = XMLNode::parse(manifestFile);
+ if (manifestTree == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ err = massageManifest(bundle, manifestTree);
+ if (err < NO_ERROR) {
+ return err;
+ }
+ err = compileXmlFile(assets, manifestTree, manifestFile, &table);
+ if (err < NO_ERROR) {
+ return err;
+ }
+
+ //block.restart();
+ //printXMLBlock(&block);
+
+ // --------------------------------------------------------------
+ // Generate the final resource table.
+ // Re-flatten because we may have added new resource IDs
+ // --------------------------------------------------------------
+
+ ResTable finalResTable;
+ sp<AaptFile> resFile;
+
+ if (table.hasResources()) {
+ sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
+ err = table.addSymbols(symbols);
+ if (err < NO_ERROR) {
+ return err;
+ }
+
+ resFile = getResourceFile(assets);
+ if (resFile == NULL) {
+ fprintf(stderr, "Error: unable to generate entry for resource data\n");
+ return UNKNOWN_ERROR;
+ }
+
+ err = table.flatten(bundle, resFile);
+ if (err < NO_ERROR) {
+ return err;
+ }
+
+ if (bundle->getPublicOutputFile()) {
+ FILE* fp = fopen(bundle->getPublicOutputFile(), "w+");
+ if (fp == NULL) {
+ fprintf(stderr, "ERROR: Unable to open public definitions output file %s: %s\n",
+ (const char*)bundle->getPublicOutputFile(), strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+ if (bundle->getVerbose()) {
+ printf(" Writing public definitions to %s.\n", bundle->getPublicOutputFile());
+ }
+ table.writePublicDefinitions(String16(assets->getPackage()), fp);
+ fclose(fp);
+ }
+
+ // Read resources back in,
+ finalResTable.add(resFile->getData(), resFile->getSize(), NULL);
+
+#if 0
+ NOISY(
+ printf("Generated resources:\n");
+ finalResTable.print();
+ )
+#endif
+ }
+
+ // Perform a basic validation of the manifest file. This time we
+ // parse it with the comments intact, so that we can use them to
+ // generate java docs... so we are not going to write this one
+ // back out to the final manifest data.
+ sp<AaptFile> outManifestFile = new AaptFile(manifestFile->getSourceFile(),
+ manifestFile->getGroupEntry(),
+ manifestFile->getResourceType());
+ err = compileXmlFile(assets, manifestFile,
+ outManifestFile, &table,
+ XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+ | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
+ if (err < NO_ERROR) {
+ return err;
+ }
+ ResXMLTree block;
+ block.setTo(outManifestFile->getData(), outManifestFile->getSize(), true);
+ String16 manifest16("manifest");
+ String16 permission16("permission");
+ String16 permission_group16("permission-group");
+ String16 uses_permission16("uses-permission");
+ String16 instrumentation16("instrumentation");
+ String16 application16("application");
+ String16 provider16("provider");
+ String16 service16("service");
+ String16 receiver16("receiver");
+ String16 activity16("activity");
+ String16 action16("action");
+ String16 category16("category");
+ String16 data16("scheme");
+ const char* packageIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789";
+ const char* packageIdentCharsWithTheStupid = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
+ const char* classIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789$";
+ const char* processIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:";
+ const char* authoritiesIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-:;";
+ const char* typeIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:-/*+";
+ const char* schemeIdentChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
+ ResXMLTree::event_code_t code;
+ sp<AaptSymbols> permissionSymbols;
+ sp<AaptSymbols> permissionGroupSymbols;
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code > ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ size_t len;
+ if (block.getElementNamespace(&len) != NULL) {
+ continue;
+ }
+ if (strcmp16(block.getElementName(&len), manifest16.string()) == 0) {
+ if (validateAttr(manifestPath, finalResTable, block, NULL, "package",
+ packageIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+ "sharedUserId", packageIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), permission16.string()) == 0
+ || strcmp16(block.getElementName(&len), permission_group16.string()) == 0) {
+ const bool isGroup = strcmp16(block.getElementName(&len),
+ permission_group16.string()) == 0;
+ if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+ "name", isGroup ? packageIdentCharsWithTheStupid
+ : packageIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ SourcePos srcPos(manifestPath, block.getLineNumber());
+ sp<AaptSymbols> syms;
+ if (!isGroup) {
+ syms = permissionSymbols;
+ if (syms == NULL) {
+ sp<AaptSymbols> symbols =
+ assets->getSymbolsFor(String8("Manifest"));
+ syms = permissionSymbols = symbols->addNestedSymbol(
+ String8("permission"), srcPos);
+ }
+ } else {
+ syms = permissionGroupSymbols;
+ if (syms == NULL) {
+ sp<AaptSymbols> symbols =
+ assets->getSymbolsFor(String8("Manifest"));
+ syms = permissionGroupSymbols = symbols->addNestedSymbol(
+ String8("permission_group"), srcPos);
+ }
+ }
+ size_t len;
+ ssize_t index = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "name");
+ const uint16_t* id = block.getAttributeStringValue(index, &len);
+ if (id == NULL) {
+ fprintf(stderr, "%s:%d: missing name attribute in element <%s>.\n",
+ manifestPath.string(), block.getLineNumber(),
+ String8(block.getElementName(&len)).string());
+ hasErrors = true;
+ break;
+ }
+ String8 idStr(id);
+ char* p = idStr.lockBuffer(idStr.size());
+ char* e = p + idStr.size();
+ bool begins_with_digit = true; // init to true so an empty string fails
+ while (e > p) {
+ e--;
+ if (*e >= '0' && *e <= '9') {
+ begins_with_digit = true;
+ continue;
+ }
+ if ((*e >= 'a' && *e <= 'z') ||
+ (*e >= 'A' && *e <= 'Z') ||
+ (*e == '_')) {
+ begins_with_digit = false;
+ continue;
+ }
+ if (isGroup && (*e == '-')) {
+ *e = '_';
+ begins_with_digit = false;
+ continue;
+ }
+ e++;
+ break;
+ }
+ idStr.unlockBuffer();
+ // verify that we stopped because we hit a period or
+ // the beginning of the string, and that the
+ // identifier didn't begin with a digit.
+ if (begins_with_digit || (e != p && *(e-1) != '.')) {
+ fprintf(stderr,
+ "%s:%d: Permission name <%s> is not a valid Java symbol\n",
+ manifestPath.string(), block.getLineNumber(), idStr.string());
+ hasErrors = true;
+ }
+ syms->addStringSymbol(String8(e), idStr, srcPos);
+ const uint16_t* cmt = block.getComment(&len);
+ if (cmt != NULL && *cmt != 0) {
+ //printf("Comment of %s: %s\n", String8(e).string(),
+ // String8(cmt).string());
+ syms->appendComment(String8(e), String16(cmt), srcPos);
+ } else {
+ //printf("No comment for %s\n", String8(e).string());
+ }
+ syms->makeSymbolPublic(String8(e), srcPos);
+ } else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) {
+ if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+ "name", packageIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), instrumentation16.string()) == 0) {
+ if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+ "name", classIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "targetPackage",
+ packageIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), application16.string()) == 0) {
+ if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+ "name", classIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "permission",
+ packageIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "process",
+ processIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
+ processIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), provider16.string()) == 0) {
+ if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+ "name", classIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "authorities",
+ authoritiesIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "permission",
+ packageIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "process",
+ processIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), service16.string()) == 0
+ || strcmp16(block.getElementName(&len), receiver16.string()) == 0
+ || strcmp16(block.getElementName(&len), activity16.string()) == 0) {
+ if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
+ "name", classIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "permission",
+ packageIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "process",
+ processIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
+ processIdentChars, false) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), action16.string()) == 0
+ || strcmp16(block.getElementName(&len), category16.string()) == 0) {
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "name",
+ packageIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), data16.string()) == 0) {
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "mimeType",
+ typeIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ if (validateAttr(manifestPath, finalResTable, block,
+ RESOURCES_ANDROID_NAMESPACE, "scheme",
+ schemeIdentChars, true) != ATTR_OKAY) {
+ hasErrors = true;
+ }
+ }
+ }
+ }
+
+ if (resFile != NULL) {
+ // These resources are now considered to be a part of the included
+ // resources, for others to reference.
+ err = assets->addIncludedResources(resFile);
+ if (err < NO_ERROR) {
+ fprintf(stderr, "ERROR: Unable to parse generated resources, aborting.\n");
+ return err;
+ }
+ }
+
+ return err;
+}
+
+static const char* getIndentSpace(int indent)
+{
+static const char whitespace[] =
+" ";
+
+ return whitespace + sizeof(whitespace) - 1 - indent*4;
+}
+
+static String8 flattenSymbol(const String8& symbol) {
+ String8 result(symbol);
+ ssize_t first;
+ if ((first = symbol.find(":", 0)) >= 0
+ || (first = symbol.find(".", 0)) >= 0) {
+ size_t size = symbol.size();
+ char* buf = result.lockBuffer(size);
+ for (size_t i = first; i < size; i++) {
+ if (buf[i] == ':' || buf[i] == '.') {
+ buf[i] = '_';
+ }
+ }
+ result.unlockBuffer(size);
+ }
+ return result;
+}
+
+static String8 getSymbolPackage(const String8& symbol, const sp<AaptAssets>& assets, bool pub) {
+ ssize_t colon = symbol.find(":", 0);
+ if (colon >= 0) {
+ return String8(symbol.string(), colon);
+ }
+ return pub ? assets->getPackage() : assets->getSymbolsPrivatePackage();
+}
+
+static String8 getSymbolName(const String8& symbol) {
+ ssize_t colon = symbol.find(":", 0);
+ if (colon >= 0) {
+ return String8(symbol.string() + colon + 1);
+ }
+ return symbol;
+}
+
+static String16 getAttributeComment(const sp<AaptAssets>& assets,
+ const String8& name,
+ String16* outTypeComment = NULL)
+{
+ sp<AaptSymbols> asym = assets->getSymbolsFor(String8("R"));
+ if (asym != NULL) {
+ //printf("Got R symbols!\n");
+ asym = asym->getNestedSymbols().valueFor(String8("attr"));
+ if (asym != NULL) {
+ //printf("Got attrs symbols! comment %s=%s\n",
+ // name.string(), String8(asym->getComment(name)).string());
+ if (outTypeComment != NULL) {
+ *outTypeComment = asym->getTypeComment(name);
+ }
+ return asym->getComment(name);
+ }
+ }
+ return String16();
+}
+
+static status_t writeLayoutClasses(
+ FILE* fp, const sp<AaptAssets>& assets,
+ const sp<AaptSymbols>& symbols, int indent, bool includePrivate)
+{
+ const char* indentStr = getIndentSpace(indent);
+ if (!includePrivate) {
+ fprintf(fp, "%s/** @doconly */\n", indentStr);
+ }
+ fprintf(fp, "%spublic static final class styleable {\n", indentStr);
+ indent++;
+
+ String16 attr16("attr");
+ String16 package16(assets->getPackage());
+
+ indentStr = getIndentSpace(indent);
+ bool hasErrors = false;
+
+ size_t i;
+ size_t N = symbols->getNestedSymbols().size();
+ for (i=0; i<N; i++) {
+ sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
+ String8 realClassName(symbols->getNestedSymbols().keyAt(i));
+ String8 nclassName(flattenSymbol(realClassName));
+
+ SortedVector<uint32_t> idents;
+ Vector<uint32_t> origOrder;
+ Vector<bool> publicFlags;
+
+ size_t a;
+ size_t NA = nsymbols->getSymbols().size();
+ for (a=0; a<NA; a++) {
+ const AaptSymbolEntry& sym(nsymbols->getSymbols().valueAt(a));
+ int32_t code = sym.typeCode == AaptSymbolEntry::TYPE_INT32
+ ? sym.int32Val : 0;
+ bool isPublic = true;
+ if (code == 0) {
+ String16 name16(sym.name);
+ uint32_t typeSpecFlags;
+ code = assets->getIncludedResources().identifierForName(
+ name16.string(), name16.size(),
+ attr16.string(), attr16.size(),
+ package16.string(), package16.size(), &typeSpecFlags);
+ if (code == 0) {
+ fprintf(stderr, "ERROR: In <declare-styleable> %s, unable to find attribute %s\n",
+ nclassName.string(), sym.name.string());
+ hasErrors = true;
+ }
+ isPublic = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
+ }
+ idents.add(code);
+ origOrder.add(code);
+ publicFlags.add(isPublic);
+ }
+
+ NA = idents.size();
+
+ bool deprecated = false;
+
+ String16 comment = symbols->getComment(realClassName);
+ fprintf(fp, "%s/** ", indentStr);
+ if (comment.size() > 0) {
+ String8 cmt(comment);
+ fprintf(fp, "%s\n", cmt.string());
+ if (strstr(cmt.string(), "@deprecated") != NULL) {
+ deprecated = true;
+ }
+ } else {
+ fprintf(fp, "Attributes that can be used with a %s.\n", nclassName.string());
+ }
+ bool hasTable = false;
+ for (a=0; a<NA; a++) {
+ ssize_t pos = idents.indexOf(origOrder.itemAt(a));
+ if (pos >= 0) {
+ if (!hasTable) {
+ hasTable = true;
+ fprintf(fp,
+ "%s <p>Includes the following attributes:</p>\n"
+ "%s <table>\n"
+ "%s <colgroup align=\"left\" />\n"
+ "%s <colgroup align=\"left\" />\n"
+ "%s <tr><th>Attribute</th><th>Description</th></tr>\n",
+ indentStr,
+ indentStr,
+ indentStr,
+ indentStr,
+ indentStr);
+ }
+ const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
+ if (!publicFlags.itemAt(a) && !includePrivate) {
+ continue;
+ }
+ String8 name8(sym.name);
+ String16 comment(sym.comment);
+ if (comment.size() <= 0) {
+ comment = getAttributeComment(assets, name8);
+ }
+ if (comment.size() > 0) {
+ const char16_t* p = comment.string();
+ while (*p != 0 && *p != '.') {
+ if (*p == '{') {
+ while (*p != 0 && *p != '}') {
+ p++;
+ }
+ } else {
+ p++;
+ }
+ }
+ if (*p == '.') {
+ p++;
+ }
+ comment = String16(comment.string(), p-comment.string());
+ }
+ fprintf(fp, "%s <tr><td><code>{@link #%s_%s %s:%s}</code></td><td>%s</td></tr>\n",
+ indentStr, nclassName.string(),
+ flattenSymbol(name8).string(),
+ getSymbolPackage(name8, assets, true).string(),
+ getSymbolName(name8).string(),
+ String8(comment).string());
+ }
+ }
+ if (hasTable) {
+ fprintf(fp, "%s </table>\n", indentStr);
+ }
+ for (a=0; a<NA; a++) {
+ ssize_t pos = idents.indexOf(origOrder.itemAt(a));
+ if (pos >= 0) {
+ const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
+ if (!publicFlags.itemAt(a) && !includePrivate) {
+ continue;
+ }
+ fprintf(fp, "%s @see #%s_%s\n",
+ indentStr, nclassName.string(),
+ flattenSymbol(sym.name).string());
+ }
+ }
+ fprintf(fp, "%s */\n", getIndentSpace(indent));
+
+ if (deprecated) {
+ fprintf(fp, "%s@Deprecated\n", indentStr);
+ }
+
+ fprintf(fp,
+ "%spublic static final int[] %s = {\n"
+ "%s",
+ indentStr, nclassName.string(),
+ getIndentSpace(indent+1));
+
+ for (a=0; a<NA; a++) {
+ if (a != 0) {
+ if ((a&3) == 0) {
+ fprintf(fp, ",\n%s", getIndentSpace(indent+1));
+ } else {
+ fprintf(fp, ", ");
+ }
+ }
+ fprintf(fp, "0x%08x", idents[a]);
+ }
+
+ fprintf(fp, "\n%s};\n", indentStr);
+
+ for (a=0; a<NA; a++) {
+ ssize_t pos = idents.indexOf(origOrder.itemAt(a));
+ if (pos >= 0) {
+ const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
+ if (!publicFlags.itemAt(a) && !includePrivate) {
+ continue;
+ }
+ String8 name8(sym.name);
+ String16 comment(sym.comment);
+ String16 typeComment;
+ if (comment.size() <= 0) {
+ comment = getAttributeComment(assets, name8, &typeComment);
+ } else {
+ getAttributeComment(assets, name8, &typeComment);
+ }
+
+ uint32_t typeSpecFlags = 0;
+ String16 name16(sym.name);
+ assets->getIncludedResources().identifierForName(
+ name16.string(), name16.size(),
+ attr16.string(), attr16.size(),
+ package16.string(), package16.size(), &typeSpecFlags);
+ //printf("%s:%s/%s: 0x%08x\n", String8(package16).string(),
+ // String8(attr16).string(), String8(name16).string(), typeSpecFlags);
+ const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
+
+ bool deprecated = false;
+
+ fprintf(fp, "%s/**\n", indentStr);
+ if (comment.size() > 0) {
+ String8 cmt(comment);
+ fprintf(fp, "%s <p>\n%s @attr description\n", indentStr, indentStr);
+ fprintf(fp, "%s %s\n", indentStr, cmt.string());
+ if (strstr(cmt.string(), "@deprecated") != NULL) {
+ deprecated = true;
+ }
+ } else {
+ fprintf(fp,
+ "%s <p>This symbol is the offset where the {@link %s.R.attr#%s}\n"
+ "%s attribute's value can be found in the {@link #%s} array.\n",
+ indentStr,
+ getSymbolPackage(name8, assets, pub).string(),
+ getSymbolName(name8).string(),
+ indentStr, nclassName.string());
+ }
+ if (typeComment.size() > 0) {
+ String8 cmt(typeComment);
+ fprintf(fp, "\n\n%s %s\n", indentStr, cmt.string());
+ if (strstr(cmt.string(), "@deprecated") != NULL) {
+ deprecated = true;
+ }
+ }
+ if (comment.size() > 0) {
+ if (pub) {
+ fprintf(fp,
+ "%s <p>This corresponds to the global attribute\n"
+ "%s resource symbol {@link %s.R.attr#%s}.\n",
+ indentStr, indentStr,
+ getSymbolPackage(name8, assets, true).string(),
+ getSymbolName(name8).string());
+ } else {
+ fprintf(fp,
+ "%s <p>This is a private symbol.\n", indentStr);
+ }
+ }
+ fprintf(fp, "%s @attr name %s:%s\n", indentStr,
+ getSymbolPackage(name8, assets, pub).string(),
+ getSymbolName(name8).string());
+ fprintf(fp, "%s*/\n", indentStr);
+ if (deprecated) {
+ fprintf(fp, "%s@Deprecated\n", indentStr);
+ }
+ fprintf(fp,
+ "%spublic static final int %s_%s = %d;\n",
+ indentStr, nclassName.string(),
+ flattenSymbol(name8).string(), (int)pos);
+ }
+ }
+ }
+
+ indent--;
+ fprintf(fp, "%s};\n", getIndentSpace(indent));
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+static status_t writeTextLayoutClasses(
+ FILE* fp, const sp<AaptAssets>& assets,
+ const sp<AaptSymbols>& symbols, bool includePrivate)
+{
+ String16 attr16("attr");
+ String16 package16(assets->getPackage());
+
+ bool hasErrors = false;
+
+ size_t i;
+ size_t N = symbols->getNestedSymbols().size();
+ for (i=0; i<N; i++) {
+ sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
+ String8 realClassName(symbols->getNestedSymbols().keyAt(i));
+ String8 nclassName(flattenSymbol(realClassName));
+
+ SortedVector<uint32_t> idents;
+ Vector<uint32_t> origOrder;
+ Vector<bool> publicFlags;
+
+ size_t a;
+ size_t NA = nsymbols->getSymbols().size();
+ for (a=0; a<NA; a++) {
+ const AaptSymbolEntry& sym(nsymbols->getSymbols().valueAt(a));
+ int32_t code = sym.typeCode == AaptSymbolEntry::TYPE_INT32
+ ? sym.int32Val : 0;
+ bool isPublic = true;
+ if (code == 0) {
+ String16 name16(sym.name);
+ uint32_t typeSpecFlags;
+ code = assets->getIncludedResources().identifierForName(
+ name16.string(), name16.size(),
+ attr16.string(), attr16.size(),
+ package16.string(), package16.size(), &typeSpecFlags);
+ if (code == 0) {
+ fprintf(stderr, "ERROR: In <declare-styleable> %s, unable to find attribute %s\n",
+ nclassName.string(), sym.name.string());
+ hasErrors = true;
+ }
+ isPublic = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
+ }
+ idents.add(code);
+ origOrder.add(code);
+ publicFlags.add(isPublic);
+ }
+
+ NA = idents.size();
+
+ fprintf(fp, "int[] styleable %s {", nclassName.string());
+
+ for (a=0; a<NA; a++) {
+ if (a != 0) {
+ fprintf(fp, ",");
+ }
+ fprintf(fp, " 0x%08x", idents[a]);
+ }
+
+ fprintf(fp, " }\n");
+
+ for (a=0; a<NA; a++) {
+ ssize_t pos = idents.indexOf(origOrder.itemAt(a));
+ if (pos >= 0) {
+ const AaptSymbolEntry& sym = nsymbols->getSymbols().valueAt(a);
+ if (!publicFlags.itemAt(a) && !includePrivate) {
+ continue;
+ }
+ String8 name8(sym.name);
+ String16 comment(sym.comment);
+ String16 typeComment;
+ if (comment.size() <= 0) {
+ comment = getAttributeComment(assets, name8, &typeComment);
+ } else {
+ getAttributeComment(assets, name8, &typeComment);
+ }
+
+ uint32_t typeSpecFlags = 0;
+ String16 name16(sym.name);
+ assets->getIncludedResources().identifierForName(
+ name16.string(), name16.size(),
+ attr16.string(), attr16.size(),
+ package16.string(), package16.size(), &typeSpecFlags);
+ //printf("%s:%s/%s: 0x%08x\n", String8(package16).string(),
+ // String8(attr16).string(), String8(name16).string(), typeSpecFlags);
+ const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
+
+ fprintf(fp,
+ "int styleable %s_%s %d\n",
+ nclassName.string(),
+ flattenSymbol(name8).string(), (int)pos);
+ }
+ }
+ }
+
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+static status_t writeSymbolClass(
+ FILE* fp, const sp<AaptAssets>& assets, bool includePrivate,
+ const sp<AaptSymbols>& symbols, const String8& className, int indent,
+ bool nonConstantId)
+{
+ fprintf(fp, "%spublic %sfinal class %s {\n",
+ getIndentSpace(indent),
+ indent != 0 ? "static " : "", className.string());
+ indent++;
+
+ size_t i;
+ status_t err = NO_ERROR;
+
+ const char * id_format = nonConstantId ?
+ "%spublic static int %s=0x%08x;\n" :
+ "%spublic static final int %s=0x%08x;\n";
+
+ size_t N = symbols->getSymbols().size();
+ for (i=0; i<N; i++) {
+ const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
+ if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) {
+ continue;
+ }
+ if (!assets->isJavaSymbol(sym, includePrivate)) {
+ continue;
+ }
+ String8 name8(sym.name);
+ String16 comment(sym.comment);
+ bool haveComment = false;
+ bool deprecated = false;
+ if (comment.size() > 0) {
+ haveComment = true;
+ String8 cmt(comment);
+ fprintf(fp,
+ "%s/** %s\n",
+ getIndentSpace(indent), cmt.string());
+ if (strstr(cmt.string(), "@deprecated") != NULL) {
+ deprecated = true;
+ }
+ } else if (sym.isPublic && !includePrivate) {
+ sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
+ assets->getPackage().string(), className.string(),
+ String8(sym.name).string());
+ }
+ String16 typeComment(sym.typeComment);
+ if (typeComment.size() > 0) {
+ String8 cmt(typeComment);
+ if (!haveComment) {
+ haveComment = true;
+ fprintf(fp,
+ "%s/** %s\n", getIndentSpace(indent), cmt.string());
+ } else {
+ fprintf(fp,
+ "%s %s\n", getIndentSpace(indent), cmt.string());
+ }
+ if (strstr(cmt.string(), "@deprecated") != NULL) {
+ deprecated = true;
+ }
+ }
+ if (haveComment) {
+ fprintf(fp,"%s */\n", getIndentSpace(indent));
+ }
+ if (deprecated) {
+ fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent));
+ }
+ fprintf(fp, id_format,
+ getIndentSpace(indent),
+ flattenSymbol(name8).string(), (int)sym.int32Val);
+ }
+
+ for (i=0; i<N; i++) {
+ const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
+ if (sym.typeCode != AaptSymbolEntry::TYPE_STRING) {
+ continue;
+ }
+ if (!assets->isJavaSymbol(sym, includePrivate)) {
+ continue;
+ }
+ String8 name8(sym.name);
+ String16 comment(sym.comment);
+ bool deprecated = false;
+ if (comment.size() > 0) {
+ String8 cmt(comment);
+ fprintf(fp,
+ "%s/** %s\n"
+ "%s */\n",
+ getIndentSpace(indent), cmt.string(),
+ getIndentSpace(indent));
+ if (strstr(cmt.string(), "@deprecated") != NULL) {
+ deprecated = true;
+ }
+ } else if (sym.isPublic && !includePrivate) {
+ sym.sourcePos.warning("No comment for public symbol %s:%s/%s",
+ assets->getPackage().string(), className.string(),
+ String8(sym.name).string());
+ }
+ if (deprecated) {
+ fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent));
+ }
+ fprintf(fp, "%spublic static final String %s=\"%s\";\n",
+ getIndentSpace(indent),
+ flattenSymbol(name8).string(), sym.stringVal.string());
+ }
+
+ sp<AaptSymbols> styleableSymbols;
+
+ N = symbols->getNestedSymbols().size();
+ for (i=0; i<N; i++) {
+ sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
+ String8 nclassName(symbols->getNestedSymbols().keyAt(i));
+ if (nclassName == "styleable") {
+ styleableSymbols = nsymbols;
+ } else {
+ err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, indent, nonConstantId);
+ }
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+
+ if (styleableSymbols != NULL) {
+ err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+
+ indent--;
+ fprintf(fp, "%s}\n", getIndentSpace(indent));
+ return NO_ERROR;
+}
+
+static status_t writeTextSymbolClass(
+ FILE* fp, const sp<AaptAssets>& assets, bool includePrivate,
+ const sp<AaptSymbols>& symbols, const String8& className)
+{
+ size_t i;
+ status_t err = NO_ERROR;
+
+ size_t N = symbols->getSymbols().size();
+ for (i=0; i<N; i++) {
+ const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i);
+ if (sym.typeCode != AaptSymbolEntry::TYPE_INT32) {
+ continue;
+ }
+
+ if (!assets->isJavaSymbol(sym, includePrivate)) {
+ continue;
+ }
+
+ String8 name8(sym.name);
+ fprintf(fp, "int %s %s 0x%08x\n",
+ className.string(),
+ flattenSymbol(name8).string(), (int)sym.int32Val);
+ }
+
+ N = symbols->getNestedSymbols().size();
+ for (i=0; i<N; i++) {
+ sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i);
+ String8 nclassName(symbols->getNestedSymbols().keyAt(i));
+ if (nclassName == "styleable") {
+ err = writeTextLayoutClasses(fp, assets, nsymbols, includePrivate);
+ } else {
+ err = writeTextSymbolClass(fp, assets, includePrivate, nsymbols, nclassName);
+ }
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets,
+ const String8& package, bool includePrivate)
+{
+ if (!bundle->getRClassDir()) {
+ return NO_ERROR;
+ }
+
+ const char* textSymbolsDest = bundle->getOutputTextSymbols();
+
+ String8 R("R");
+ const size_t N = assets->getSymbols().size();
+ for (size_t i=0; i<N; i++) {
+ sp<AaptSymbols> symbols = assets->getSymbols().valueAt(i);
+ String8 className(assets->getSymbols().keyAt(i));
+ String8 dest(bundle->getRClassDir());
+
+ if (bundle->getMakePackageDirs()) {
+ String8 pkg(package);
+ const char* last = pkg.string();
+ const char* s = last-1;
+ do {
+ s++;
+ if (s > last && (*s == '.' || *s == 0)) {
+ String8 part(last, s-last);
+ dest.appendPath(part);
+#ifdef HAVE_MS_C_RUNTIME
+ _mkdir(dest.string());
+#else
+ mkdir(dest.string(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
+#endif
+ last = s+1;
+ }
+ } while (*s);
+ }
+ dest.appendPath(className);
+ dest.append(".java");
+ FILE* fp = fopen(dest.string(), "w+");
+ if (fp == NULL) {
+ fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
+ dest.string(), strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+ if (bundle->getVerbose()) {
+ printf(" Writing symbols for class %s.\n", className.string());
+ }
+
+ fprintf(fp,
+ "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
+ " *\n"
+ " * This class was automatically generated by the\n"
+ " * aapt tool from the resource data it found. It\n"
+ " * should not be modified by hand.\n"
+ " */\n"
+ "\n"
+ "package %s;\n\n", package.string());
+
+ status_t err = writeSymbolClass(fp, assets, includePrivate, symbols,
+ className, 0, bundle->getNonConstantId());
+ fclose(fp);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ if (textSymbolsDest != NULL && R == className) {
+ String8 textDest(textSymbolsDest);
+ textDest.appendPath(className);
+ textDest.append(".txt");
+
+ FILE* fp = fopen(textDest.string(), "w+");
+ if (fp == NULL) {
+ fprintf(stderr, "ERROR: Unable to open text symbol file %s: %s\n",
+ textDest.string(), strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+ if (bundle->getVerbose()) {
+ printf(" Writing text symbols for class %s.\n", className.string());
+ }
+
+ status_t err = writeTextSymbolClass(fp, assets, includePrivate, symbols,
+ className);
+ fclose(fp);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+
+ // If we were asked to generate a dependency file, we'll go ahead and add this R.java
+ // as a target in the dependency file right next to it.
+ if (bundle->getGenDependencies() && R == className) {
+ // Add this R.java to the dependency file
+ String8 dependencyFile(bundle->getRClassDir());
+ dependencyFile.appendPath("R.java.d");
+
+ FILE *fp = fopen(dependencyFile.string(), "a");
+ fprintf(fp,"%s \\\n", dest.string());
+ fclose(fp);
+ }
+ }
+
+ return NO_ERROR;
+}
+
+
+class ProguardKeepSet
+{
+public:
+ // { rule --> { file locations } }
+ KeyedVector<String8, SortedVector<String8> > rules;
+
+ void add(const String8& rule, const String8& where);
+};
+
+void ProguardKeepSet::add(const String8& rule, const String8& where)
+{
+ ssize_t index = rules.indexOfKey(rule);
+ if (index < 0) {
+ index = rules.add(rule, SortedVector<String8>());
+ }
+ rules.editValueAt(index).add(where);
+}
+
+void
+addProguardKeepRule(ProguardKeepSet* keep, const String8& inClassName,
+ const char* pkg, const String8& srcName, int line)
+{
+ String8 className(inClassName);
+ if (pkg != NULL) {
+ // asdf --> package.asdf
+ // .asdf .a.b --> package.asdf package.a.b
+ // asdf.adsf --> asdf.asdf
+ const char* p = className.string();
+ const char* q = strchr(p, '.');
+ if (p == q) {
+ className = pkg;
+ className.append(inClassName);
+ } else if (q == NULL) {
+ className = pkg;
+ className.append(".");
+ className.append(inClassName);
+ }
+ }
+
+ String8 rule("-keep class ");
+ rule += className;
+ rule += " { <init>(...); }";
+
+ String8 location("view ");
+ location += srcName;
+ char lineno[20];
+ sprintf(lineno, ":%d", line);
+ location += lineno;
+
+ keep->add(rule, location);
+}
+
+void
+addProguardKeepMethodRule(ProguardKeepSet* keep, const String8& memberName,
+ const char* pkg, const String8& srcName, int line)
+{
+ String8 rule("-keepclassmembers class * { *** ");
+ rule += memberName;
+ rule += "(...); }";
+
+ String8 location("onClick ");
+ location += srcName;
+ char lineno[20];
+ sprintf(lineno, ":%d", line);
+ location += lineno;
+
+ keep->add(rule, location);
+}
+
+status_t
+writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
+{
+ status_t err;
+ ResXMLTree tree;
+ size_t len;
+ ResXMLTree::event_code_t code;
+ int depth = 0;
+ bool inApplication = false;
+ String8 error;
+ sp<AaptGroup> assGroup;
+ sp<AaptFile> assFile;
+ String8 pkg;
+
+ // First, look for a package file to parse. This is required to
+ // be able to generate the resource information.
+ assGroup = assets->getFiles().valueFor(String8("AndroidManifest.xml"));
+ if (assGroup == NULL) {
+ fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
+ return -1;
+ }
+
+ if (assGroup->getFiles().size() != 1) {
+ fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
+ assGroup->getFiles().valueAt(0)->getPrintableSource().string());
+ }
+
+ assFile = assGroup->getFiles().valueAt(0);
+
+ err = parseXMLResource(assFile, &tree);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ tree.restart();
+
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (/* name == "Application" && */ depth == 2) {
+ inApplication = false;
+ }
+ depth--;
+ continue;
+ }
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ depth++;
+ String8 tag(tree.getElementName(&len));
+ // printf("Depth %d tag %s\n", depth, tag.string());
+ bool keepTag = false;
+ if (depth == 1) {
+ if (tag != "manifest") {
+ fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
+ return -1;
+ }
+ pkg = getAttribute(tree, NULL, "package", NULL);
+ } else if (depth == 2) {
+ if (tag == "application") {
+ inApplication = true;
+ keepTag = true;
+
+ String8 agent = getAttribute(tree, "http://schemas.android.com/apk/res/android",
+ "backupAgent", &error);
+ if (agent.length() > 0) {
+ addProguardKeepRule(keep, agent, pkg.string(),
+ assFile->getPrintableSource(), tree.getLineNumber());
+ }
+ } else if (tag == "instrumentation") {
+ keepTag = true;
+ }
+ }
+ if (!keepTag && inApplication && depth == 3) {
+ if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") {
+ keepTag = true;
+ }
+ }
+ if (keepTag) {
+ String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android",
+ "name", &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ return -1;
+ }
+ if (name.length() > 0) {
+ addProguardKeepRule(keep, name, pkg.string(),
+ assFile->getPrintableSource(), tree.getLineNumber());
+ }
+ }
+ }
+
+ return NO_ERROR;
+}
+
+struct NamespaceAttributePair {
+ const char* ns;
+ const char* attr;
+
+ NamespaceAttributePair(const char* n, const char* a) : ns(n), attr(a) {}
+ NamespaceAttributePair() : ns(NULL), attr(NULL) {}
+};
+
+status_t
+writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile,
+ const char* startTag, const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs)
+{
+ status_t err;
+ ResXMLTree tree;
+ size_t len;
+ ResXMLTree::event_code_t code;
+
+ err = parseXMLResource(layoutFile, &tree);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ tree.restart();
+
+ if (startTag != NULL) {
+ bool haveStart = false;
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ String8 tag(tree.getElementName(&len));
+ if (tag == startTag) {
+ haveStart = true;
+ }
+ break;
+ }
+ if (!haveStart) {
+ return NO_ERROR;
+ }
+ }
+
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code != ResXMLTree::START_TAG) {
+ continue;
+ }
+ String8 tag(tree.getElementName(&len));
+
+ // If there is no '.', we'll assume that it's one of the built in names.
+ if (strchr(tag.string(), '.')) {
+ addProguardKeepRule(keep, tag, NULL,
+ layoutFile->getPrintableSource(), tree.getLineNumber());
+ } else if (tagAttrPairs != NULL) {
+ ssize_t tagIndex = tagAttrPairs->indexOfKey(tag);
+ if (tagIndex >= 0) {
+ const Vector<NamespaceAttributePair>& nsAttrVector = tagAttrPairs->valueAt(tagIndex);
+ for (size_t i = 0; i < nsAttrVector.size(); i++) {
+ const NamespaceAttributePair& nsAttr = nsAttrVector[i];
+
+ ssize_t attrIndex = tree.indexOfAttribute(nsAttr.ns, nsAttr.attr);
+ if (attrIndex < 0) {
+ // fprintf(stderr, "%s:%d: <%s> does not have attribute %s:%s.\n",
+ // layoutFile->getPrintableSource().string(), tree.getLineNumber(),
+ // tag.string(), nsAttr.ns, nsAttr.attr);
+ } else {
+ size_t len;
+ addProguardKeepRule(keep,
+ String8(tree.getAttributeStringValue(attrIndex, &len)), NULL,
+ layoutFile->getPrintableSource(), tree.getLineNumber());
+ }
+ }
+ }
+ }
+ ssize_t attrIndex = tree.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "onClick");
+ if (attrIndex >= 0) {
+ size_t len;
+ addProguardKeepMethodRule(keep,
+ String8(tree.getAttributeStringValue(attrIndex, &len)), NULL,
+ layoutFile->getPrintableSource(), tree.getLineNumber());
+ }
+ }
+
+ return NO_ERROR;
+}
+
+static void addTagAttrPair(KeyedVector<String8, Vector<NamespaceAttributePair> >* dest,
+ const char* tag, const char* ns, const char* attr) {
+ String8 tagStr(tag);
+ ssize_t index = dest->indexOfKey(tagStr);
+
+ if (index < 0) {
+ Vector<NamespaceAttributePair> vector;
+ vector.add(NamespaceAttributePair(ns, attr));
+ dest->add(tagStr, vector);
+ } else {
+ dest->editValueAt(index).add(NamespaceAttributePair(ns, attr));
+ }
+}
+
+status_t
+writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
+{
+ status_t err;
+
+ // tag:attribute pairs that should be checked in layout files.
+ KeyedVector<String8, Vector<NamespaceAttributePair> > kLayoutTagAttrPairs;
+ addTagAttrPair(&kLayoutTagAttrPairs, "view", NULL, "class");
+ addTagAttrPair(&kLayoutTagAttrPairs, "fragment", NULL, "class");
+ addTagAttrPair(&kLayoutTagAttrPairs, "fragment", RESOURCES_ANDROID_NAMESPACE, "name");
+
+ // tag:attribute pairs that should be checked in xml files.
+ KeyedVector<String8, Vector<NamespaceAttributePair> > kXmlTagAttrPairs;
+ addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, "fragment");
+ addTagAttrPair(&kXmlTagAttrPairs, "header", RESOURCES_ANDROID_NAMESPACE, "fragment");
+
+ const Vector<sp<AaptDir> >& dirs = assets->resDirs();
+ const size_t K = dirs.size();
+ for (size_t k=0; k<K; k++) {
+ const sp<AaptDir>& d = dirs.itemAt(k);
+ const String8& dirName = d->getLeaf();
+ const char* startTag = NULL;
+ const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs = NULL;
+ if ((dirName == String8("layout")) || (strncmp(dirName.string(), "layout-", 7) == 0)) {
+ tagAttrPairs = &kLayoutTagAttrPairs;
+ } else if ((dirName == String8("xml")) || (strncmp(dirName.string(), "xml-", 4) == 0)) {
+ startTag = "PreferenceScreen";
+ tagAttrPairs = &kXmlTagAttrPairs;
+ } else if ((dirName == String8("menu")) || (strncmp(dirName.string(), "menu-", 5) == 0)) {
+ startTag = "menu";
+ tagAttrPairs = NULL;
+ } else {
+ continue;
+ }
+
+ const KeyedVector<String8,sp<AaptGroup> > groups = d->getFiles();
+ const size_t N = groups.size();
+ for (size_t i=0; i<N; i++) {
+ const sp<AaptGroup>& group = groups.valueAt(i);
+ const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files = group->getFiles();
+ const size_t M = files.size();
+ for (size_t j=0; j<M; j++) {
+ err = writeProguardForXml(keep, files.valueAt(j), startTag, tagAttrPairs);
+ if (err < 0) {
+ return err;
+ }
+ }
+ }
+ }
+ // Handle the overlays
+ sp<AaptAssets> overlay = assets->getOverlay();
+ if (overlay.get()) {
+ return writeProguardForLayouts(keep, overlay);
+ }
+
+ return NO_ERROR;
+}
+
+status_t
+writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets)
+{
+ status_t err = -1;
+
+ if (!bundle->getProguardFile()) {
+ return NO_ERROR;
+ }
+
+ ProguardKeepSet keep;
+
+ err = writeProguardForAndroidManifest(&keep, assets);
+ if (err < 0) {
+ return err;
+ }
+
+ err = writeProguardForLayouts(&keep, assets);
+ if (err < 0) {
+ return err;
+ }
+
+ FILE* fp = fopen(bundle->getProguardFile(), "w+");
+ if (fp == NULL) {
+ fprintf(stderr, "ERROR: Unable to open class file %s: %s\n",
+ bundle->getProguardFile(), strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+
+ const KeyedVector<String8, SortedVector<String8> >& rules = keep.rules;
+ const size_t N = rules.size();
+ for (size_t i=0; i<N; i++) {
+ const SortedVector<String8>& locations = rules.valueAt(i);
+ const size_t M = locations.size();
+ for (size_t j=0; j<M; j++) {
+ fprintf(fp, "# %s\n", locations.itemAt(j).string());
+ }
+ fprintf(fp, "%s\n\n", rules.keyAt(i).string());
+ }
+ fclose(fp);
+
+ return err;
+}
+
+// Loops through the string paths and writes them to the file pointer
+// Each file path is written on its own line with a terminating backslash.
+status_t writePathsToFile(const sp<FilePathStore>& files, FILE* fp)
+{
+ status_t deps = -1;
+ for (size_t file_i = 0; file_i < files->size(); ++file_i) {
+ // Add the full file path to the dependency file
+ fprintf(fp, "%s \\\n", files->itemAt(file_i).string());
+ deps++;
+ }
+ return deps;
+}
+
+status_t
+writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets, FILE* fp, bool includeRaw)
+{
+ status_t deps = -1;
+ deps += writePathsToFile(assets->getFullResPaths(), fp);
+ if (includeRaw) {
+ deps += writePathsToFile(assets->getFullAssetPaths(), fp);
+ }
+ return deps;
+}
diff --git a/tools/aapt/ResourceFilter.cpp b/tools/aapt/ResourceFilter.cpp
new file mode 100644
index 0000000..8cfd2a5
--- /dev/null
+++ b/tools/aapt/ResourceFilter.cpp
@@ -0,0 +1,112 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#include "ResourceFilter.h"
+
+status_t
+ResourceFilter::parse(const char* arg)
+{
+ if (arg == NULL) {
+ return 0;
+ }
+
+ const char* p = arg;
+ const char* q;
+
+ while (true) {
+ q = strchr(p, ',');
+ if (q == NULL) {
+ q = p + strlen(p);
+ }
+
+ String8 part(p, q-p);
+
+ if (part == "zz_ZZ") {
+ mContainsPseudo = true;
+ }
+ int axis;
+ uint32_t value;
+ if (AaptGroupEntry::parseNamePart(part, &axis, &value)) {
+ fprintf(stderr, "Invalid configuration: %s\n", arg);
+ fprintf(stderr, " ");
+ for (int i=0; i<p-arg; i++) {
+ fprintf(stderr, " ");
+ }
+ for (int i=0; i<q-p; i++) {
+ fprintf(stderr, "^");
+ }
+ fprintf(stderr, "\n");
+ return 1;
+ }
+
+ ssize_t index = mData.indexOfKey(axis);
+ if (index < 0) {
+ mData.add(axis, SortedVector<uint32_t>());
+ }
+ SortedVector<uint32_t>& sv = mData.editValueFor(axis);
+ sv.add(value);
+ // if it's a locale with a region, also match an unmodified locale of the
+ // same language
+ if (axis == AXIS_LANGUAGE) {
+ if (value & 0xffff0000) {
+ sv.add(value & 0x0000ffff);
+ }
+ }
+ p = q;
+ if (!*p) break;
+ p++;
+ }
+
+ return NO_ERROR;
+}
+
+bool
+ResourceFilter::isEmpty() const
+{
+ return mData.size() == 0;
+}
+
+bool
+ResourceFilter::match(int axis, uint32_t value) const
+{
+ if (value == 0) {
+ // they didn't specify anything so take everything
+ return true;
+ }
+ ssize_t index = mData.indexOfKey(axis);
+ if (index < 0) {
+ // we didn't request anything on this axis so take everything
+ return true;
+ }
+ const SortedVector<uint32_t>& sv = mData.valueAt(index);
+ return sv.indexOf(value) >= 0;
+}
+
+bool
+ResourceFilter::match(int axis, const ResTable_config& config) const
+{
+ return match(axis, AaptGroupEntry::getConfigValueForAxis(config, axis));
+}
+
+bool
+ResourceFilter::match(const ResTable_config& config) const
+{
+ for (int i=AXIS_START; i<=AXIS_END; i++) {
+ if (!match(i, AaptGroupEntry::getConfigValueForAxis(config, i))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+const SortedVector<uint32_t>* ResourceFilter::configsForAxis(int axis) const
+{
+ ssize_t index = mData.indexOfKey(axis);
+ if (index < 0) {
+ return NULL;
+ }
+ return &mData.valueAt(index);
+}
diff --git a/tools/aapt/ResourceFilter.h b/tools/aapt/ResourceFilter.h
new file mode 100644
index 0000000..647b7bb
--- /dev/null
+++ b/tools/aapt/ResourceFilter.h
@@ -0,0 +1,33 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef RESOURCE_FILTER_H
+#define RESOURCE_FILTER_H
+
+#include "AaptAssets.h"
+
+/**
+ * Implements logic for parsing and handling "-c" and "--preferred-configurations"
+ * options.
+ */
+class ResourceFilter
+{
+public:
+ ResourceFilter() : mData(), mContainsPseudo(false) {}
+ status_t parse(const char* arg);
+ bool isEmpty() const;
+ bool match(int axis, uint32_t value) const;
+ bool match(int axis, const ResTable_config& config) const;
+ bool match(const ResTable_config& config) const;
+ const SortedVector<uint32_t>* configsForAxis(int axis) const;
+ inline bool containsPseudo() const { return mContainsPseudo; }
+
+private:
+ KeyedVector<int,SortedVector<uint32_t> > mData;
+ bool mContainsPseudo;
+};
+
+#endif
diff --git a/tools/aapt/ResourceIdCache.cpp b/tools/aapt/ResourceIdCache.cpp
new file mode 100644
index 0000000..e03f4f6
--- /dev/null
+++ b/tools/aapt/ResourceIdCache.cpp
@@ -0,0 +1,107 @@
+//
+// Copyright 2012 The Android Open Source Project
+//
+// Manage a resource ID cache.
+
+#define LOG_TAG "ResourceIdCache"
+
+#include <utils/String16.h>
+#include <utils/Log.h>
+#include "ResourceIdCache.h"
+#include <map>
+using namespace std;
+
+
+static size_t mHits = 0;
+static size_t mMisses = 0;
+static size_t mCollisions = 0;
+
+static const size_t MAX_CACHE_ENTRIES = 2048;
+static const android::String16 TRUE16("1");
+static const android::String16 FALSE16("0");
+
+struct CacheEntry {
+ // concatenation of the relevant strings into a single instance
+ android::String16 hashedName;
+ uint32_t id;
+
+ CacheEntry() {}
+ CacheEntry(const android::String16& name, uint32_t resId) : hashedName(name), id(resId) { }
+};
+
+static map< uint32_t, CacheEntry > mIdMap;
+
+
+// djb2; reasonable choice for strings when collisions aren't particularly important
+static inline uint32_t hashround(uint32_t hash, int c) {
+ return ((hash << 5) + hash) + c; /* hash * 33 + c */
+}
+
+static uint32_t hash(const android::String16& hashableString) {
+ uint32_t hash = 5381;
+ const char16_t* str = hashableString.string();
+ while (int c = *str++) hash = hashround(hash, c);
+ return hash;
+}
+
+namespace android {
+
+static inline String16 makeHashableName(const android::String16& package,
+ const android::String16& type,
+ const android::String16& name,
+ bool onlyPublic) {
+ String16 hashable = String16(name);
+ hashable += type;
+ hashable += package;
+ hashable += (onlyPublic ? TRUE16 : FALSE16);
+ return hashable;
+}
+
+uint32_t ResourceIdCache::lookup(const android::String16& package,
+ const android::String16& type,
+ const android::String16& name,
+ bool onlyPublic) {
+ const String16 hashedName = makeHashableName(package, type, name, onlyPublic);
+ const uint32_t hashcode = hash(hashedName);
+ map<uint32_t, CacheEntry>::iterator item = mIdMap.find(hashcode);
+ if (item == mIdMap.end()) {
+ // cache miss
+ mMisses++;
+ return 0;
+ }
+
+ // legit match?
+ if (hashedName == (*item).second.hashedName) {
+ mHits++;
+ return (*item).second.id;
+ }
+
+ // collision
+ mCollisions++;
+ mIdMap.erase(hashcode);
+ return 0;
+}
+
+// returns the resource ID being stored, for callsite convenience
+uint32_t ResourceIdCache::store(const android::String16& package,
+ const android::String16& type,
+ const android::String16& name,
+ bool onlyPublic,
+ uint32_t resId) {
+ if (mIdMap.size() < MAX_CACHE_ENTRIES) {
+ const String16 hashedName = makeHashableName(package, type, name, onlyPublic);
+ const uint32_t hashcode = hash(hashedName);
+ mIdMap[hashcode] = CacheEntry(hashedName, resId);
+ }
+ return resId;
+}
+
+void ResourceIdCache::dump() {
+ printf("ResourceIdCache dump:\n");
+ printf("Size: %ld\n", mIdMap.size());
+ printf("Hits: %ld\n", mHits);
+ printf("Misses: %ld\n", mMisses);
+ printf("(Collisions: %ld)\n", mCollisions);
+}
+
+}
diff --git a/tools/aapt/ResourceIdCache.h b/tools/aapt/ResourceIdCache.h
new file mode 100644
index 0000000..65f7781
--- /dev/null
+++ b/tools/aapt/ResourceIdCache.h
@@ -0,0 +1,30 @@
+//
+// Copyright 2012 The Android Open Source Project
+//
+// Manage a resource ID cache.
+
+#ifndef RESOURCE_ID_CACHE_H
+#define RESOURCE_ID_CACHE_H
+
+namespace android {
+class android::String16;
+
+class ResourceIdCache {
+public:
+ static uint32_t lookup(const android::String16& package,
+ const android::String16& type,
+ const android::String16& name,
+ bool onlyPublic);
+
+ static uint32_t store(const android::String16& package,
+ const android::String16& type,
+ const android::String16& name,
+ bool onlyPublic,
+ uint32_t resId);
+
+ static void dump(void);
+};
+
+}
+
+#endif
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
new file mode 100644
index 0000000..02a74b1
--- /dev/null
+++ b/tools/aapt/ResourceTable.cpp
@@ -0,0 +1,3982 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#include "ResourceTable.h"
+
+#include "XMLNode.h"
+#include "ResourceFilter.h"
+#include "ResourceIdCache.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/ByteOrder.h>
+#include <stdarg.h>
+
+#define NOISY(x) //x
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+ const sp<AaptFile>& target,
+ ResourceTable* table,
+ int options)
+{
+ sp<XMLNode> root = XMLNode::parse(target);
+ if (root == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ return compileXmlFile(assets, root, target, table, options);
+}
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+ const sp<AaptFile>& target,
+ const sp<AaptFile>& outTarget,
+ ResourceTable* table,
+ int options)
+{
+ sp<XMLNode> root = XMLNode::parse(target);
+ if (root == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ return compileXmlFile(assets, root, outTarget, table, options);
+}
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+ const sp<XMLNode>& root,
+ const sp<AaptFile>& target,
+ ResourceTable* table,
+ int options)
+{
+ if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) {
+ root->removeWhitespace(true, NULL);
+ } else if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) {
+ root->removeWhitespace(false, NULL);
+ }
+
+ if ((options&XML_COMPILE_UTF8) != 0) {
+ root->setUTF8(true);
+ }
+
+ bool hasErrors = false;
+
+ if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
+ status_t err = root->assignResourceIds(assets, table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ status_t err = root->parseValues(assets, table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+
+ if (hasErrors) {
+ return UNKNOWN_ERROR;
+ }
+
+ NOISY(printf("Input XML Resource:\n"));
+ NOISY(root->print());
+ err = root->flatten(target,
+ (options&XML_COMPILE_STRIP_COMMENTS) != 0,
+ (options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ NOISY(printf("Output XML Resource:\n"));
+ NOISY(ResXMLTree tree;
+ tree.setTo(target->getData(), target->getSize());
+ printXMLBlock(&tree));
+
+ target->setCompressionMethod(ZipEntry::kCompressDeflated);
+
+ return err;
+}
+
+#undef NOISY
+#define NOISY(x) //x
+
+struct flag_entry
+{
+ const char16_t* name;
+ size_t nameLen;
+ uint32_t value;
+ const char* description;
+};
+
+static const char16_t referenceArray[] =
+ { 'r', 'e', 'f', 'e', 'r', 'e', 'n', 'c', 'e' };
+static const char16_t stringArray[] =
+ { 's', 't', 'r', 'i', 'n', 'g' };
+static const char16_t integerArray[] =
+ { 'i', 'n', 't', 'e', 'g', 'e', 'r' };
+static const char16_t booleanArray[] =
+ { 'b', 'o', 'o', 'l', 'e', 'a', 'n' };
+static const char16_t colorArray[] =
+ { 'c', 'o', 'l', 'o', 'r' };
+static const char16_t floatArray[] =
+ { 'f', 'l', 'o', 'a', 't' };
+static const char16_t dimensionArray[] =
+ { 'd', 'i', 'm', 'e', 'n', 's', 'i', 'o', 'n' };
+static const char16_t fractionArray[] =
+ { 'f', 'r', 'a', 'c', 't', 'i', 'o', 'n' };
+static const char16_t enumArray[] =
+ { 'e', 'n', 'u', 'm' };
+static const char16_t flagsArray[] =
+ { 'f', 'l', 'a', 'g', 's' };
+
+static const flag_entry gFormatFlags[] = {
+ { referenceArray, sizeof(referenceArray)/2, ResTable_map::TYPE_REFERENCE,
+ "a reference to another resource, in the form \"<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>\"\n"
+ "or to a theme attribute in the form \"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\"."},
+ { stringArray, sizeof(stringArray)/2, ResTable_map::TYPE_STRING,
+ "a string value, using '\\\\;' to escape characters such as '\\\\n' or '\\\\uxxxx' for a unicode character." },
+ { integerArray, sizeof(integerArray)/2, ResTable_map::TYPE_INTEGER,
+ "an integer value, such as \"<code>100</code>\"." },
+ { booleanArray, sizeof(booleanArray)/2, ResTable_map::TYPE_BOOLEAN,
+ "a boolean value, either \"<code>true</code>\" or \"<code>false</code>\"." },
+ { colorArray, sizeof(colorArray)/2, ResTable_map::TYPE_COLOR,
+ "a color value, in the form of \"<code>#<i>rgb</i></code>\", \"<code>#<i>argb</i></code>\",\n"
+ "\"<code>#<i>rrggbb</i></code>\", or \"<code>#<i>aarrggbb</i></code>\"." },
+ { floatArray, sizeof(floatArray)/2, ResTable_map::TYPE_FLOAT,
+ "a floating point value, such as \"<code>1.2</code>\"."},
+ { dimensionArray, sizeof(dimensionArray)/2, ResTable_map::TYPE_DIMENSION,
+ "a dimension value, which is a floating point number appended with a unit such as \"<code>14.5sp</code>\".\n"
+ "Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),\n"
+ "in (inches), mm (millimeters)." },
+ { fractionArray, sizeof(fractionArray)/2, ResTable_map::TYPE_FRACTION,
+ "a fractional value, which is a floating point number appended with either % or %p, such as \"<code>14.5%</code>\".\n"
+ "The % suffix always means a percentage of the base size; the optional %p suffix provides a size relative to\n"
+ "some parent container." },
+ { enumArray, sizeof(enumArray)/2, ResTable_map::TYPE_ENUM, NULL },
+ { flagsArray, sizeof(flagsArray)/2, ResTable_map::TYPE_FLAGS, NULL },
+ { NULL, 0, 0, NULL }
+};
+
+static const char16_t suggestedArray[] = { 's', 'u', 'g', 'g', 'e', 's', 't', 'e', 'd' };
+
+static const flag_entry l10nRequiredFlags[] = {
+ { suggestedArray, sizeof(suggestedArray)/2, ResTable_map::L10N_SUGGESTED, NULL },
+ { NULL, 0, 0, NULL }
+};
+
+static const char16_t nulStr[] = { 0 };
+
+static uint32_t parse_flags(const char16_t* str, size_t len,
+ const flag_entry* flags, bool* outError = NULL)
+{
+ while (len > 0 && isspace(*str)) {
+ str++;
+ len--;
+ }
+ while (len > 0 && isspace(str[len-1])) {
+ len--;
+ }
+
+ const char16_t* const end = str + len;
+ uint32_t value = 0;
+
+ while (str < end) {
+ const char16_t* div = str;
+ while (div < end && *div != '|') {
+ div++;
+ }
+
+ const flag_entry* cur = flags;
+ while (cur->name) {
+ if (strzcmp16(cur->name, cur->nameLen, str, div-str) == 0) {
+ value |= cur->value;
+ break;
+ }
+ cur++;
+ }
+
+ if (!cur->name) {
+ if (outError) *outError = true;
+ return 0;
+ }
+
+ str = div < end ? div+1 : div;
+ }
+
+ if (outError) *outError = false;
+ return value;
+}
+
+static String16 mayOrMust(int type, int flags)
+{
+ if ((type&(~flags)) == 0) {
+ return String16("<p>Must");
+ }
+
+ return String16("<p>May");
+}
+
+static void appendTypeInfo(ResourceTable* outTable, const String16& pkg,
+ const String16& typeName, const String16& ident, int type,
+ const flag_entry* flags)
+{
+ bool hadType = false;
+ while (flags->name) {
+ if ((type&flags->value) != 0 && flags->description != NULL) {
+ String16 fullMsg(mayOrMust(type, flags->value));
+ fullMsg.append(String16(" be "));
+ fullMsg.append(String16(flags->description));
+ outTable->appendTypeComment(pkg, typeName, ident, fullMsg);
+ hadType = true;
+ }
+ flags++;
+ }
+ if (hadType && (type&ResTable_map::TYPE_REFERENCE) == 0) {
+ outTable->appendTypeComment(pkg, typeName, ident,
+ String16("<p>This may also be a reference to a resource (in the form\n"
+ "\"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>\") or\n"
+ "theme attribute (in the form\n"
+ "\"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>\")\n"
+ "containing a value of this type."));
+ }
+}
+
+struct PendingAttribute
+{
+ const String16 myPackage;
+ const SourcePos sourcePos;
+ const bool appendComment;
+ int32_t type;
+ String16 ident;
+ String16 comment;
+ bool hasErrors;
+ bool added;
+
+ PendingAttribute(String16 _package, const sp<AaptFile>& in,
+ ResXMLTree& block, bool _appendComment)
+ : myPackage(_package)
+ , sourcePos(in->getPrintableSource(), block.getLineNumber())
+ , appendComment(_appendComment)
+ , type(ResTable_map::TYPE_ANY)
+ , hasErrors(false)
+ , added(false)
+ {
+ }
+
+ status_t createIfNeeded(ResourceTable* outTable)
+ {
+ if (added || hasErrors) {
+ return NO_ERROR;
+ }
+ added = true;
+
+ String16 attr16("attr");
+
+ if (outTable->hasBagOrEntry(myPackage, attr16, ident)) {
+ sourcePos.error("Attribute \"%s\" has already been defined\n",
+ String8(ident).string());
+ hasErrors = true;
+ return UNKNOWN_ERROR;
+ }
+
+ char numberStr[16];
+ sprintf(numberStr, "%d", type);
+ status_t err = outTable->addBag(sourcePos, myPackage,
+ attr16, ident, String16(""),
+ String16("^type"),
+ String16(numberStr), NULL, NULL);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ return err;
+ }
+ outTable->appendComment(myPackage, attr16, ident, comment, appendComment);
+ //printf("Attribute %s comment: %s\n", String8(ident).string(),
+ // String8(comment).string());
+ return err;
+ }
+};
+
+static status_t compileAttribute(const sp<AaptFile>& in,
+ ResXMLTree& block,
+ const String16& myPackage,
+ ResourceTable* outTable,
+ String16* outIdent = NULL,
+ bool inStyleable = false)
+{
+ PendingAttribute attr(myPackage, in, block, inStyleable);
+
+ const String16 attr16("attr");
+ const String16 id16("id");
+
+ // Attribute type constants.
+ const String16 enum16("enum");
+ const String16 flag16("flag");
+
+ ResXMLTree::event_code_t code;
+ size_t len;
+ status_t err;
+
+ ssize_t identIdx = block.indexOfAttribute(NULL, "name");
+ if (identIdx >= 0) {
+ attr.ident = String16(block.getAttributeStringValue(identIdx, &len));
+ if (outIdent) {
+ *outIdent = attr.ident;
+ }
+ } else {
+ attr.sourcePos.error("A 'name' attribute is required for <attr>\n");
+ attr.hasErrors = true;
+ }
+
+ attr.comment = String16(
+ block.getComment(&len) ? block.getComment(&len) : nulStr);
+
+ ssize_t typeIdx = block.indexOfAttribute(NULL, "format");
+ if (typeIdx >= 0) {
+ String16 typeStr = String16(block.getAttributeStringValue(typeIdx, &len));
+ attr.type = parse_flags(typeStr.string(), typeStr.size(), gFormatFlags);
+ if (attr.type == 0) {
+ attr.sourcePos.error("Tag <attr> 'format' attribute value \"%s\" not valid\n",
+ String8(typeStr).string());
+ attr.hasErrors = true;
+ }
+ attr.createIfNeeded(outTable);
+ } else if (!inStyleable) {
+ // Attribute definitions outside of styleables always define the
+ // attribute as a generic value.
+ attr.createIfNeeded(outTable);
+ }
+
+ //printf("Attribute %s: type=0x%08x\n", String8(attr.ident).string(), attr.type);
+
+ ssize_t minIdx = block.indexOfAttribute(NULL, "min");
+ if (minIdx >= 0) {
+ String16 val = String16(block.getAttributeStringValue(minIdx, &len));
+ if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
+ attr.sourcePos.error("Tag <attr> 'min' attribute must be a number, not \"%s\"\n",
+ String8(val).string());
+ attr.hasErrors = true;
+ }
+ attr.createIfNeeded(outTable);
+ if (!attr.hasErrors) {
+ err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
+ String16(""), String16("^min"), String16(val), NULL, NULL);
+ if (err != NO_ERROR) {
+ attr.hasErrors = true;
+ }
+ }
+ }
+
+ ssize_t maxIdx = block.indexOfAttribute(NULL, "max");
+ if (maxIdx >= 0) {
+ String16 val = String16(block.getAttributeStringValue(maxIdx, &len));
+ if (!ResTable::stringToInt(val.string(), val.size(), NULL)) {
+ attr.sourcePos.error("Tag <attr> 'max' attribute must be a number, not \"%s\"\n",
+ String8(val).string());
+ attr.hasErrors = true;
+ }
+ attr.createIfNeeded(outTable);
+ if (!attr.hasErrors) {
+ err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
+ String16(""), String16("^max"), String16(val), NULL, NULL);
+ attr.hasErrors = true;
+ }
+ }
+
+ if ((minIdx >= 0 || maxIdx >= 0) && (attr.type&ResTable_map::TYPE_INTEGER) == 0) {
+ attr.sourcePos.error("Tag <attr> must have format=integer attribute if using max or min\n");
+ attr.hasErrors = true;
+ }
+
+ ssize_t l10nIdx = block.indexOfAttribute(NULL, "localization");
+ if (l10nIdx >= 0) {
+ const uint16_t* str = block.getAttributeStringValue(l10nIdx, &len);
+ bool error;
+ uint32_t l10n_required = parse_flags(str, len, l10nRequiredFlags, &error);
+ if (error) {
+ attr.sourcePos.error("Tag <attr> 'localization' attribute value \"%s\" not valid\n",
+ String8(str).string());
+ attr.hasErrors = true;
+ }
+ attr.createIfNeeded(outTable);
+ if (!attr.hasErrors) {
+ char buf[11];
+ sprintf(buf, "%d", l10n_required);
+ err = outTable->addBag(attr.sourcePos, myPackage, attr16, attr.ident,
+ String16(""), String16("^l10n"), String16(buf), NULL, NULL);
+ if (err != NO_ERROR) {
+ attr.hasErrors = true;
+ }
+ }
+ }
+
+ String16 enumOrFlagsComment;
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ uint32_t localType = 0;
+ if (strcmp16(block.getElementName(&len), enum16.string()) == 0) {
+ localType = ResTable_map::TYPE_ENUM;
+ } else if (strcmp16(block.getElementName(&len), flag16.string()) == 0) {
+ localType = ResTable_map::TYPE_FLAGS;
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("Tag <%s> can not appear inside <attr>, only <enum> or <flag>\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+
+ attr.createIfNeeded(outTable);
+
+ if (attr.type == ResTable_map::TYPE_ANY) {
+ // No type was explicitly stated, so supplying enum tags
+ // implicitly creates an enum or flag.
+ attr.type = 0;
+ }
+
+ if ((attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) == 0) {
+ // Wasn't originally specified as an enum, so update its type.
+ attr.type |= localType;
+ if (!attr.hasErrors) {
+ char numberStr[16];
+ sprintf(numberStr, "%d", attr.type);
+ err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
+ myPackage, attr16, attr.ident, String16(""),
+ String16("^type"), String16(numberStr), NULL, NULL, true);
+ if (err != NO_ERROR) {
+ attr.hasErrors = true;
+ }
+ }
+ } else if ((uint32_t)(attr.type&(ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS)) != localType) {
+ if (localType == ResTable_map::TYPE_ENUM) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("<enum> attribute can not be used inside a flags format\n");
+ attr.hasErrors = true;
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("<flag> attribute can not be used inside a enum format\n");
+ attr.hasErrors = true;
+ }
+ }
+
+ String16 itemIdent;
+ ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
+ if (itemIdentIdx >= 0) {
+ itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("A 'name' attribute is required for <enum> or <flag>\n");
+ attr.hasErrors = true;
+ }
+
+ String16 value;
+ ssize_t valueIdx = block.indexOfAttribute(NULL, "value");
+ if (valueIdx >= 0) {
+ value = String16(block.getAttributeStringValue(valueIdx, &len));
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("A 'value' attribute is required for <enum> or <flag>\n");
+ attr.hasErrors = true;
+ }
+ if (!attr.hasErrors && !ResTable::stringToInt(value.string(), value.size(), NULL)) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("Tag <enum> or <flag> 'value' attribute must be a number,"
+ " not \"%s\"\n",
+ String8(value).string());
+ attr.hasErrors = true;
+ }
+
+ // Make sure an id is defined for this enum/flag identifier...
+ if (!attr.hasErrors && !outTable->hasBagOrEntry(itemIdent, &id16, &myPackage)) {
+ err = outTable->startBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
+ myPackage, id16, itemIdent, String16(), NULL);
+ if (err != NO_ERROR) {
+ attr.hasErrors = true;
+ }
+ }
+
+ if (!attr.hasErrors) {
+ if (enumOrFlagsComment.size() == 0) {
+ enumOrFlagsComment.append(mayOrMust(attr.type,
+ ResTable_map::TYPE_ENUM|ResTable_map::TYPE_FLAGS));
+ enumOrFlagsComment.append((attr.type&ResTable_map::TYPE_ENUM)
+ ? String16(" be one of the following constant values.")
+ : String16(" be one or more (separated by '|') of the following constant values."));
+ enumOrFlagsComment.append(String16("</p>\n<table>\n"
+ "<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<colgroup align=\"left\" />\n"
+ "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>"));
+ }
+
+ enumOrFlagsComment.append(String16("\n<tr><td><code>"));
+ enumOrFlagsComment.append(itemIdent);
+ enumOrFlagsComment.append(String16("</code></td><td>"));
+ enumOrFlagsComment.append(value);
+ enumOrFlagsComment.append(String16("</td><td>"));
+ if (block.getComment(&len)) {
+ enumOrFlagsComment.append(String16(block.getComment(&len)));
+ }
+ enumOrFlagsComment.append(String16("</td></tr>"));
+
+ err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()),
+ myPackage,
+ attr16, attr.ident, String16(""),
+ itemIdent, value, NULL, NULL, false, true);
+ if (err != NO_ERROR) {
+ attr.hasErrors = true;
+ }
+ }
+ } else if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
+ break;
+ }
+ if ((attr.type&ResTable_map::TYPE_ENUM) != 0) {
+ if (strcmp16(block.getElementName(&len), enum16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("Found tag </%s> where </enum> is expected\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+ } else {
+ if (strcmp16(block.getElementName(&len), flag16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber())
+ .error("Found tag </%s> where </flag> is expected\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+ }
+ }
+
+ if (!attr.hasErrors && attr.added) {
+ appendTypeInfo(outTable, myPackage, attr16, attr.ident, attr.type, gFormatFlags);
+ }
+
+ if (!attr.hasErrors && enumOrFlagsComment.size() > 0) {
+ enumOrFlagsComment.append(String16("\n</table>"));
+ outTable->appendTypeComment(myPackage, attr16, attr.ident, enumOrFlagsComment);
+ }
+
+
+ return NO_ERROR;
+}
+
+bool localeIsDefined(const ResTable_config& config)
+{
+ return config.locale == 0;
+}
+
+status_t parseAndAddBag(Bundle* bundle,
+ const sp<AaptFile>& in,
+ ResXMLTree* block,
+ const ResTable_config& config,
+ const String16& myPackage,
+ const String16& curType,
+ const String16& ident,
+ const String16& parentIdent,
+ const String16& itemIdent,
+ int32_t curFormat,
+ bool isFormatted,
+ const String16& product,
+ bool pseudolocalize,
+ const bool overwrite,
+ ResourceTable* outTable)
+{
+ status_t err;
+ const String16 item16("item");
+
+ String16 str;
+ Vector<StringPool::entry_style_span> spans;
+ err = parseStyledString(bundle, in->getPrintableSource().string(),
+ block, item16, &str, &spans, isFormatted,
+ pseudolocalize);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ NOISY(printf("Adding resource bag entry l=%c%c c=%c%c orien=%d d=%d "
+ " pid=%s, bag=%s, id=%s: %s\n",
+ config.language[0], config.language[1],
+ config.country[0], config.country[1],
+ config.orientation, config.density,
+ String8(parentIdent).string(),
+ String8(ident).string(),
+ String8(itemIdent).string(),
+ String8(str).string()));
+
+ err = outTable->addBag(SourcePos(in->getPrintableSource(), block->getLineNumber()),
+ myPackage, curType, ident, parentIdent, itemIdent, str,
+ &spans, &config, overwrite, false, curFormat);
+ return err;
+}
+
+/*
+ * Returns true if needle is one of the elements in the comma-separated list
+ * haystack, false otherwise.
+ */
+bool isInProductList(const String16& needle, const String16& haystack) {
+ const char16_t *needle2 = needle.string();
+ const char16_t *haystack2 = haystack.string();
+ size_t needlesize = needle.size();
+
+ while (*haystack2 != '\0') {
+ if (strncmp16(haystack2, needle2, needlesize) == 0) {
+ if (haystack2[needlesize] == '\0' || haystack2[needlesize] == ',') {
+ return true;
+ }
+ }
+
+ while (*haystack2 != '\0' && *haystack2 != ',') {
+ haystack2++;
+ }
+ if (*haystack2 == ',') {
+ haystack2++;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * A simple container that holds a resource type and name. It is ordered first by type then
+ * by name.
+ */
+struct type_ident_pair_t {
+ String16 type;
+ String16 ident;
+
+ type_ident_pair_t() { };
+ type_ident_pair_t(const String16& t, const String16& i) : type(t), ident(i) { }
+ type_ident_pair_t(const type_ident_pair_t& o) : type(o.type), ident(o.ident) { }
+ inline bool operator < (const type_ident_pair_t& o) const {
+ int cmp = compare_type(type, o.type);
+ if (cmp < 0) {
+ return true;
+ } else if (cmp > 0) {
+ return false;
+ } else {
+ return strictly_order_type(ident, o.ident);
+ }
+ }
+};
+
+
+status_t parseAndAddEntry(Bundle* bundle,
+ const sp<AaptFile>& in,
+ ResXMLTree* block,
+ const ResTable_config& config,
+ const String16& myPackage,
+ const String16& curType,
+ const String16& ident,
+ const String16& curTag,
+ bool curIsStyled,
+ int32_t curFormat,
+ bool isFormatted,
+ const String16& product,
+ bool pseudolocalize,
+ const bool overwrite,
+ KeyedVector<type_ident_pair_t, bool>* skippedResourceNames,
+ ResourceTable* outTable)
+{
+ status_t err;
+
+ String16 str;
+ Vector<StringPool::entry_style_span> spans;
+ err = parseStyledString(bundle, in->getPrintableSource().string(), block,
+ curTag, &str, curIsStyled ? &spans : NULL,
+ isFormatted, pseudolocalize);
+
+ if (err < NO_ERROR) {
+ return err;
+ }
+
+ /*
+ * If a product type was specified on the command line
+ * and also in the string, and the two are not the same,
+ * return without adding the string.
+ */
+
+ const char *bundleProduct = bundle->getProduct();
+ if (bundleProduct == NULL) {
+ bundleProduct = "";
+ }
+
+ if (product.size() != 0) {
+ /*
+ * If the command-line-specified product is empty, only "default"
+ * matches. Other variants are skipped. This is so generation
+ * of the R.java file when the product is not known is predictable.
+ */
+
+ if (bundleProduct[0] == '\0') {
+ if (strcmp16(String16("default").string(), product.string()) != 0) {
+ /*
+ * This string has a product other than 'default'. Do not add it,
+ * but record it so that if we do not see the same string with
+ * product 'default' or no product, then report an error.
+ */
+ skippedResourceNames->replaceValueFor(
+ type_ident_pair_t(curType, ident), true);
+ return NO_ERROR;
+ }
+ } else {
+ /*
+ * The command-line product is not empty.
+ * If the product for this string is on the command-line list,
+ * it matches. "default" also matches, but only if nothing
+ * else has matched already.
+ */
+
+ if (isInProductList(product, String16(bundleProduct))) {
+ ;
+ } else if (strcmp16(String16("default").string(), product.string()) == 0 &&
+ !outTable->hasBagOrEntry(myPackage, curType, ident, config)) {
+ ;
+ } else {
+ return NO_ERROR;
+ }
+ }
+ }
+
+ NOISY(printf("Adding resource entry l=%c%c c=%c%c orien=%d d=%d id=%s: %s\n",
+ config.language[0], config.language[1],
+ config.country[0], config.country[1],
+ config.orientation, config.density,
+ String8(ident).string(), String8(str).string()));
+
+ err = outTable->addEntry(SourcePos(in->getPrintableSource(), block->getLineNumber()),
+ myPackage, curType, ident, str, &spans, &config,
+ false, curFormat, overwrite);
+
+ return err;
+}
+
+status_t compileResourceFile(Bundle* bundle,
+ const sp<AaptAssets>& assets,
+ const sp<AaptFile>& in,
+ const ResTable_config& defParams,
+ const bool overwrite,
+ ResourceTable* outTable)
+{
+ ResXMLTree block;
+ status_t err = parseXMLResource(in, &block, false, true);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ // Top-level tag.
+ const String16 resources16("resources");
+
+ // Identifier declaration tags.
+ const String16 declare_styleable16("declare-styleable");
+ const String16 attr16("attr");
+
+ // Data creation organizational tags.
+ const String16 string16("string");
+ const String16 drawable16("drawable");
+ const String16 color16("color");
+ 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");
+ const String16 string_array16("string-array");
+ const String16 integer_array16("integer-array");
+ const String16 public16("public");
+ const String16 public_padding16("public-padding");
+ const String16 private_symbols16("private-symbols");
+ const String16 java_symbol16("java-symbol");
+ const String16 add_resource16("add-resource");
+ const String16 skip16("skip");
+ const String16 eat_comment16("eat-comment");
+
+ // Data creation tags.
+ const String16 bag16("bag");
+ const String16 item16("item");
+
+ // Attribute type constants.
+ const String16 enum16("enum");
+
+ // plural values
+ const String16 other16("other");
+ const String16 quantityOther16("^other");
+ const String16 zero16("zero");
+ const String16 quantityZero16("^zero");
+ const String16 one16("one");
+ const String16 quantityOne16("^one");
+ const String16 two16("two");
+ const String16 quantityTwo16("^two");
+ const String16 few16("few");
+ const String16 quantityFew16("^few");
+ const String16 many16("many");
+ const String16 quantityMany16("^many");
+
+ // useful attribute names and special values
+ const String16 name16("name");
+ const String16 translatable16("translatable");
+ const String16 formatted16("formatted");
+ const String16 false16("false");
+
+ const String16 myPackage(assets->getPackage());
+
+ bool hasErrors = false;
+
+ bool fileIsTranslatable = true;
+ if (strstr(in->getPrintableSource().string(), "donottranslate") != NULL) {
+ fileIsTranslatable = false;
+ }
+
+ DefaultKeyedVector<String16, uint32_t> nextPublicId(0);
+
+ // Stores the resource names that were skipped. Typically this happens when
+ // AAPT is invoked without a product specified and a resource has no
+ // 'default' product attribute.
+ KeyedVector<type_ident_pair_t, bool> skippedResourceNames;
+
+ ResXMLTree::event_code_t code;
+ do {
+ code = block.next();
+ } while (code == ResXMLTree::START_NAMESPACE);
+
+ size_t len;
+ if (code != ResXMLTree::START_TAG) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "No start tag found\n");
+ return UNKNOWN_ERROR;
+ }
+ if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Invalid start tag %s\n", String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+
+ ResTable_config curParams(defParams);
+
+ ResTable_config pseudoParams(curParams);
+ pseudoParams.language[0] = 'z';
+ pseudoParams.language[1] = 'z';
+ pseudoParams.country[0] = 'Z';
+ pseudoParams.country[1] = 'Z';
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ const String16* curTag = NULL;
+ String16 curType;
+ int32_t curFormat = ResTable_map::TYPE_ANY;
+ bool curIsBag = false;
+ bool curIsBagReplaceOnOverwrite = false;
+ bool curIsStyled = false;
+ bool curIsPseudolocalizable = false;
+ bool curIsFormatted = fileIsTranslatable;
+ bool localHasErrors = false;
+
+ if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
+ SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+
+ String16 type;
+ ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
+ if (typeIdx < 0) {
+ srcPos.error("A 'type' attribute is required for <public>\n");
+ hasErrors = localHasErrors = true;
+ }
+ type = String16(block.getAttributeStringValue(typeIdx, &len));
+
+ String16 name;
+ ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
+ if (nameIdx < 0) {
+ srcPos.error("A 'name' attribute is required for <public>\n");
+ hasErrors = localHasErrors = true;
+ }
+ name = String16(block.getAttributeStringValue(nameIdx, &len));
+
+ uint32_t ident = 0;
+ ssize_t identIdx = block.indexOfAttribute(NULL, "id");
+ if (identIdx >= 0) {
+ const char16_t* identStr = block.getAttributeStringValue(identIdx, &len);
+ Res_value identValue;
+ if (!ResTable::stringToInt(identStr, len, &identValue)) {
+ srcPos.error("Given 'id' attribute is not an integer: %s\n",
+ String8(block.getAttributeStringValue(identIdx, &len)).string());
+ hasErrors = localHasErrors = true;
+ } else {
+ ident = identValue.data;
+ nextPublicId.replaceValueFor(type, ident+1);
+ }
+ } else if (nextPublicId.indexOfKey(type) < 0) {
+ srcPos.error("No 'id' attribute supplied <public>,"
+ " and no previous id defined in this file.\n");
+ hasErrors = localHasErrors = true;
+ } else if (!localHasErrors) {
+ ident = nextPublicId.valueFor(type);
+ nextPublicId.replaceValueFor(type, ident+1);
+ }
+
+ if (!localHasErrors) {
+ err = outTable->addPublic(srcPos, myPackage, type, name, ident);
+ if (err < NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ }
+ }
+ if (!localHasErrors) {
+ sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
+ if (symbols != NULL) {
+ symbols = symbols->addNestedSymbol(String8(type), srcPos);
+ }
+ if (symbols != NULL) {
+ symbols->makeSymbolPublic(String8(name), srcPos);
+ String16 comment(
+ block.getComment(&len) ? block.getComment(&len) : nulStr);
+ symbols->appendComment(String8(name), comment, srcPos);
+ } else {
+ srcPos.error("Unable to create symbols!\n");
+ hasErrors = localHasErrors = true;
+ }
+ }
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), public16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) {
+ SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+
+ String16 type;
+ ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
+ if (typeIdx < 0) {
+ srcPos.error("A 'type' attribute is required for <public-padding>\n");
+ hasErrors = localHasErrors = true;
+ }
+ type = String16(block.getAttributeStringValue(typeIdx, &len));
+
+ String16 name;
+ ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
+ if (nameIdx < 0) {
+ srcPos.error("A 'name' attribute is required for <public-padding>\n");
+ hasErrors = localHasErrors = true;
+ }
+ name = String16(block.getAttributeStringValue(nameIdx, &len));
+
+ uint32_t start = 0;
+ ssize_t startIdx = block.indexOfAttribute(NULL, "start");
+ if (startIdx >= 0) {
+ const char16_t* startStr = block.getAttributeStringValue(startIdx, &len);
+ Res_value startValue;
+ if (!ResTable::stringToInt(startStr, len, &startValue)) {
+ srcPos.error("Given 'start' attribute is not an integer: %s\n",
+ String8(block.getAttributeStringValue(startIdx, &len)).string());
+ hasErrors = localHasErrors = true;
+ } else {
+ start = startValue.data;
+ }
+ } else if (nextPublicId.indexOfKey(type) < 0) {
+ srcPos.error("No 'start' attribute supplied <public-padding>,"
+ " and no previous id defined in this file.\n");
+ hasErrors = localHasErrors = true;
+ } else if (!localHasErrors) {
+ start = nextPublicId.valueFor(type);
+ }
+
+ uint32_t end = 0;
+ ssize_t endIdx = block.indexOfAttribute(NULL, "end");
+ if (endIdx >= 0) {
+ const char16_t* endStr = block.getAttributeStringValue(endIdx, &len);
+ Res_value endValue;
+ if (!ResTable::stringToInt(endStr, len, &endValue)) {
+ srcPos.error("Given 'end' attribute is not an integer: %s\n",
+ String8(block.getAttributeStringValue(endIdx, &len)).string());
+ hasErrors = localHasErrors = true;
+ } else {
+ end = endValue.data;
+ }
+ } else {
+ srcPos.error("No 'end' attribute supplied <public-padding>\n");
+ hasErrors = localHasErrors = true;
+ }
+
+ if (end >= start) {
+ nextPublicId.replaceValueFor(type, end+1);
+ } else {
+ srcPos.error("Padding start '%ul' is after end '%ul'\n",
+ start, end);
+ hasErrors = localHasErrors = true;
+ }
+
+ String16 comment(
+ block.getComment(&len) ? block.getComment(&len) : nulStr);
+ for (uint32_t curIdent=start; curIdent<=end; curIdent++) {
+ if (localHasErrors) {
+ break;
+ }
+ String16 curName(name);
+ char buf[64];
+ sprintf(buf, "%d", (int)(end-curIdent+1));
+ curName.append(String16(buf));
+
+ err = outTable->addEntry(srcPos, myPackage, type, curName,
+ String16("padding"), NULL, &curParams, false,
+ ResTable_map::TYPE_STRING, overwrite);
+ if (err < NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ break;
+ }
+ err = outTable->addPublic(srcPos, myPackage, type,
+ curName, curIdent);
+ if (err < NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ break;
+ }
+ sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
+ if (symbols != NULL) {
+ symbols = symbols->addNestedSymbol(String8(type), srcPos);
+ }
+ if (symbols != NULL) {
+ symbols->makeSymbolPublic(String8(curName), srcPos);
+ symbols->appendComment(String8(curName), comment, srcPos);
+ } else {
+ srcPos.error("Unable to create symbols!\n");
+ hasErrors = localHasErrors = true;
+ }
+ }
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), public_padding16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) {
+ String16 pkg;
+ ssize_t pkgIdx = block.indexOfAttribute(NULL, "package");
+ if (pkgIdx < 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'package' attribute is required for <private-symbols>\n");
+ hasErrors = localHasErrors = true;
+ }
+ pkg = String16(block.getAttributeStringValue(pkgIdx, &len));
+ if (!localHasErrors) {
+ assets->setSymbolsPrivatePackage(String8(pkg));
+ }
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), private_symbols16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) {
+ SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+
+ String16 type;
+ ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
+ if (typeIdx < 0) {
+ srcPos.error("A 'type' attribute is required for <public>\n");
+ hasErrors = localHasErrors = true;
+ }
+ type = String16(block.getAttributeStringValue(typeIdx, &len));
+
+ String16 name;
+ ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
+ if (nameIdx < 0) {
+ srcPos.error("A 'name' attribute is required for <public>\n");
+ hasErrors = localHasErrors = true;
+ }
+ name = String16(block.getAttributeStringValue(nameIdx, &len));
+
+ sp<AaptSymbols> symbols = assets->getJavaSymbolsFor(String8("R"));
+ if (symbols != NULL) {
+ symbols = symbols->addNestedSymbol(String8(type), srcPos);
+ }
+ if (symbols != NULL) {
+ symbols->makeSymbolJavaSymbol(String8(name), srcPos);
+ String16 comment(
+ block.getComment(&len) ? block.getComment(&len) : nulStr);
+ symbols->appendComment(String8(name), comment, srcPos);
+ } else {
+ srcPos.error("Unable to create symbols!\n");
+ hasErrors = localHasErrors = true;
+ }
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), java_symbol16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+
+
+ } else if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) {
+ SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+
+ String16 typeName;
+ ssize_t typeIdx = block.indexOfAttribute(NULL, "type");
+ if (typeIdx < 0) {
+ srcPos.error("A 'type' attribute is required for <add-resource>\n");
+ hasErrors = localHasErrors = true;
+ }
+ typeName = String16(block.getAttributeStringValue(typeIdx, &len));
+
+ String16 name;
+ ssize_t nameIdx = block.indexOfAttribute(NULL, "name");
+ if (nameIdx < 0) {
+ srcPos.error("A 'name' attribute is required for <add-resource>\n");
+ hasErrors = localHasErrors = true;
+ }
+ name = String16(block.getAttributeStringValue(nameIdx, &len));
+
+ outTable->canAddEntry(srcPos, myPackage, typeName, name);
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
+ SourcePos srcPos(in->getPrintableSource(), block.getLineNumber());
+
+ String16 ident;
+ ssize_t identIdx = block.indexOfAttribute(NULL, "name");
+ if (identIdx < 0) {
+ srcPos.error("A 'name' attribute is required for <declare-styleable>\n");
+ hasErrors = localHasErrors = true;
+ }
+ ident = String16(block.getAttributeStringValue(identIdx, &len));
+
+ sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
+ if (!localHasErrors) {
+ if (symbols != NULL) {
+ symbols = symbols->addNestedSymbol(String8("styleable"), srcPos);
+ }
+ sp<AaptSymbols> styleSymbols = symbols;
+ if (symbols != NULL) {
+ symbols = symbols->addNestedSymbol(String8(ident), srcPos);
+ }
+ if (symbols == NULL) {
+ srcPos.error("Unable to create symbols!\n");
+ return UNKNOWN_ERROR;
+ }
+
+ String16 comment(
+ block.getComment(&len) ? block.getComment(&len) : nulStr);
+ styleSymbols->appendComment(String8(ident), comment, srcPos);
+ } else {
+ symbols = NULL;
+ }
+
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+ } else if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), eat_comment16.string()) == 0) {
+ break;
+ }
+ }
+ }
+ continue;
+ } else if (strcmp16(block.getElementName(&len), attr16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Tag <%s> can not appear inside <declare-styleable>, only <attr>\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+
+ String16 comment(
+ block.getComment(&len) ? block.getComment(&len) : nulStr);
+ String16 itemIdent;
+ err = compileAttribute(in, block, myPackage, outTable, &itemIdent, true);
+ if (err != NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ }
+
+ if (symbols != NULL) {
+ SourcePos srcPos(String8(in->getPrintableSource()), block.getLineNumber());
+ symbols->addSymbol(String8(itemIdent), 0, srcPos);
+ symbols->appendComment(String8(itemIdent), comment, srcPos);
+ //printf("Attribute %s comment: %s\n", String8(itemIdent).string(),
+ // String8(comment).string());
+ }
+ } else if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) {
+ break;
+ }
+
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Found tag </%s> where </attr> is expected\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), attr16.string()) == 0) {
+ err = compileAttribute(in, block, myPackage, outTable, NULL);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ continue;
+
+ } else if (strcmp16(block.getElementName(&len), item16.string()) == 0) {
+ curTag = &item16;
+ ssize_t attri = block.indexOfAttribute(NULL, "type");
+ if (attri >= 0) {
+ curType = String16(block.getAttributeStringValue(attri, &len));
+ ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
+ if (formatIdx >= 0) {
+ String16 formatStr = String16(block.getAttributeStringValue(
+ formatIdx, &len));
+ curFormat = parse_flags(formatStr.string(), formatStr.size(),
+ gFormatFlags);
+ if (curFormat == 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Tag <item> 'format' attribute value \"%s\" not valid\n",
+ String8(formatStr).string());
+ hasErrors = localHasErrors = true;
+ }
+ }
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'type' attribute is required for <item>\n");
+ hasErrors = localHasErrors = true;
+ }
+ curIsStyled = true;
+ } else if (strcmp16(block.getElementName(&len), string16.string()) == 0) {
+ // Note the existence and locale of every string we process
+ char rawLocale[16];
+ curParams.getLocale(rawLocale);
+ String8 locale(rawLocale);
+ String16 name;
+ String16 translatable;
+ String16 formatted;
+
+ size_t n = block.getAttributeCount();
+ for (size_t i = 0; i < n; i++) {
+ size_t length;
+ const uint16_t* attr = block.getAttributeName(i, &length);
+ if (strcmp16(attr, name16.string()) == 0) {
+ name.setTo(block.getAttributeStringValue(i, &length));
+ } else if (strcmp16(attr, translatable16.string()) == 0) {
+ translatable.setTo(block.getAttributeStringValue(i, &length));
+ } else if (strcmp16(attr, formatted16.string()) == 0) {
+ formatted.setTo(block.getAttributeStringValue(i, &length));
+ }
+ }
+
+ if (name.size() > 0) {
+ if (translatable == false16) {
+ curIsFormatted = false;
+ // Untranslatable strings must only exist in the default [empty] locale
+ if (locale.size() > 0) {
+ fprintf(stderr, "aapt: warning: string '%s' in %s marked untranslatable but exists"
+ " in locale '%s'\n", String8(name).string(),
+ bundle->getResourceSourceDirs()[0],
+ locale.string());
+ // hasErrors = localHasErrors = true;
+ } else {
+ // Intentionally empty block:
+ //
+ // Don't add untranslatable strings to the localization table; that
+ // way if we later see localizations of them, they'll be flagged as
+ // having no default translation.
+ }
+ } else {
+ outTable->addLocalization(name, locale);
+ }
+
+ if (formatted == false16) {
+ curIsFormatted = false;
+ }
+ }
+
+ curTag = &string16;
+ curType = string16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
+ curIsStyled = true;
+ curIsPseudolocalizable = (translatable != false16);
+ } else if (strcmp16(block.getElementName(&len), drawable16.string()) == 0) {
+ curTag = &drawable16;
+ curType = drawable16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
+ } else if (strcmp16(block.getElementName(&len), color16.string()) == 0) {
+ curTag = &color16;
+ curType = color16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR;
+ } else if (strcmp16(block.getElementName(&len), bool16.string()) == 0) {
+ curTag = &bool16;
+ curType = bool16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_BOOLEAN;
+ } else if (strcmp16(block.getElementName(&len), integer16.string()) == 0) {
+ curTag = &integer16;
+ curType = integer16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
+ } else if (strcmp16(block.getElementName(&len), dimen16.string()) == 0) {
+ 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;
+ ssize_t attri = block.indexOfAttribute(NULL, "type");
+ if (attri >= 0) {
+ curType = String16(block.getAttributeStringValue(attri, &len));
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'type' attribute is required for <bag>\n");
+ hasErrors = localHasErrors = true;
+ }
+ } else if (strcmp16(block.getElementName(&len), style16.string()) == 0) {
+ curTag = &style16;
+ curType = style16;
+ curIsBag = true;
+ } else if (strcmp16(block.getElementName(&len), plurals16.string()) == 0) {
+ curTag = &plurals16;
+ curType = plurals16;
+ curIsBag = true;
+ } else if (strcmp16(block.getElementName(&len), array16.string()) == 0) {
+ curTag = &array16;
+ curType = array16;
+ curIsBag = true;
+ curIsBagReplaceOnOverwrite = true;
+ ssize_t formatIdx = block.indexOfAttribute(NULL, "format");
+ if (formatIdx >= 0) {
+ String16 formatStr = String16(block.getAttributeStringValue(
+ formatIdx, &len));
+ curFormat = parse_flags(formatStr.string(), formatStr.size(),
+ gFormatFlags);
+ if (curFormat == 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Tag <array> 'format' attribute value \"%s\" not valid\n",
+ String8(formatStr).string());
+ hasErrors = localHasErrors = true;
+ }
+ }
+ } else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) {
+ // Check whether these strings need valid formats.
+ // (simplified form of what string16 does above)
+ size_t n = block.getAttributeCount();
+
+ // Pseudolocalizable by default, unless this string array isn't
+ // translatable.
+ curIsPseudolocalizable = true;
+ for (size_t i = 0; i < n; i++) {
+ size_t length;
+ const uint16_t* attr = block.getAttributeName(i, &length);
+ if (strcmp16(attr, translatable16.string()) == 0) {
+ const uint16_t* value = block.getAttributeStringValue(i, &length);
+ if (strcmp16(value, false16.string()) == 0) {
+ curIsPseudolocalizable = false;
+ }
+ }
+
+ if (strcmp16(attr, formatted16.string()) == 0) {
+ const uint16_t* value = block.getAttributeStringValue(i, &length);
+ if (strcmp16(value, false16.string()) == 0) {
+ curIsFormatted = false;
+ }
+ }
+ }
+
+ curTag = &string_array16;
+ curType = array16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING;
+ curIsBag = true;
+ curIsBagReplaceOnOverwrite = true;
+ } else if (strcmp16(block.getElementName(&len), integer_array16.string()) == 0) {
+ curTag = &integer_array16;
+ curType = array16;
+ curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_INTEGER;
+ curIsBag = true;
+ curIsBagReplaceOnOverwrite = true;
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Found tag %s where item is expected\n",
+ String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+
+ String16 ident;
+ ssize_t identIdx = block.indexOfAttribute(NULL, "name");
+ if (identIdx >= 0) {
+ ident = String16(block.getAttributeStringValue(identIdx, &len));
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'name' attribute is required for <%s>\n",
+ String8(*curTag).string());
+ hasErrors = localHasErrors = true;
+ }
+
+ String16 product;
+ identIdx = block.indexOfAttribute(NULL, "product");
+ if (identIdx >= 0) {
+ product = String16(block.getAttributeStringValue(identIdx, &len));
+ }
+
+ String16 comment(block.getComment(&len) ? block.getComment(&len) : nulStr);
+
+ if (curIsBag) {
+ // Figure out the parent of this bag...
+ String16 parentIdent;
+ ssize_t parentIdentIdx = block.indexOfAttribute(NULL, "parent");
+ if (parentIdentIdx >= 0) {
+ parentIdent = String16(block.getAttributeStringValue(parentIdentIdx, &len));
+ } else {
+ ssize_t sep = ident.findLast('.');
+ if (sep >= 0) {
+ parentIdent.setTo(ident, sep);
+ }
+ }
+
+ if (!localHasErrors) {
+ err = outTable->startBag(SourcePos(in->getPrintableSource(),
+ block.getLineNumber()), myPackage, curType, ident,
+ parentIdent, &curParams,
+ overwrite, curIsBagReplaceOnOverwrite);
+ if (err != NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ }
+ }
+
+ ssize_t elmIndex = 0;
+ char elmIndexStr[14];
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code != ResXMLTree::BAD_DOCUMENT) {
+
+ if (code == ResXMLTree::START_TAG) {
+ if (strcmp16(block.getElementName(&len), item16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Tag <%s> can not appear inside <%s>, only <item>\n",
+ String8(block.getElementName(&len)).string(),
+ String8(*curTag).string());
+ return UNKNOWN_ERROR;
+ }
+
+ String16 itemIdent;
+ if (curType == array16) {
+ sprintf(elmIndexStr, "^index_%d", (int)elmIndex++);
+ itemIdent = String16(elmIndexStr);
+ } else if (curType == plurals16) {
+ ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "quantity");
+ if (itemIdentIdx >= 0) {
+ String16 quantity16(block.getAttributeStringValue(itemIdentIdx, &len));
+ if (quantity16 == other16) {
+ itemIdent = quantityOther16;
+ }
+ else if (quantity16 == zero16) {
+ itemIdent = quantityZero16;
+ }
+ else if (quantity16 == one16) {
+ itemIdent = quantityOne16;
+ }
+ else if (quantity16 == two16) {
+ itemIdent = quantityTwo16;
+ }
+ else if (quantity16 == few16) {
+ itemIdent = quantityFew16;
+ }
+ else if (quantity16 == many16) {
+ itemIdent = quantityMany16;
+ }
+ else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Illegal 'quantity' attribute is <item> inside <plurals>\n");
+ hasErrors = localHasErrors = true;
+ }
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'quantity' attribute is required for <item> inside <plurals>\n");
+ hasErrors = localHasErrors = true;
+ }
+ } else {
+ ssize_t itemIdentIdx = block.indexOfAttribute(NULL, "name");
+ if (itemIdentIdx >= 0) {
+ itemIdent = String16(block.getAttributeStringValue(itemIdentIdx, &len));
+ } else {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "A 'name' attribute is required for <item>\n");
+ hasErrors = localHasErrors = true;
+ }
+ }
+
+ ResXMLParser::ResXMLPosition parserPosition;
+ block.getPosition(&parserPosition);
+
+ err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType,
+ ident, parentIdent, itemIdent, curFormat, curIsFormatted,
+ product, false, overwrite, outTable);
+ if (err == NO_ERROR) {
+ if (curIsPseudolocalizable && localeIsDefined(curParams)
+ && bundle->getPseudolocalize()) {
+ // pseudolocalize here
+#if 1
+ block.setPosition(parserPosition);
+ err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage,
+ curType, ident, parentIdent, itemIdent, curFormat,
+ curIsFormatted, product, true, overwrite, outTable);
+#endif
+ }
+ }
+ if (err != NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ }
+ } else if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), curTag->string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Found tag </%s> where </%s> is expected\n",
+ String8(block.getElementName(&len)).string(),
+ String8(*curTag).string());
+ return UNKNOWN_ERROR;
+ }
+ break;
+ }
+ }
+ } else {
+ ResXMLParser::ResXMLPosition parserPosition;
+ block.getPosition(&parserPosition);
+
+ err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident,
+ *curTag, curIsStyled, curFormat, curIsFormatted,
+ product, false, overwrite, &skippedResourceNames, outTable);
+
+ if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR?
+ hasErrors = localHasErrors = true;
+ }
+ else if (err == NO_ERROR) {
+ if (curIsPseudolocalizable && localeIsDefined(curParams)
+ && bundle->getPseudolocalize()) {
+ // pseudolocalize here
+ block.setPosition(parserPosition);
+ err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType,
+ ident, *curTag, curIsStyled, curFormat,
+ curIsFormatted, product,
+ true, overwrite, &skippedResourceNames, outTable);
+ if (err != NO_ERROR) {
+ hasErrors = localHasErrors = true;
+ }
+ }
+ }
+ }
+
+#if 0
+ if (comment.size() > 0) {
+ printf("Comment for @%s:%s/%s: %s\n", String8(myPackage).string(),
+ String8(curType).string(), String8(ident).string(),
+ String8(comment).string());
+ }
+#endif
+ if (!localHasErrors) {
+ outTable->appendComment(myPackage, curType, ident, comment, false);
+ }
+ }
+ else if (code == ResXMLTree::END_TAG) {
+ if (strcmp16(block.getElementName(&len), resources16.string()) != 0) {
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Unexpected end tag %s\n", String8(block.getElementName(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+ else if (code == ResXMLTree::START_NAMESPACE || code == ResXMLTree::END_NAMESPACE) {
+ }
+ else if (code == ResXMLTree::TEXT) {
+ if (isWhitespace(block.getText(&len))) {
+ continue;
+ }
+ SourcePos(in->getPrintableSource(), block.getLineNumber()).error(
+ "Found text \"%s\" where item tag is expected\n",
+ String8(block.getText(&len)).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ // For every resource defined, there must be exist one variant with a product attribute
+ // set to 'default' (or no product attribute at all).
+ // We check to see that for every resource that was ignored because of a mismatched
+ // product attribute, some product variant of that resource was processed.
+ for (size_t i = 0; i < skippedResourceNames.size(); i++) {
+ if (skippedResourceNames[i]) {
+ const type_ident_pair_t& p = skippedResourceNames.keyAt(i);
+ if (!outTable->hasBagOrEntry(myPackage, p.type, p.ident)) {
+ const char* bundleProduct =
+ (bundle->getProduct() == NULL) ? "" : bundle->getProduct();
+ fprintf(stderr, "In resource file %s: %s\n",
+ in->getPrintableSource().string(),
+ curParams.toString().string());
+
+ fprintf(stderr, "\t%s '%s' does not match product %s.\n"
+ "\tYou may have forgotten to include a 'default' product variant"
+ " of the resource.\n",
+ String8(p.type).string(), String8(p.ident).string(),
+ bundleProduct[0] == 0 ? "default" : bundleProduct);
+ return UNKNOWN_ERROR;
+ }
+ }
+ }
+
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage)
+ : mAssetsPackage(assetsPackage), mNextPackageId(1), mHaveAppPackage(false),
+ mIsAppPackage(!bundle->getExtending()),
+ mNumLocal(0),
+ mBundle(bundle)
+{
+}
+
+status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets)
+{
+ status_t err = assets->buildIncludedResources(bundle);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ // For future reference to included resources.
+ mAssets = assets;
+
+ const ResTable& incl = assets->getIncludedResources();
+
+ // Retrieve all the packages.
+ const size_t N = incl.getBasePackageCount();
+ for (size_t phase=0; phase<2; phase++) {
+ for (size_t i=0; i<N; i++) {
+ String16 name(incl.getBasePackageName(i));
+ uint32_t id = incl.getBasePackageId(i);
+ // First time through: only add base packages (id
+ // is not 0); second time through add the other
+ // packages.
+ if (phase != 0) {
+ if (id != 0) {
+ // Skip base packages -- already one.
+ id = 0;
+ } else {
+ // Assign a dynamic id.
+ id = mNextPackageId;
+ }
+ } else if (id != 0) {
+ if (id == 127) {
+ if (mHaveAppPackage) {
+ fprintf(stderr, "Included resources have two application packages!\n");
+ return UNKNOWN_ERROR;
+ }
+ mHaveAppPackage = true;
+ }
+ if (mNextPackageId > id) {
+ fprintf(stderr, "Included base package ID %d already in use!\n", id);
+ return UNKNOWN_ERROR;
+ }
+ }
+ if (id != 0) {
+ NOISY(printf("Including package %s with ID=%d\n",
+ String8(name).string(), id));
+ sp<Package> p = new Package(name, id);
+ mPackages.add(name, p);
+ mOrderedPackages.add(p);
+
+ if (id >= mNextPackageId) {
+ mNextPackageId = id+1;
+ }
+ }
+ }
+ }
+
+ // Every resource table always has one first entry, the bag attributes.
+ const SourcePos unknown(String8("????"), 0);
+ sp<Type> attr = getType(mAssetsPackage, String16("attr"), unknown);
+
+ return NO_ERROR;
+}
+
+status_t ResourceTable::addPublic(const SourcePos& sourcePos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const uint32_t ident)
+{
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ sourcePos.error("Error declaring public resource %s/%s for included package %s\n",
+ String8(type).string(), String8(name).string(),
+ String8(package).string());
+ return UNKNOWN_ERROR;
+ }
+
+ sp<Type> t = getType(package, type, sourcePos);
+ if (t == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ return t->addPublic(sourcePos, name, ident);
+}
+
+status_t ResourceTable::addEntry(const SourcePos& sourcePos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style,
+ const ResTable_config* params,
+ const bool doSetIndex,
+ const int32_t format,
+ const bool overwrite)
+{
+ // Check for adding entries in other packages... for now we do
+ // nothing. We need to do the right thing here to support skinning.
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ return NO_ERROR;
+ }
+
+#if 0
+ if (name == String16("left")) {
+ printf("Adding entry left: file=%s, line=%d, type=%s, value=%s\n",
+ sourcePos.file.string(), sourcePos.line, String8(type).string(),
+ String8(value).string());
+ }
+#endif
+
+ sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite,
+ params, doSetIndex);
+ if (e == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ status_t err = e->setItem(sourcePos, value, style, format, overwrite);
+ if (err == NO_ERROR) {
+ mNumLocal++;
+ }
+ return err;
+}
+
+status_t ResourceTable::startBag(const SourcePos& sourcePos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& bagParent,
+ const ResTable_config* params,
+ bool overlay,
+ bool replace, bool isId)
+{
+ status_t result = NO_ERROR;
+
+ // Check for adding entries in other packages... for now we do
+ // nothing. We need to do the right thing here to support skinning.
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ return NO_ERROR;
+ }
+
+#if 0
+ if (name == String16("left")) {
+ printf("Adding bag left: file=%s, line=%d, type=%s\n",
+ sourcePos.file.striing(), sourcePos.line, String8(type).string());
+ }
+#endif
+ if (overlay && !mBundle->getAutoAddOverlay() && !hasBagOrEntry(package, type, name)) {
+ bool canAdd = false;
+ sp<Package> p = mPackages.valueFor(package);
+ if (p != NULL) {
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t != NULL) {
+ if (t->getCanAddEntries().indexOf(name) >= 0) {
+ canAdd = true;
+ }
+ }
+ }
+ if (!canAdd) {
+ sourcePos.error("Resource does not already exist in overlay at '%s'; use <add-resource> to add.\n",
+ String8(name).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+ sp<Entry> e = getEntry(package, type, name, sourcePos, overlay, params);
+ if (e == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ // If a parent is explicitly specified, set it.
+ if (bagParent.size() > 0) {
+ e->setParent(bagParent);
+ }
+
+ if ((result = e->makeItABag(sourcePos)) != NO_ERROR) {
+ return result;
+ }
+
+ if (overlay && replace) {
+ return e->emptyBag(sourcePos);
+ }
+ return result;
+}
+
+status_t ResourceTable::addBag(const SourcePos& sourcePos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& bagParent,
+ const String16& bagKey,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style,
+ const ResTable_config* params,
+ bool replace, bool isId, const int32_t format)
+{
+ // Check for adding entries in other packages... for now we do
+ // nothing. We need to do the right thing here to support skinning.
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ return NO_ERROR;
+ }
+
+#if 0
+ if (name == String16("left")) {
+ printf("Adding bag left: file=%s, line=%d, type=%s\n",
+ sourcePos.file.striing(), sourcePos.line, String8(type).string());
+ }
+#endif
+ sp<Entry> e = getEntry(package, type, name, sourcePos, replace, params);
+ if (e == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ // If a parent is explicitly specified, set it.
+ if (bagParent.size() > 0) {
+ e->setParent(bagParent);
+ }
+
+ const bool first = e->getBag().indexOfKey(bagKey) < 0;
+ status_t err = e->addToBag(sourcePos, bagKey, value, style, replace, isId, format);
+ if (err == NO_ERROR && first) {
+ mNumLocal++;
+ }
+ return err;
+}
+
+bool ResourceTable::hasBagOrEntry(const String16& package,
+ const String16& type,
+ const String16& name) const
+{
+ // First look for this in the included resources...
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ return true;
+ }
+
+ sp<Package> p = mPackages.valueFor(package);
+ if (p != NULL) {
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t != NULL) {
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c != NULL) return true;
+ }
+ }
+
+ return false;
+}
+
+bool ResourceTable::hasBagOrEntry(const String16& package,
+ const String16& type,
+ const String16& name,
+ const ResTable_config& config) const
+{
+ // First look for this in the included resources...
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ return true;
+ }
+
+ sp<Package> p = mPackages.valueFor(package);
+ if (p != NULL) {
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t != NULL) {
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c != NULL) {
+ sp<Entry> e = c->getEntries().valueFor(config);
+ if (e != NULL) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+bool ResourceTable::hasBagOrEntry(const String16& ref,
+ const String16* defType,
+ const String16* defPackage)
+{
+ String16 package, type, name;
+ if (!ResTable::expandResourceRef(ref.string(), ref.size(), &package, &type, &name,
+ defType, defPackage ? defPackage:&mAssetsPackage, NULL)) {
+ return false;
+ }
+ return hasBagOrEntry(package, type, name);
+}
+
+bool ResourceTable::appendComment(const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& comment,
+ bool onlyIfEmpty)
+{
+ if (comment.size() <= 0) {
+ return true;
+ }
+
+ sp<Package> p = mPackages.valueFor(package);
+ if (p != NULL) {
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t != NULL) {
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c != NULL) {
+ c->appendComment(comment, onlyIfEmpty);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool ResourceTable::appendTypeComment(const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& comment)
+{
+ if (comment.size() <= 0) {
+ return true;
+ }
+
+ sp<Package> p = mPackages.valueFor(package);
+ if (p != NULL) {
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t != NULL) {
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c != NULL) {
+ c->appendTypeComment(comment);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void ResourceTable::canAddEntry(const SourcePos& pos,
+ const String16& package, const String16& type, const String16& name)
+{
+ sp<Type> t = getType(package, type, pos);
+ if (t != NULL) {
+ t->canAddEntry(name);
+ }
+}
+
+size_t ResourceTable::size() const {
+ return mPackages.size();
+}
+
+size_t ResourceTable::numLocalResources() const {
+ return mNumLocal;
+}
+
+bool ResourceTable::hasResources() const {
+ return mNumLocal > 0;
+}
+
+sp<AaptFile> ResourceTable::flatten(Bundle* bundle)
+{
+ sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
+ status_t err = flatten(bundle, data);
+ return err == NO_ERROR ? data : NULL;
+}
+
+inline uint32_t ResourceTable::getResId(const sp<Package>& p,
+ const sp<Type>& t,
+ uint32_t nameId)
+{
+ return makeResId(p->getAssignedId(), t->getIndex(), nameId);
+}
+
+uint32_t ResourceTable::getResId(const String16& package,
+ const String16& type,
+ const String16& name,
+ bool onlyPublic) const
+{
+ uint32_t id = ResourceIdCache::lookup(package, type, name, onlyPublic);
+ if (id != 0) return id; // cache hit
+
+ sp<Package> p = mPackages.valueFor(package);
+ if (p == NULL) return 0;
+
+ // First look for this in the included resources...
+ uint32_t specFlags = 0;
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size(),
+ &specFlags);
+ if (rid != 0) {
+ if (onlyPublic) {
+ if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) {
+ return 0;
+ }
+ }
+
+ if (Res_INTERNALID(rid)) {
+ return ResourceIdCache::store(package, type, name, onlyPublic, rid);
+ }
+ return ResourceIdCache::store(package, type, name, onlyPublic,
+ Res_MAKEID(p->getAssignedId()-1, Res_GETTYPE(rid), Res_GETENTRY(rid)));
+ }
+
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t == NULL) return 0;
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c == NULL) return 0;
+ int32_t ei = c->getEntryIndex();
+ if (ei < 0) return 0;
+
+ return ResourceIdCache::store(package, type, name, onlyPublic,
+ getResId(p, t, ei));
+}
+
+uint32_t ResourceTable::getResId(const String16& ref,
+ const String16* defType,
+ const String16* defPackage,
+ const char** outErrorMsg,
+ bool onlyPublic) const
+{
+ String16 package, type, name;
+ bool refOnlyPublic = true;
+ if (!ResTable::expandResourceRef(
+ ref.string(), ref.size(), &package, &type, &name,
+ defType, defPackage ? defPackage:&mAssetsPackage,
+ outErrorMsg, &refOnlyPublic)) {
+ NOISY(printf("Expanding resource: ref=%s\n",
+ String8(ref).string()));
+ NOISY(printf("Expanding resource: defType=%s\n",
+ defType ? String8(*defType).string() : "NULL"));
+ NOISY(printf("Expanding resource: defPackage=%s\n",
+ defPackage ? String8(*defPackage).string() : "NULL"));
+ NOISY(printf("Expanding resource: ref=%s\n", String8(ref).string()));
+ NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=0\n",
+ String8(package).string(), String8(type).string(),
+ String8(name).string()));
+ return 0;
+ }
+ uint32_t res = getResId(package, type, name, onlyPublic && refOnlyPublic);
+ NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=%d\n",
+ String8(package).string(), String8(type).string(),
+ String8(name).string(), res));
+ if (res == 0) {
+ if (outErrorMsg)
+ *outErrorMsg = "No resource found that matches the given name";
+ }
+ return res;
+}
+
+bool ResourceTable::isValidResourceName(const String16& s)
+{
+ const char16_t* p = s.string();
+ bool first = true;
+ while (*p) {
+ if ((*p >= 'a' && *p <= 'z')
+ || (*p >= 'A' && *p <= 'Z')
+ || *p == '_'
+ || (!first && *p >= '0' && *p <= '9')) {
+ first = false;
+ p++;
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+bool ResourceTable::stringToValue(Res_value* outValue, StringPool* pool,
+ const String16& str,
+ bool preserveSpaces, bool coerceType,
+ uint32_t attrID,
+ const Vector<StringPool::entry_style_span>* style,
+ String16* outStr, void* accessorCookie,
+ uint32_t attrType, const String8* configTypeName,
+ const ConfigDescription* config)
+{
+ String16 finalStr;
+
+ bool res = true;
+ if (style == NULL || style->size() == 0) {
+ // Text is not styled so it can be any type... let's figure it out.
+ res = mAssets->getIncludedResources()
+ .stringToValue(outValue, &finalStr, str.string(), str.size(), preserveSpaces,
+ coerceType, attrID, NULL, &mAssetsPackage, this,
+ accessorCookie, attrType);
+ } else {
+ // Styled text can only be a string, and while collecting the style
+ // information we have already processed that string!
+ outValue->size = sizeof(Res_value);
+ outValue->res0 = 0;
+ outValue->dataType = outValue->TYPE_STRING;
+ outValue->data = 0;
+ finalStr = str;
+ }
+
+ if (!res) {
+ return false;
+ }
+
+ if (outValue->dataType == outValue->TYPE_STRING) {
+ // Should do better merging styles.
+ if (pool) {
+ String8 configStr;
+ if (config != NULL) {
+ configStr = config->toString();
+ } else {
+ configStr = "(null)";
+ }
+ NOISY(printf("Adding to pool string style #%d config %s: %s\n",
+ style != NULL ? style->size() : 0,
+ configStr.string(), String8(finalStr).string()));
+ if (style != NULL && style->size() > 0) {
+ outValue->data = pool->add(finalStr, *style, configTypeName, config);
+ } else {
+ outValue->data = pool->add(finalStr, true, configTypeName, config);
+ }
+ } else {
+ // Caller will fill this in later.
+ outValue->data = 0;
+ }
+
+ if (outStr) {
+ *outStr = finalStr;
+ }
+
+ }
+
+ return true;
+}
+
+uint32_t ResourceTable::getCustomResource(
+ const String16& package, const String16& type, const String16& name) const
+{
+ //printf("getCustomResource: %s %s %s\n", String8(package).string(),
+ // String8(type).string(), String8(name).string());
+ sp<Package> p = mPackages.valueFor(package);
+ if (p == NULL) return 0;
+ sp<Type> t = p->getTypes().valueFor(type);
+ if (t == NULL) return 0;
+ sp<ConfigList> c = t->getConfigs().valueFor(name);
+ if (c == NULL) return 0;
+ int32_t ei = c->getEntryIndex();
+ if (ei < 0) return 0;
+ return getResId(p, t, ei);
+}
+
+uint32_t ResourceTable::getCustomResourceWithCreation(
+ const String16& package, const String16& type, const String16& name,
+ const bool createIfNotFound)
+{
+ uint32_t resId = getCustomResource(package, type, name);
+ if (resId != 0 || !createIfNotFound) {
+ return resId;
+ }
+ String16 value("false");
+
+ status_t status = addEntry(mCurrentXmlPos, package, type, name, value, NULL, NULL, true);
+ if (status == NO_ERROR) {
+ resId = getResId(package, type, name);
+ return resId;
+ }
+ return 0;
+}
+
+uint32_t ResourceTable::getRemappedPackage(uint32_t origPackage) const
+{
+ return origPackage;
+}
+
+bool ResourceTable::getAttributeType(uint32_t attrID, uint32_t* outType)
+{
+ //printf("getAttributeType #%08x\n", attrID);
+ Res_value value;
+ if (getItemValue(attrID, ResTable_map::ATTR_TYPE, &value)) {
+ //printf("getAttributeType #%08x (%s): #%08x\n", attrID,
+ // String8(getEntry(attrID)->getName()).string(), value.data);
+ *outType = value.data;
+ return true;
+ }
+ return false;
+}
+
+bool ResourceTable::getAttributeMin(uint32_t attrID, uint32_t* outMin)
+{
+ //printf("getAttributeMin #%08x\n", attrID);
+ Res_value value;
+ if (getItemValue(attrID, ResTable_map::ATTR_MIN, &value)) {
+ *outMin = value.data;
+ return true;
+ }
+ return false;
+}
+
+bool ResourceTable::getAttributeMax(uint32_t attrID, uint32_t* outMax)
+{
+ //printf("getAttributeMax #%08x\n", attrID);
+ Res_value value;
+ if (getItemValue(attrID, ResTable_map::ATTR_MAX, &value)) {
+ *outMax = value.data;
+ return true;
+ }
+ return false;
+}
+
+uint32_t ResourceTable::getAttributeL10N(uint32_t attrID)
+{
+ //printf("getAttributeL10N #%08x\n", attrID);
+ Res_value value;
+ if (getItemValue(attrID, ResTable_map::ATTR_L10N, &value)) {
+ return value.data;
+ }
+ return ResTable_map::L10N_NOT_REQUIRED;
+}
+
+bool ResourceTable::getLocalizationSetting()
+{
+ return mBundle->getRequireLocalization();
+}
+
+void ResourceTable::reportError(void* accessorCookie, const char* fmt, ...)
+{
+ if (accessorCookie != NULL && fmt != NULL) {
+ AccessorCookie* ac = (AccessorCookie*)accessorCookie;
+ int retval=0;
+ char buf[1024];
+ va_list ap;
+ va_start(ap, fmt);
+ retval = vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+ ac->sourcePos.error("Error: %s (at '%s' with value '%s').\n",
+ buf, ac->attr.string(), ac->value.string());
+ }
+}
+
+bool ResourceTable::getAttributeKeys(
+ uint32_t attrID, Vector<String16>* outKeys)
+{
+ sp<const Entry> e = getEntry(attrID);
+ if (e != NULL) {
+ const size_t N = e->getBag().size();
+ for (size_t i=0; i<N; i++) {
+ const String16& key = e->getBag().keyAt(i);
+ if (key.size() > 0 && key.string()[0] != '^') {
+ outKeys->add(key);
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool ResourceTable::getAttributeEnum(
+ uint32_t attrID, const char16_t* name, size_t nameLen,
+ Res_value* outValue)
+{
+ //printf("getAttributeEnum #%08x %s\n", attrID, String8(name, nameLen).string());
+ String16 nameStr(name, nameLen);
+ sp<const Entry> e = getEntry(attrID);
+ if (e != NULL) {
+ const size_t N = e->getBag().size();
+ for (size_t i=0; i<N; i++) {
+ //printf("Comparing %s to %s\n", String8(name, nameLen).string(),
+ // String8(e->getBag().keyAt(i)).string());
+ if (e->getBag().keyAt(i) == nameStr) {
+ return getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, outValue);
+ }
+ }
+ }
+ return false;
+}
+
+bool ResourceTable::getAttributeFlags(
+ uint32_t attrID, const char16_t* name, size_t nameLen,
+ Res_value* outValue)
+{
+ outValue->dataType = Res_value::TYPE_INT_HEX;
+ outValue->data = 0;
+
+ //printf("getAttributeFlags #%08x %s\n", attrID, String8(name, nameLen).string());
+ String16 nameStr(name, nameLen);
+ sp<const Entry> e = getEntry(attrID);
+ if (e != NULL) {
+ const size_t N = e->getBag().size();
+
+ const char16_t* end = name + nameLen;
+ const char16_t* pos = name;
+ while (pos < end) {
+ const char16_t* start = pos;
+ while (pos < end && *pos != '|') {
+ pos++;
+ }
+
+ String16 nameStr(start, pos-start);
+ size_t i;
+ for (i=0; i<N; i++) {
+ //printf("Comparing \"%s\" to \"%s\"\n", String8(nameStr).string(),
+ // String8(e->getBag().keyAt(i)).string());
+ if (e->getBag().keyAt(i) == nameStr) {
+ Res_value val;
+ bool got = getItemValue(attrID, e->getBag().valueAt(i).bagKeyId, &val);
+ if (!got) {
+ return false;
+ }
+ //printf("Got value: 0x%08x\n", val.data);
+ outValue->data |= val.data;
+ break;
+ }
+ }
+
+ if (i >= N) {
+ // Didn't find this flag identifier.
+ return false;
+ }
+ pos++;
+ }
+
+ return true;
+ }
+ return false;
+}
+
+status_t ResourceTable::assignResourceIds()
+{
+ const size_t N = mOrderedPackages.size();
+ size_t pi;
+ status_t firstError = NO_ERROR;
+
+ // First generate all bag attributes and assign indices.
+ for (pi=0; pi<N; pi++) {
+ sp<Package> p = mOrderedPackages.itemAt(pi);
+ if (p == NULL || p->getTypes().size() == 0) {
+ // Empty, skip!
+ continue;
+ }
+
+ status_t err = p->applyPublicTypeOrder();
+ if (err != NO_ERROR && firstError == NO_ERROR) {
+ firstError = err;
+ }
+
+ // Generate attributes...
+ const size_t N = p->getOrderedTypes().size();
+ size_t ti;
+ for (ti=0; ti<N; ti++) {
+ sp<Type> t = p->getOrderedTypes().itemAt(ti);
+ if (t == NULL) {
+ continue;
+ }
+ const size_t N = t->getOrderedConfigs().size();
+ for (size_t ci=0; ci<N; ci++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+ if (c == NULL) {
+ continue;
+ }
+ const size_t N = c->getEntries().size();
+ for (size_t ei=0; ei<N; ei++) {
+ sp<Entry> e = c->getEntries().valueAt(ei);
+ if (e == NULL) {
+ continue;
+ }
+ status_t err = e->generateAttributes(this, p->getName());
+ if (err != NO_ERROR && firstError == NO_ERROR) {
+ firstError = err;
+ }
+ }
+ }
+ }
+
+ const SourcePos unknown(String8("????"), 0);
+ sp<Type> attr = p->getType(String16("attr"), unknown);
+
+ // Assign indices...
+ for (ti=0; ti<N; ti++) {
+ sp<Type> t = p->getOrderedTypes().itemAt(ti);
+ if (t == NULL) {
+ continue;
+ }
+ err = t->applyPublicEntryOrder();
+ if (err != NO_ERROR && firstError == NO_ERROR) {
+ firstError = err;
+ }
+
+ const size_t N = t->getOrderedConfigs().size();
+ t->setIndex(ti+1);
+
+ LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t,
+ "First type is not attr!");
+
+ for (size_t ei=0; ei<N; ei++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(ei);
+ if (c == NULL) {
+ continue;
+ }
+ c->setEntryIndex(ei);
+ }
+ }
+
+ // Assign resource IDs to keys in bags...
+ for (ti=0; ti<N; ti++) {
+ sp<Type> t = p->getOrderedTypes().itemAt(ti);
+ if (t == NULL) {
+ continue;
+ }
+ const size_t N = t->getOrderedConfigs().size();
+ for (size_t ci=0; ci<N; ci++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+ //printf("Ordered config #%d: %p\n", ci, c.get());
+ const size_t N = c->getEntries().size();
+ for (size_t ei=0; ei<N; ei++) {
+ sp<Entry> e = c->getEntries().valueAt(ei);
+ if (e == NULL) {
+ continue;
+ }
+ status_t err = e->assignResourceIds(this, p->getName());
+ if (err != NO_ERROR && firstError == NO_ERROR) {
+ firstError = err;
+ }
+ }
+ }
+ }
+ }
+ return firstError;
+}
+
+status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols) {
+ const size_t N = mOrderedPackages.size();
+ size_t pi;
+
+ for (pi=0; pi<N; pi++) {
+ sp<Package> p = mOrderedPackages.itemAt(pi);
+ if (p->getTypes().size() == 0) {
+ // Empty, skip!
+ continue;
+ }
+
+ const size_t N = p->getOrderedTypes().size();
+ size_t ti;
+
+ for (ti=0; ti<N; ti++) {
+ sp<Type> t = p->getOrderedTypes().itemAt(ti);
+ if (t == NULL) {
+ continue;
+ }
+ const size_t N = t->getOrderedConfigs().size();
+ sp<AaptSymbols> typeSymbols;
+ typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos());
+ for (size_t ci=0; ci<N; ci++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+ if (c == NULL) {
+ continue;
+ }
+ uint32_t rid = getResId(p, t, ci);
+ if (rid == 0) {
+ return UNKNOWN_ERROR;
+ }
+ if (Res_GETPACKAGE(rid) == (size_t)(p->getAssignedId()-1)) {
+ typeSymbols->addSymbol(String8(c->getName()), rid, c->getPos());
+
+ String16 comment(c->getComment());
+ typeSymbols->appendComment(String8(c->getName()), comment, c->getPos());
+ //printf("Type symbol [%08x] %s comment: %s\n", rid,
+ // String8(c->getName()).string(), String8(comment).string());
+ comment = c->getTypeComment();
+ typeSymbols->appendTypeComment(String8(c->getName()), comment);
+ } else {
+#if 0
+ printf("**** NO MATCH: 0x%08x vs 0x%08x\n",
+ Res_GETPACKAGE(rid), p->getAssignedId());
+#endif
+ }
+ }
+ }
+ }
+ return NO_ERROR;
+}
+
+
+void
+ResourceTable::addLocalization(const String16& name, const String8& locale)
+{
+ mLocalizations[name].insert(locale);
+}
+
+
+/*!
+ * Flag various sorts of localization problems. '+' indicates checks already implemented;
+ * '-' indicates checks that will be implemented in the future.
+ *
+ * + A localized string for which no default-locale version exists => warning
+ * + A string for which no version in an explicitly-requested locale exists => warning
+ * + A localized translation of an translateable="false" string => warning
+ * - A localized string not provided in every locale used by the table
+ */
+status_t
+ResourceTable::validateLocalizations(void)
+{
+ status_t err = NO_ERROR;
+ const String8 defaultLocale;
+
+ // For all strings...
+ for (map<String16, set<String8> >::iterator nameIter = mLocalizations.begin();
+ nameIter != mLocalizations.end();
+ nameIter++) {
+ const set<String8>& configSet = nameIter->second; // naming convenience
+
+ // Look for strings with no default localization
+ if (configSet.count(defaultLocale) == 0) {
+ fprintf(stdout, "aapt: warning: string '%s' has no default translation in %s; found:",
+ String8(nameIter->first).string(), mBundle->getResourceSourceDirs()[0]);
+ for (set<String8>::const_iterator locales = configSet.begin();
+ locales != configSet.end();
+ locales++) {
+ fprintf(stdout, " %s", (*locales).string());
+ }
+ fprintf(stdout, "\n");
+ // !!! TODO: throw an error here in some circumstances
+ }
+
+ // Check that all requested localizations are present for this string
+ if (mBundle->getConfigurations() != NULL && mBundle->getRequireLocalization()) {
+ const char* allConfigs = mBundle->getConfigurations();
+ const char* start = allConfigs;
+ const char* comma;
+
+ do {
+ String8 config;
+ comma = strchr(start, ',');
+ if (comma != NULL) {
+ config.setTo(start, comma - start);
+ start = comma + 1;
+ } else {
+ config.setTo(start);
+ }
+
+ // don't bother with the pseudolocale "zz_ZZ"
+ if (config != "zz_ZZ") {
+ if (configSet.find(config) == configSet.end()) {
+ // okay, no specific localization found. it's possible that we are
+ // requiring a specific regional localization [e.g. de_DE] but there is an
+ // available string in the generic language localization [e.g. de];
+ // consider that string to have fulfilled the localization requirement.
+ String8 region(config.string(), 2);
+ if (configSet.find(region) == configSet.end()) {
+ if (configSet.count(defaultLocale) == 0) {
+ fprintf(stdout, "aapt: warning: "
+ "**** string '%s' has no default or required localization "
+ "for '%s' in %s\n",
+ String8(nameIter->first).string(),
+ config.string(),
+ mBundle->getResourceSourceDirs()[0]);
+ }
+ }
+ }
+ }
+ } while (comma != NULL);
+ }
+ }
+
+ return err;
+}
+
+status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest)
+{
+ ResourceFilter filter;
+ status_t err = filter.parse(bundle->getConfigurations());
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ const ConfigDescription nullConfig;
+
+ const size_t N = mOrderedPackages.size();
+ size_t pi;
+
+ const static String16 mipmap16("mipmap");
+
+ bool useUTF8 = !bundle->getUTF16StringsOption();
+
+ // Iterate through all data, collecting all values (strings,
+ // references, etc).
+ StringPool valueStrings(useUTF8);
+ Vector<sp<Entry> > allEntries;
+ for (pi=0; pi<N; pi++) {
+ sp<Package> p = mOrderedPackages.itemAt(pi);
+ if (p->getTypes().size() == 0) {
+ // Empty, skip!
+ continue;
+ }
+
+ StringPool typeStrings(useUTF8);
+ StringPool keyStrings(useUTF8);
+
+ const size_t N = p->getOrderedTypes().size();
+ for (size_t ti=0; ti<N; ti++) {
+ sp<Type> t = p->getOrderedTypes().itemAt(ti);
+ if (t == NULL) {
+ typeStrings.add(String16("<empty>"), false);
+ continue;
+ }
+ const String16 typeName(t->getName());
+ typeStrings.add(typeName, false);
+
+ // This is a hack to tweak the sorting order of the final strings,
+ // to put stuff that is generally not language-specific first.
+ String8 configTypeName(typeName);
+ if (configTypeName == "drawable" || configTypeName == "layout"
+ || configTypeName == "color" || configTypeName == "anim"
+ || configTypeName == "interpolator" || configTypeName == "animator"
+ || configTypeName == "xml" || configTypeName == "menu"
+ || configTypeName == "mipmap" || configTypeName == "raw") {
+ configTypeName = "1complex";
+ } else {
+ configTypeName = "2value";
+ }
+
+ const bool filterable = (typeName != mipmap16);
+
+ const size_t N = t->getOrderedConfigs().size();
+ for (size_t ci=0; ci<N; ci++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+ if (c == NULL) {
+ continue;
+ }
+ const size_t N = c->getEntries().size();
+ for (size_t ei=0; ei<N; ei++) {
+ ConfigDescription config = c->getEntries().keyAt(ei);
+ if (filterable && !filter.match(config)) {
+ continue;
+ }
+ sp<Entry> e = c->getEntries().valueAt(ei);
+ if (e == NULL) {
+ continue;
+ }
+ e->setNameIndex(keyStrings.add(e->getName(), true));
+
+ // If this entry has no values for other configs,
+ // and is the default config, then it is special. Otherwise
+ // we want to add it with the config info.
+ ConfigDescription* valueConfig = NULL;
+ if (N != 1 || config == nullConfig) {
+ valueConfig = &config;
+ }
+
+ status_t err = e->prepareFlatten(&valueStrings, this,
+ &configTypeName, &config);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ allEntries.add(e);
+ }
+ }
+ }
+
+ p->setTypeStrings(typeStrings.createStringBlock());
+ p->setKeyStrings(keyStrings.createStringBlock());
+ }
+
+ if (bundle->getOutputAPKFile() != NULL) {
+ // Now we want to sort the value strings for better locality. This will
+ // cause the positions of the strings to change, so we need to go back
+ // through out resource entries and update them accordingly. Only need
+ // to do this if actually writing the output file.
+ valueStrings.sortByConfig();
+ for (pi=0; pi<allEntries.size(); pi++) {
+ allEntries[pi]->remapStringValue(&valueStrings);
+ }
+ }
+
+ ssize_t strAmt = 0;
+
+ // Now build the array of package chunks.
+ Vector<sp<AaptFile> > flatPackages;
+ for (pi=0; pi<N; pi++) {
+ sp<Package> p = mOrderedPackages.itemAt(pi);
+ if (p->getTypes().size() == 0) {
+ // Empty, skip!
+ continue;
+ }
+
+ const size_t N = p->getTypeStrings().size();
+
+ const size_t baseSize = sizeof(ResTable_package);
+
+ // Start the package data.
+ sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
+ ResTable_package* header = (ResTable_package*)data->editData(baseSize);
+ if (header == NULL) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_package\n");
+ return NO_MEMORY;
+ }
+ memset(header, 0, sizeof(*header));
+ header->header.type = htods(RES_TABLE_PACKAGE_TYPE);
+ header->header.headerSize = htods(sizeof(*header));
+ header->id = htodl(p->getAssignedId());
+ strcpy16_htod(header->name, p->getName().string());
+
+ // Write the string blocks.
+ const size_t typeStringsStart = data->getSize();
+ sp<AaptFile> strFile = p->getTypeStringsData();
+ ssize_t amt = data->writeData(strFile->getData(), strFile->getSize());
+ #if PRINT_STRING_METRICS
+ fprintf(stderr, "**** type strings: %d\n", amt);
+ #endif
+ strAmt += amt;
+ if (amt < 0) {
+ return amt;
+ }
+ const size_t keyStringsStart = data->getSize();
+ strFile = p->getKeyStringsData();
+ amt = data->writeData(strFile->getData(), strFile->getSize());
+ #if PRINT_STRING_METRICS
+ fprintf(stderr, "**** key strings: %d\n", amt);
+ #endif
+ strAmt += amt;
+ if (amt < 0) {
+ return amt;
+ }
+
+ // Build the type chunks inside of this package.
+ for (size_t ti=0; ti<N; ti++) {
+ // Retrieve them in the same order as the type string block.
+ size_t len;
+ String16 typeName(p->getTypeStrings().stringAt(ti, &len));
+ sp<Type> t = p->getTypes().valueFor(typeName);
+ LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16("<empty>"),
+ "Type name %s not found",
+ String8(typeName).string());
+
+ const bool filterable = (typeName != mipmap16);
+
+ const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0;
+
+ // Until a non-NO_ENTRY value has been written for a resource,
+ // that resource is invalid; validResources[i] represents
+ // the item at t->getOrderedConfigs().itemAt(i).
+ Vector<bool> validResources;
+ validResources.insertAt(false, 0, N);
+
+ // First write the typeSpec chunk, containing information about
+ // each resource entry in this type.
+ {
+ const size_t typeSpecSize = sizeof(ResTable_typeSpec) + sizeof(uint32_t)*N;
+ const size_t typeSpecStart = data->getSize();
+ ResTable_typeSpec* tsHeader = (ResTable_typeSpec*)
+ (((uint8_t*)data->editData(typeSpecStart+typeSpecSize)) + typeSpecStart);
+ if (tsHeader == NULL) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_typeSpec\n");
+ return NO_MEMORY;
+ }
+ memset(tsHeader, 0, sizeof(*tsHeader));
+ tsHeader->header.type = htods(RES_TABLE_TYPE_SPEC_TYPE);
+ tsHeader->header.headerSize = htods(sizeof(*tsHeader));
+ tsHeader->header.size = htodl(typeSpecSize);
+ tsHeader->id = ti+1;
+ tsHeader->entryCount = htodl(N);
+
+ uint32_t* typeSpecFlags = (uint32_t*)
+ (((uint8_t*)data->editData())
+ + typeSpecStart + sizeof(ResTable_typeSpec));
+ memset(typeSpecFlags, 0, sizeof(uint32_t)*N);
+
+ for (size_t ei=0; ei<N; ei++) {
+ sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
+ if (cl->getPublic()) {
+ typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC);
+ }
+ const size_t CN = cl->getEntries().size();
+ for (size_t ci=0; ci<CN; ci++) {
+ if (filterable && !filter.match(cl->getEntries().keyAt(ci))) {
+ continue;
+ }
+ for (size_t cj=ci+1; cj<CN; cj++) {
+ if (filterable && !filter.match(cl->getEntries().keyAt(cj))) {
+ continue;
+ }
+ typeSpecFlags[ei] |= htodl(
+ cl->getEntries().keyAt(ci).diff(cl->getEntries().keyAt(cj)));
+ }
+ }
+ }
+ }
+
+ // We need to write one type chunk for each configuration for
+ // which we have entries in this type.
+ const size_t NC = t->getUniqueConfigs().size();
+
+ const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N;
+
+ for (size_t ci=0; ci<NC; ci++) {
+ ConfigDescription config = t->getUniqueConfigs().itemAt(ci);
+
+ NOISY(printf("Writing config %d config: imsi:%d/%d lang:%c%c cnt:%c%c "
+ "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
+ "sw%ddp w%ddp h%ddp dir:%d\n",
+ ti+1,
+ config.mcc, config.mnc,
+ config.language[0] ? config.language[0] : '-',
+ config.language[1] ? config.language[1] : '-',
+ config.country[0] ? config.country[0] : '-',
+ config.country[1] ? config.country[1] : '-',
+ config.orientation,
+ config.uiMode,
+ config.touchscreen,
+ config.density,
+ config.keyboard,
+ config.inputFlags,
+ config.navigation,
+ config.screenWidth,
+ config.screenHeight,
+ config.smallestScreenWidthDp,
+ config.screenWidthDp,
+ config.screenHeightDp,
+ config.layoutDirection));
+
+ if (filterable && !filter.match(config)) {
+ continue;
+ }
+
+ const size_t typeStart = data->getSize();
+
+ ResTable_type* tHeader = (ResTable_type*)
+ (((uint8_t*)data->editData(typeStart+typeSize)) + typeStart);
+ if (tHeader == NULL) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_type\n");
+ return NO_MEMORY;
+ }
+
+ memset(tHeader, 0, sizeof(*tHeader));
+ tHeader->header.type = htods(RES_TABLE_TYPE_TYPE);
+ tHeader->header.headerSize = htods(sizeof(*tHeader));
+ tHeader->id = ti+1;
+ tHeader->entryCount = htodl(N);
+ tHeader->entriesStart = htodl(typeSize);
+ tHeader->config = config;
+ NOISY(printf("Writing type %d config: imsi:%d/%d lang:%c%c cnt:%c%c "
+ "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
+ "sw%ddp w%ddp h%ddp dir:%d\n",
+ ti+1,
+ tHeader->config.mcc, tHeader->config.mnc,
+ tHeader->config.language[0] ? tHeader->config.language[0] : '-',
+ tHeader->config.language[1] ? tHeader->config.language[1] : '-',
+ tHeader->config.country[0] ? tHeader->config.country[0] : '-',
+ tHeader->config.country[1] ? tHeader->config.country[1] : '-',
+ tHeader->config.orientation,
+ tHeader->config.uiMode,
+ tHeader->config.touchscreen,
+ tHeader->config.density,
+ tHeader->config.keyboard,
+ tHeader->config.inputFlags,
+ tHeader->config.navigation,
+ tHeader->config.screenWidth,
+ tHeader->config.screenHeight,
+ tHeader->config.smallestScreenWidthDp,
+ tHeader->config.screenWidthDp,
+ tHeader->config.screenHeightDp,
+ tHeader->config.layoutDirection));
+ tHeader->config.swapHtoD();
+
+ // Build the entries inside of this type.
+ for (size_t ei=0; ei<N; ei++) {
+ sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
+ sp<Entry> e = cl->getEntries().valueFor(config);
+
+ // Set the offset for this entry in its type.
+ uint32_t* index = (uint32_t*)
+ (((uint8_t*)data->editData())
+ + typeStart + sizeof(ResTable_type));
+ if (e != NULL) {
+ index[ei] = htodl(data->getSize()-typeStart-typeSize);
+
+ // Create the entry.
+ ssize_t amt = e->flatten(bundle, data, cl->getPublic());
+ if (amt < 0) {
+ return amt;
+ }
+ validResources.editItemAt(ei) = true;
+ } else {
+ index[ei] = htodl(ResTable_type::NO_ENTRY);
+ }
+ }
+
+ // Fill in the rest of the type information.
+ tHeader = (ResTable_type*)
+ (((uint8_t*)data->editData()) + typeStart);
+ tHeader->header.size = htodl(data->getSize()-typeStart);
+ }
+
+ bool missing_entry = false;
+ const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ?
+ "error" : "warning";
+ for (size_t i = 0; i < N; ++i) {
+ if (!validResources[i]) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(i);
+ fprintf(stderr, "%s: no entries written for %s/%s\n", log_prefix,
+ String8(typeName).string(), String8(c->getName()).string());
+ missing_entry = true;
+ }
+ }
+ if (bundle->getErrorOnMissingConfigEntry() && missing_entry) {
+ fprintf(stderr, "Error: Missing entries, quit!\n");
+ return NOT_ENOUGH_DATA;
+ }
+ }
+
+ // Fill in the rest of the package information.
+ header = (ResTable_package*)data->editData();
+ header->header.size = htodl(data->getSize());
+ header->typeStrings = htodl(typeStringsStart);
+ header->lastPublicType = htodl(p->getTypeStrings().size());
+ header->keyStrings = htodl(keyStringsStart);
+ header->lastPublicKey = htodl(p->getKeyStrings().size());
+
+ flatPackages.add(data);
+ }
+
+ // And now write out the final chunks.
+ const size_t dataStart = dest->getSize();
+
+ {
+ // blah
+ ResTable_header header;
+ memset(&header, 0, sizeof(header));
+ header.header.type = htods(RES_TABLE_TYPE);
+ header.header.headerSize = htods(sizeof(header));
+ header.packageCount = htodl(flatPackages.size());
+ status_t err = dest->writeData(&header, sizeof(header));
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_header\n");
+ return err;
+ }
+ }
+
+ ssize_t strStart = dest->getSize();
+ err = valueStrings.writeStringBlock(dest);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ ssize_t amt = (dest->getSize()-strStart);
+ strAmt += amt;
+ #if PRINT_STRING_METRICS
+ fprintf(stderr, "**** value strings: %d\n", amt);
+ fprintf(stderr, "**** total strings: %d\n", strAmt);
+ #endif
+
+ for (pi=0; pi<flatPackages.size(); pi++) {
+ err = dest->writeData(flatPackages[pi]->getData(),
+ flatPackages[pi]->getSize());
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating package chunk for ResTable_header\n");
+ return err;
+ }
+ }
+
+ ResTable_header* header = (ResTable_header*)
+ (((uint8_t*)dest->getData()) + dataStart);
+ header->header.size = htodl(dest->getSize() - dataStart);
+
+ NOISY(aout << "Resource table:"
+ << HexDump(dest->getData(), dest->getSize()) << endl);
+
+ #if PRINT_STRING_METRICS
+ fprintf(stderr, "**** total resource table size: %d / %d%% strings\n",
+ dest->getSize(), (strAmt*100)/dest->getSize());
+ #endif
+
+ return NO_ERROR;
+}
+
+void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp)
+{
+ fprintf(fp,
+ "<!-- This file contains <public> resource definitions for all\n"
+ " resources that were generated from the source data. -->\n"
+ "\n"
+ "<resources>\n");
+
+ writePublicDefinitions(package, fp, true);
+ writePublicDefinitions(package, fp, false);
+
+ fprintf(fp,
+ "\n"
+ "</resources>\n");
+}
+
+void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp, bool pub)
+{
+ bool didHeader = false;
+
+ sp<Package> pkg = mPackages.valueFor(package);
+ if (pkg != NULL) {
+ const size_t NT = pkg->getOrderedTypes().size();
+ for (size_t i=0; i<NT; i++) {
+ sp<Type> t = pkg->getOrderedTypes().itemAt(i);
+ if (t == NULL) {
+ continue;
+ }
+
+ bool didType = false;
+
+ const size_t NC = t->getOrderedConfigs().size();
+ for (size_t j=0; j<NC; j++) {
+ sp<ConfigList> c = t->getOrderedConfigs().itemAt(j);
+ if (c == NULL) {
+ continue;
+ }
+
+ if (c->getPublic() != pub) {
+ continue;
+ }
+
+ if (!didType) {
+ fprintf(fp, "\n");
+ didType = true;
+ }
+ if (!didHeader) {
+ if (pub) {
+ fprintf(fp," <!-- PUBLIC SECTION. These resources have been declared public.\n");
+ fprintf(fp," Changes to these definitions will break binary compatibility. -->\n\n");
+ } else {
+ fprintf(fp," <!-- PRIVATE SECTION. These resources have not been declared public.\n");
+ fprintf(fp," You can make them public my moving these lines into a file in res/values. -->\n\n");
+ }
+ didHeader = true;
+ }
+ if (!pub) {
+ const size_t NE = c->getEntries().size();
+ for (size_t k=0; k<NE; k++) {
+ const SourcePos& pos = c->getEntries().valueAt(k)->getPos();
+ if (pos.file != "") {
+ fprintf(fp," <!-- Declared at %s:%d -->\n",
+ pos.file.string(), pos.line);
+ }
+ }
+ }
+ fprintf(fp, " <public type=\"%s\" name=\"%s\" id=\"0x%08x\" />\n",
+ String8(t->getName()).string(),
+ String8(c->getName()).string(),
+ getResId(pkg, t, c->getEntryIndex()));
+ }
+ }
+ }
+}
+
+ResourceTable::Item::Item(const SourcePos& _sourcePos,
+ bool _isId,
+ const String16& _value,
+ const Vector<StringPool::entry_style_span>* _style,
+ int32_t _format)
+ : sourcePos(_sourcePos)
+ , isId(_isId)
+ , value(_value)
+ , format(_format)
+ , bagKeyId(0)
+ , evaluating(false)
+{
+ if (_style) {
+ style = *_style;
+ }
+}
+
+status_t ResourceTable::Entry::makeItABag(const SourcePos& sourcePos)
+{
+ if (mType == TYPE_BAG) {
+ return NO_ERROR;
+ }
+ if (mType == TYPE_UNKNOWN) {
+ mType = TYPE_BAG;
+ return NO_ERROR;
+ }
+ sourcePos.error("Resource entry %s is already defined as a single item.\n"
+ "%s:%d: Originally defined here.\n",
+ String8(mName).string(),
+ mItem.sourcePos.file.string(), mItem.sourcePos.line);
+ return UNKNOWN_ERROR;
+}
+
+status_t ResourceTable::Entry::setItem(const SourcePos& sourcePos,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style,
+ int32_t format,
+ const bool overwrite)
+{
+ Item item(sourcePos, false, value, style);
+
+ if (mType == TYPE_BAG) {
+ const Item& item(mBag.valueAt(0));
+ sourcePos.error("Resource entry %s is already defined as a bag.\n"
+ "%s:%d: Originally defined here.\n",
+ String8(mName).string(),
+ item.sourcePos.file.string(), item.sourcePos.line);
+ return UNKNOWN_ERROR;
+ }
+ if ( (mType != TYPE_UNKNOWN) && (overwrite == false) ) {
+ sourcePos.error("Resource entry %s is already defined.\n"
+ "%s:%d: Originally defined here.\n",
+ String8(mName).string(),
+ mItem.sourcePos.file.string(), mItem.sourcePos.line);
+ return UNKNOWN_ERROR;
+ }
+
+ mType = TYPE_ITEM;
+ mItem = item;
+ mItemFormat = format;
+ return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::addToBag(const SourcePos& sourcePos,
+ const String16& key, const String16& value,
+ const Vector<StringPool::entry_style_span>* style,
+ bool replace, bool isId, int32_t format)
+{
+ status_t err = makeItABag(sourcePos);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ Item item(sourcePos, isId, value, style, format);
+
+ // XXX NOTE: there is an error if you try to have a bag with two keys,
+ // one an attr and one an id, with the same name. Not something we
+ // currently ever have to worry about.
+ ssize_t origKey = mBag.indexOfKey(key);
+ if (origKey >= 0) {
+ if (!replace) {
+ const Item& item(mBag.valueAt(origKey));
+ sourcePos.error("Resource entry %s already has bag item %s.\n"
+ "%s:%d: Originally defined here.\n",
+ String8(mName).string(), String8(key).string(),
+ item.sourcePos.file.string(), item.sourcePos.line);
+ return UNKNOWN_ERROR;
+ }
+ //printf("Replacing %s with %s\n",
+ // String8(mBag.valueFor(key).value).string(), String8(value).string());
+ mBag.replaceValueFor(key, item);
+ }
+
+ mBag.add(key, item);
+ return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::emptyBag(const SourcePos& sourcePos)
+{
+ status_t err = makeItABag(sourcePos);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ mBag.clear();
+ return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::generateAttributes(ResourceTable* table,
+ const String16& package)
+{
+ const String16 attr16("attr");
+ const String16 id16("id");
+ const size_t N = mBag.size();
+ for (size_t i=0; i<N; i++) {
+ const String16& key = mBag.keyAt(i);
+ const Item& it = mBag.valueAt(i);
+ if (it.isId) {
+ if (!table->hasBagOrEntry(key, &id16, &package)) {
+ String16 value("false");
+ status_t err = table->addEntry(SourcePos(String8("<generated>"), 0), package,
+ id16, key, value);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+ } else if (!table->hasBagOrEntry(key, &attr16, &package)) {
+
+#if 1
+// fprintf(stderr, "ERROR: Bag attribute '%s' has not been defined.\n",
+// String8(key).string());
+// const Item& item(mBag.valueAt(i));
+// fprintf(stderr, "Referenced from file %s line %d\n",
+// item.sourcePos.file.string(), item.sourcePos.line);
+// return UNKNOWN_ERROR;
+#else
+ char numberStr[16];
+ sprintf(numberStr, "%d", ResTable_map::TYPE_ANY);
+ status_t err = table->addBag(SourcePos("<generated>", 0), package,
+ attr16, key, String16(""),
+ String16("^type"),
+ String16(numberStr), NULL, NULL);
+ if (err != NO_ERROR) {
+ return err;
+ }
+#endif
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::assignResourceIds(ResourceTable* table,
+ const String16& package)
+{
+ bool hasErrors = false;
+
+ if (mType == TYPE_BAG) {
+ const char* errorMsg;
+ const String16 style16("style");
+ const String16 attr16("attr");
+ const String16 id16("id");
+ mParentId = 0;
+ if (mParent.size() > 0) {
+ mParentId = table->getResId(mParent, &style16, NULL, &errorMsg);
+ if (mParentId == 0) {
+ mPos.error("Error retrieving parent for item: %s '%s'.\n",
+ errorMsg, String8(mParent).string());
+ hasErrors = true;
+ }
+ }
+ const size_t N = mBag.size();
+ for (size_t i=0; i<N; i++) {
+ const String16& key = mBag.keyAt(i);
+ Item& it = mBag.editValueAt(i);
+ it.bagKeyId = table->getResId(key,
+ it.isId ? &id16 : &attr16, NULL, &errorMsg);
+ //printf("Bag key of %s: #%08x\n", String8(key).string(), it.bagKeyId);
+ if (it.bagKeyId == 0) {
+ it.sourcePos.error("Error: %s: %s '%s'.\n", errorMsg,
+ String8(it.isId ? id16 : attr16).string(),
+ String8(key).string());
+ hasErrors = true;
+ }
+ }
+ }
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+status_t ResourceTable::Entry::prepareFlatten(StringPool* strings, ResourceTable* table,
+ const String8* configTypeName, const ConfigDescription* config)
+{
+ if (mType == TYPE_ITEM) {
+ Item& it = mItem;
+ AccessorCookie ac(it.sourcePos, String8(mName), String8(it.value));
+ if (!table->stringToValue(&it.parsedValue, strings,
+ it.value, false, true, 0,
+ &it.style, NULL, &ac, mItemFormat,
+ configTypeName, config)) {
+ return UNKNOWN_ERROR;
+ }
+ } else if (mType == TYPE_BAG) {
+ const size_t N = mBag.size();
+ for (size_t i=0; i<N; i++) {
+ const String16& key = mBag.keyAt(i);
+ Item& it = mBag.editValueAt(i);
+ AccessorCookie ac(it.sourcePos, String8(key), String8(it.value));
+ if (!table->stringToValue(&it.parsedValue, strings,
+ it.value, false, true, it.bagKeyId,
+ &it.style, NULL, &ac, it.format,
+ configTypeName, config)) {
+ return UNKNOWN_ERROR;
+ }
+ }
+ } else {
+ mPos.error("Error: entry %s is not a single item or a bag.\n",
+ String8(mName).string());
+ return UNKNOWN_ERROR;
+ }
+ return NO_ERROR;
+}
+
+status_t ResourceTable::Entry::remapStringValue(StringPool* strings)
+{
+ if (mType == TYPE_ITEM) {
+ Item& it = mItem;
+ if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
+ it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
+ }
+ } else if (mType == TYPE_BAG) {
+ const size_t N = mBag.size();
+ for (size_t i=0; i<N; i++) {
+ Item& it = mBag.editValueAt(i);
+ if (it.parsedValue.dataType == Res_value::TYPE_STRING) {
+ it.parsedValue.data = strings->mapOriginalPosToNewPos(it.parsedValue.data);
+ }
+ }
+ } else {
+ mPos.error("Error: entry %s is not a single item or a bag.\n",
+ String8(mName).string());
+ return UNKNOWN_ERROR;
+ }
+ return NO_ERROR;
+}
+
+ssize_t ResourceTable::Entry::flatten(Bundle* bundle, const sp<AaptFile>& data, bool isPublic)
+{
+ size_t amt = 0;
+ ResTable_entry header;
+ memset(&header, 0, sizeof(header));
+ header.size = htods(sizeof(header));
+ const type ty = this != NULL ? mType : TYPE_ITEM;
+ if (this != NULL) {
+ if (ty == TYPE_BAG) {
+ header.flags |= htods(header.FLAG_COMPLEX);
+ }
+ if (isPublic) {
+ header.flags |= htods(header.FLAG_PUBLIC);
+ }
+ header.key.index = htodl(mNameIndex);
+ }
+ if (ty != TYPE_BAG) {
+ status_t err = data->writeData(&header, sizeof(header));
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
+ return err;
+ }
+
+ const Item& it = mItem;
+ Res_value par;
+ memset(&par, 0, sizeof(par));
+ par.size = htods(it.parsedValue.size);
+ par.dataType = it.parsedValue.dataType;
+ par.res0 = it.parsedValue.res0;
+ par.data = htodl(it.parsedValue.data);
+ #if 0
+ printf("Writing item (%s): type=%d, data=0x%x, res0=0x%x\n",
+ String8(mName).string(), it.parsedValue.dataType,
+ it.parsedValue.data, par.res0);
+ #endif
+ err = data->writeData(&par, it.parsedValue.size);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating Res_value\n");
+ return err;
+ }
+ amt += it.parsedValue.size;
+ } else {
+ size_t N = mBag.size();
+ size_t i;
+ // Create correct ordering of items.
+ KeyedVector<uint32_t, const Item*> items;
+ for (i=0; i<N; i++) {
+ const Item& it = mBag.valueAt(i);
+ items.add(it.bagKeyId, &it);
+ }
+ N = items.size();
+
+ ResTable_map_entry mapHeader;
+ memcpy(&mapHeader, &header, sizeof(header));
+ mapHeader.size = htods(sizeof(mapHeader));
+ mapHeader.parent.ident = htodl(mParentId);
+ mapHeader.count = htodl(N);
+ status_t err = data->writeData(&mapHeader, sizeof(mapHeader));
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating ResTable_entry\n");
+ return err;
+ }
+
+ for (i=0; i<N; i++) {
+ const Item& it = *items.valueAt(i);
+ ResTable_map map;
+ map.name.ident = htodl(it.bagKeyId);
+ map.value.size = htods(it.parsedValue.size);
+ map.value.dataType = it.parsedValue.dataType;
+ map.value.res0 = it.parsedValue.res0;
+ map.value.data = htodl(it.parsedValue.data);
+ err = data->writeData(&map, sizeof(map));
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: out of memory creating Res_value\n");
+ return err;
+ }
+ amt += sizeof(map);
+ }
+ }
+ return amt;
+}
+
+void ResourceTable::ConfigList::appendComment(const String16& comment,
+ bool onlyIfEmpty)
+{
+ if (comment.size() <= 0) {
+ return;
+ }
+ if (onlyIfEmpty && mComment.size() > 0) {
+ return;
+ }
+ if (mComment.size() > 0) {
+ mComment.append(String16("\n"));
+ }
+ mComment.append(comment);
+}
+
+void ResourceTable::ConfigList::appendTypeComment(const String16& comment)
+{
+ if (comment.size() <= 0) {
+ return;
+ }
+ if (mTypeComment.size() > 0) {
+ mTypeComment.append(String16("\n"));
+ }
+ mTypeComment.append(comment);
+}
+
+status_t ResourceTable::Type::addPublic(const SourcePos& sourcePos,
+ const String16& name,
+ const uint32_t ident)
+{
+ #if 0
+ int32_t entryIdx = Res_GETENTRY(ident);
+ if (entryIdx < 0) {
+ sourcePos.error("Public resource %s/%s has an invalid 0 identifier (0x%08x).\n",
+ String8(mName).string(), String8(name).string(), ident);
+ return UNKNOWN_ERROR;
+ }
+ #endif
+
+ int32_t typeIdx = Res_GETTYPE(ident);
+ if (typeIdx >= 0) {
+ typeIdx++;
+ if (mPublicIndex > 0 && mPublicIndex != typeIdx) {
+ sourcePos.error("Public resource %s/%s has conflicting type codes for its"
+ " public identifiers (0x%x vs 0x%x).\n",
+ String8(mName).string(), String8(name).string(),
+ mPublicIndex, typeIdx);
+ return UNKNOWN_ERROR;
+ }
+ mPublicIndex = typeIdx;
+ }
+
+ if (mFirstPublicSourcePos == NULL) {
+ mFirstPublicSourcePos = new SourcePos(sourcePos);
+ }
+
+ if (mPublic.indexOfKey(name) < 0) {
+ mPublic.add(name, Public(sourcePos, String16(), ident));
+ } else {
+ Public& p = mPublic.editValueFor(name);
+ if (p.ident != ident) {
+ sourcePos.error("Public resource %s/%s has conflicting public identifiers"
+ " (0x%08x vs 0x%08x).\n"
+ "%s:%d: Originally defined here.\n",
+ String8(mName).string(), String8(name).string(), p.ident, ident,
+ p.sourcePos.file.string(), p.sourcePos.line);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+void ResourceTable::Type::canAddEntry(const String16& name)
+{
+ mCanAddEntries.add(name);
+}
+
+sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry,
+ const SourcePos& sourcePos,
+ const ResTable_config* config,
+ bool doSetIndex,
+ bool overlay,
+ bool autoAddOverlay)
+{
+ int pos = -1;
+ sp<ConfigList> c = mConfigs.valueFor(entry);
+ if (c == NULL) {
+ if (overlay && !autoAddOverlay && mCanAddEntries.indexOf(entry) < 0) {
+ sourcePos.error("Resource at %s appears in overlay but not"
+ " in the base package; use <add-resource> to add.\n",
+ String8(entry).string());
+ return NULL;
+ }
+ c = new ConfigList(entry, sourcePos);
+ mConfigs.add(entry, c);
+ pos = (int)mOrderedConfigs.size();
+ mOrderedConfigs.add(c);
+ if (doSetIndex) {
+ c->setEntryIndex(pos);
+ }
+ }
+
+ ConfigDescription cdesc;
+ if (config) cdesc = *config;
+
+ sp<Entry> e = c->getEntries().valueFor(cdesc);
+ if (e == NULL) {
+ if (config != NULL) {
+ NOISY(printf("New entry at %s:%d: imsi:%d/%d lang:%c%c cnt:%c%c "
+ "orien:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
+ "sw%ddp w%ddp h%ddp dir:%d\n",
+ sourcePos.file.string(), sourcePos.line,
+ config->mcc, config->mnc,
+ config->language[0] ? config->language[0] : '-',
+ config->language[1] ? config->language[1] : '-',
+ config->country[0] ? config->country[0] : '-',
+ config->country[1] ? config->country[1] : '-',
+ config->orientation,
+ config->touchscreen,
+ config->density,
+ config->keyboard,
+ config->inputFlags,
+ config->navigation,
+ config->screenWidth,
+ config->screenHeight,
+ config->smallestScreenWidthDp,
+ config->screenWidthDp,
+ config->screenHeightDp,
+ config->layoutDirection));
+ } else {
+ NOISY(printf("New entry at %s:%d: NULL config\n",
+ sourcePos.file.string(), sourcePos.line));
+ }
+ e = new Entry(entry, sourcePos);
+ c->addEntry(cdesc, e);
+ /*
+ if (doSetIndex) {
+ if (pos < 0) {
+ for (pos=0; pos<(int)mOrderedConfigs.size(); pos++) {
+ if (mOrderedConfigs[pos] == c) {
+ break;
+ }
+ }
+ if (pos >= (int)mOrderedConfigs.size()) {
+ sourcePos.error("Internal error: config not found in mOrderedConfigs when adding entry");
+ return NULL;
+ }
+ }
+ e->setEntryIndex(pos);
+ }
+ */
+ }
+
+ mUniqueConfigs.add(cdesc);
+
+ return e;
+}
+
+status_t ResourceTable::Type::applyPublicEntryOrder()
+{
+ size_t N = mOrderedConfigs.size();
+ Vector<sp<ConfigList> > origOrder(mOrderedConfigs);
+ bool hasError = false;
+
+ size_t i;
+ for (i=0; i<N; i++) {
+ mOrderedConfigs.replaceAt(NULL, i);
+ }
+
+ const size_t NP = mPublic.size();
+ //printf("Ordering %d configs from %d public defs\n", N, NP);
+ size_t j;
+ for (j=0; j<NP; j++) {
+ const String16& name = mPublic.keyAt(j);
+ const Public& p = mPublic.valueAt(j);
+ int32_t idx = Res_GETENTRY(p.ident);
+ //printf("Looking for entry \"%s\"/\"%s\" (0x%08x) in %d...\n",
+ // String8(mName).string(), String8(name).string(), p.ident, N);
+ bool found = false;
+ for (i=0; i<N; i++) {
+ sp<ConfigList> e = origOrder.itemAt(i);
+ //printf("#%d: \"%s\"\n", i, String8(e->getName()).string());
+ if (e->getName() == name) {
+ if (idx >= (int32_t)mOrderedConfigs.size()) {
+ p.sourcePos.error("Public entry identifier 0x%x entry index "
+ "is larger than available symbols (index %d, total symbols %d).\n",
+ p.ident, idx, mOrderedConfigs.size());
+ hasError = true;
+ } else if (mOrderedConfigs.itemAt(idx) == NULL) {
+ e->setPublic(true);
+ e->setPublicSourcePos(p.sourcePos);
+ mOrderedConfigs.replaceAt(e, idx);
+ origOrder.removeAt(i);
+ N--;
+ found = true;
+ break;
+ } else {
+ sp<ConfigList> oe = mOrderedConfigs.itemAt(idx);
+
+ p.sourcePos.error("Multiple entry names declared for public entry"
+ " identifier 0x%x in type %s (%s vs %s).\n"
+ "%s:%d: Originally defined here.",
+ idx+1, String8(mName).string(),
+ String8(oe->getName()).string(),
+ String8(name).string(),
+ oe->getPublicSourcePos().file.string(),
+ oe->getPublicSourcePos().line);
+ hasError = true;
+ }
+ }
+ }
+
+ if (!found) {
+ p.sourcePos.error("Public symbol %s/%s declared here is not defined.",
+ String8(mName).string(), String8(name).string());
+ hasError = true;
+ }
+ }
+
+ //printf("Copying back in %d non-public configs, have %d\n", N, origOrder.size());
+
+ if (N != origOrder.size()) {
+ printf("Internal error: remaining private symbol count mismatch\n");
+ N = origOrder.size();
+ }
+
+ j = 0;
+ for (i=0; i<N; i++) {
+ sp<ConfigList> e = origOrder.itemAt(i);
+ // There will always be enough room for the remaining entries.
+ while (mOrderedConfigs.itemAt(j) != NULL) {
+ j++;
+ }
+ mOrderedConfigs.replaceAt(e, j);
+ j++;
+ }
+
+ return hasError ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+ResourceTable::Package::Package(const String16& name, ssize_t includedId)
+ : mName(name), mIncludedId(includedId),
+ mTypeStringsMapping(0xffffffff),
+ mKeyStringsMapping(0xffffffff)
+{
+}
+
+sp<ResourceTable::Type> ResourceTable::Package::getType(const String16& type,
+ const SourcePos& sourcePos,
+ bool doSetIndex)
+{
+ sp<Type> t = mTypes.valueFor(type);
+ if (t == NULL) {
+ t = new Type(type, sourcePos);
+ mTypes.add(type, t);
+ mOrderedTypes.add(t);
+ if (doSetIndex) {
+ // For some reason the type's index is set to one plus the index
+ // in the mOrderedTypes list, rather than just the index.
+ t->setIndex(mOrderedTypes.size());
+ }
+ }
+ return t;
+}
+
+status_t ResourceTable::Package::setTypeStrings(const sp<AaptFile>& data)
+{
+ mTypeStringsData = data;
+ status_t err = setStrings(data, &mTypeStrings, &mTypeStringsMapping);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: Type string data is corrupt!\n");
+ }
+ return err;
+}
+
+status_t ResourceTable::Package::setKeyStrings(const sp<AaptFile>& data)
+{
+ mKeyStringsData = data;
+ status_t err = setStrings(data, &mKeyStrings, &mKeyStringsMapping);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: Key string data is corrupt!\n");
+ }
+ return err;
+}
+
+status_t ResourceTable::Package::setStrings(const sp<AaptFile>& data,
+ ResStringPool* strings,
+ DefaultKeyedVector<String16, uint32_t>* mappings)
+{
+ if (data->getData() == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ NOISY(aout << "Setting restable string pool: "
+ << HexDump(data->getData(), data->getSize()) << endl);
+
+ status_t err = strings->setTo(data->getData(), data->getSize());
+ if (err == NO_ERROR) {
+ const size_t N = strings->size();
+ for (size_t i=0; i<N; i++) {
+ size_t len;
+ mappings->add(String16(strings->stringAt(i, &len)), i);
+ }
+ }
+ return err;
+}
+
+status_t ResourceTable::Package::applyPublicTypeOrder()
+{
+ size_t N = mOrderedTypes.size();
+ Vector<sp<Type> > origOrder(mOrderedTypes);
+
+ size_t i;
+ for (i=0; i<N; i++) {
+ mOrderedTypes.replaceAt(NULL, i);
+ }
+
+ for (i=0; i<N; i++) {
+ sp<Type> t = origOrder.itemAt(i);
+ int32_t idx = t->getPublicIndex();
+ if (idx > 0) {
+ idx--;
+ while (idx >= (int32_t)mOrderedTypes.size()) {
+ mOrderedTypes.add();
+ }
+ if (mOrderedTypes.itemAt(idx) != NULL) {
+ sp<Type> ot = mOrderedTypes.itemAt(idx);
+ t->getFirstPublicSourcePos().error("Multiple type names declared for public type"
+ " identifier 0x%x (%s vs %s).\n"
+ "%s:%d: Originally defined here.",
+ idx, String8(ot->getName()).string(),
+ String8(t->getName()).string(),
+ ot->getFirstPublicSourcePos().file.string(),
+ ot->getFirstPublicSourcePos().line);
+ return UNKNOWN_ERROR;
+ }
+ mOrderedTypes.replaceAt(t, idx);
+ origOrder.removeAt(i);
+ i--;
+ N--;
+ }
+ }
+
+ size_t j=0;
+ for (i=0; i<N; i++) {
+ sp<Type> t = origOrder.itemAt(i);
+ // There will always be enough room for the remaining types.
+ while (mOrderedTypes.itemAt(j) != NULL) {
+ j++;
+ }
+ mOrderedTypes.replaceAt(t, j);
+ }
+
+ return NO_ERROR;
+}
+
+sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
+{
+ sp<Package> p = mPackages.valueFor(package);
+ if (p == NULL) {
+ if (mIsAppPackage) {
+ if (mHaveAppPackage) {
+ fprintf(stderr, "Adding multiple application package resources; only one is allowed.\n"
+ "Use -x to create extended resources.\n");
+ return NULL;
+ }
+ mHaveAppPackage = true;
+ p = new Package(package, 127);
+ } else {
+ p = new Package(package, mNextPackageId);
+ }
+ //printf("*** NEW PACKAGE: \"%s\" id=%d\n",
+ // String8(package).string(), p->getAssignedId());
+ mPackages.add(package, p);
+ mOrderedPackages.add(p);
+ mNextPackageId++;
+ }
+ return p;
+}
+
+sp<ResourceTable::Type> ResourceTable::getType(const String16& package,
+ const String16& type,
+ const SourcePos& sourcePos,
+ bool doSetIndex)
+{
+ sp<Package> p = getPackage(package);
+ if (p == NULL) {
+ return NULL;
+ }
+ return p->getType(type, sourcePos, doSetIndex);
+}
+
+sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package,
+ const String16& type,
+ const String16& name,
+ const SourcePos& sourcePos,
+ bool overlay,
+ const ResTable_config* config,
+ bool doSetIndex)
+{
+ sp<Type> t = getType(package, type, sourcePos, doSetIndex);
+ if (t == NULL) {
+ return NULL;
+ }
+ return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay());
+}
+
+sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID,
+ const ResTable_config* config) const
+{
+ int pid = Res_GETPACKAGE(resID)+1;
+ const size_t N = mOrderedPackages.size();
+ size_t i;
+ sp<Package> p;
+ for (i=0; i<N; i++) {
+ sp<Package> check = mOrderedPackages[i];
+ if (check->getAssignedId() == pid) {
+ p = check;
+ break;
+ }
+
+ }
+ if (p == NULL) {
+ fprintf(stderr, "warning: Package not found for resource #%08x\n", resID);
+ return NULL;
+ }
+
+ int tid = Res_GETTYPE(resID);
+ if (tid < 0 || tid >= (int)p->getOrderedTypes().size()) {
+ fprintf(stderr, "warning: Type not found for resource #%08x\n", resID);
+ return NULL;
+ }
+ sp<Type> t = p->getOrderedTypes()[tid];
+
+ int eid = Res_GETENTRY(resID);
+ if (eid < 0 || eid >= (int)t->getOrderedConfigs().size()) {
+ fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID);
+ return NULL;
+ }
+
+ sp<ConfigList> c = t->getOrderedConfigs()[eid];
+ if (c == NULL) {
+ fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID);
+ return NULL;
+ }
+
+ ConfigDescription cdesc;
+ if (config) cdesc = *config;
+ sp<Entry> e = c->getEntries().valueFor(cdesc);
+ if (c == NULL) {
+ fprintf(stderr, "warning: Entry configuration not found for resource #%08x\n", resID);
+ return NULL;
+ }
+
+ return e;
+}
+
+const ResourceTable::Item* ResourceTable::getItem(uint32_t resID, uint32_t attrID) const
+{
+ sp<const Entry> e = getEntry(resID);
+ if (e == NULL) {
+ return NULL;
+ }
+
+ const size_t N = e->getBag().size();
+ for (size_t i=0; i<N; i++) {
+ const Item& it = e->getBag().valueAt(i);
+ if (it.bagKeyId == 0) {
+ fprintf(stderr, "warning: ID not yet assigned to '%s' in bag '%s'\n",
+ String8(e->getName()).string(),
+ String8(e->getBag().keyAt(i)).string());
+ }
+ if (it.bagKeyId == attrID) {
+ return ⁢
+ }
+ }
+
+ return NULL;
+}
+
+bool ResourceTable::getItemValue(
+ uint32_t resID, uint32_t attrID, Res_value* outValue)
+{
+ const Item* item = getItem(resID, attrID);
+
+ bool res = false;
+ if (item != NULL) {
+ if (item->evaluating) {
+ sp<const Entry> e = getEntry(resID);
+ const size_t N = e->getBag().size();
+ size_t i;
+ for (i=0; i<N; i++) {
+ if (&e->getBag().valueAt(i) == item) {
+ break;
+ }
+ }
+ fprintf(stderr, "warning: Circular reference detected in key '%s' of bag '%s'\n",
+ String8(e->getName()).string(),
+ String8(e->getBag().keyAt(i)).string());
+ return false;
+ }
+ item->evaluating = true;
+ res = stringToValue(outValue, NULL, item->value, false, false, item->bagKeyId);
+ NOISY(
+ if (res) {
+ printf("getItemValue of #%08x[#%08x] (%s): type=#%08x, data=#%08x\n",
+ resID, attrID, String8(getEntry(resID)->getName()).string(),
+ outValue->dataType, outValue->data);
+ } else {
+ printf("getItemValue of #%08x[#%08x]: failed\n",
+ resID, attrID);
+ }
+ );
+ item->evaluating = false;
+ }
+ return res;
+}
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
new file mode 100644
index 0000000..a3e0666
--- /dev/null
+++ b/tools/aapt/ResourceTable.h
@@ -0,0 +1,557 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef RESOURCE_TABLE_H
+#define RESOURCE_TABLE_H
+
+#include "StringPool.h"
+#include "SourcePos.h"
+
+#include <set>
+#include <map>
+
+using namespace std;
+
+class XMLNode;
+class ResourceTable;
+
+enum {
+ XML_COMPILE_STRIP_COMMENTS = 1<<0,
+ XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1,
+ XML_COMPILE_COMPACT_WHITESPACE = 1<<2,
+ XML_COMPILE_STRIP_WHITESPACE = 1<<3,
+ XML_COMPILE_STRIP_RAW_VALUES = 1<<4,
+ XML_COMPILE_UTF8 = 1<<5,
+
+ XML_COMPILE_STANDARD_RESOURCE =
+ XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+ | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES
+};
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+ const sp<AaptFile>& target,
+ ResourceTable* table,
+ int options = XML_COMPILE_STANDARD_RESOURCE);
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+ const sp<AaptFile>& target,
+ const sp<AaptFile>& outTarget,
+ ResourceTable* table,
+ int options = XML_COMPILE_STANDARD_RESOURCE);
+
+status_t compileXmlFile(const sp<AaptAssets>& assets,
+ const sp<XMLNode>& xmlTree,
+ const sp<AaptFile>& target,
+ ResourceTable* table,
+ int options = XML_COMPILE_STANDARD_RESOURCE);
+
+status_t compileResourceFile(Bundle* bundle,
+ const sp<AaptAssets>& assets,
+ const sp<AaptFile>& in,
+ const ResTable_config& defParams,
+ const bool overwrite,
+ ResourceTable* outTable);
+
+struct AccessorCookie
+{
+ SourcePos sourcePos;
+ String8 attr;
+ String8 value;
+
+ AccessorCookie(const SourcePos&p, const String8& a, const String8& v)
+ :sourcePos(p),
+ attr(a),
+ value(v)
+ {
+ }
+};
+
+class ResourceTable : public ResTable::Accessor
+{
+public:
+ class Package;
+ class Type;
+ class Entry;
+
+ struct ConfigDescription : public ResTable_config {
+ ConfigDescription() {
+ memset(this, 0, sizeof(*this));
+ size = sizeof(ResTable_config);
+ }
+ ConfigDescription(const ResTable_config&o) {
+ *static_cast<ResTable_config*>(this) = o;
+ size = sizeof(ResTable_config);
+ }
+ ConfigDescription(const ConfigDescription&o) {
+ *static_cast<ResTable_config*>(this) = o;
+ }
+
+ ConfigDescription& operator=(const ResTable_config& o) {
+ *static_cast<ResTable_config*>(this) = o;
+ size = sizeof(ResTable_config);
+ return *this;
+ }
+ ConfigDescription& operator=(const ConfigDescription& o) {
+ *static_cast<ResTable_config*>(this) = o;
+ return *this;
+ }
+
+ inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; }
+ inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; }
+ inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; }
+ inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; }
+ inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; }
+ inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; }
+ };
+
+ ResourceTable(Bundle* bundle, const String16& assetsPackage);
+
+ status_t addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets);
+
+ status_t addPublic(const SourcePos& pos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const uint32_t ident);
+
+ status_t addEntry(const SourcePos& pos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style = NULL,
+ const ResTable_config* params = NULL,
+ const bool doSetIndex = false,
+ const int32_t format = ResTable_map::TYPE_ANY,
+ const bool overwrite = false);
+
+ status_t startBag(const SourcePos& pos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& bagParent,
+ const ResTable_config* params = NULL,
+ bool overlay = false,
+ bool replace = false,
+ bool isId = false);
+
+ status_t addBag(const SourcePos& pos,
+ const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& bagParent,
+ const String16& bagKey,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style = NULL,
+ const ResTable_config* params = NULL,
+ bool replace = false,
+ bool isId = false,
+ const int32_t format = ResTable_map::TYPE_ANY);
+
+ bool hasBagOrEntry(const String16& package,
+ const String16& type,
+ const String16& name) const;
+
+ bool hasBagOrEntry(const String16& package,
+ const String16& type,
+ const String16& name,
+ const ResTable_config& config) const;
+
+ bool hasBagOrEntry(const String16& ref,
+ const String16* defType = NULL,
+ const String16* defPackage = NULL);
+
+ bool appendComment(const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& comment,
+ bool onlyIfEmpty = false);
+
+ bool appendTypeComment(const String16& package,
+ const String16& type,
+ const String16& name,
+ const String16& comment);
+
+ void canAddEntry(const SourcePos& pos,
+ const String16& package, const String16& type, const String16& name);
+
+ size_t size() const;
+ size_t numLocalResources() const;
+ bool hasResources() const;
+
+ sp<AaptFile> flatten(Bundle*);
+
+ static inline uint32_t makeResId(uint32_t packageId,
+ uint32_t typeId,
+ uint32_t nameId)
+ {
+ return nameId | (typeId<<16) | (packageId<<24);
+ }
+
+ static inline uint32_t getResId(const sp<Package>& p,
+ const sp<Type>& t,
+ uint32_t nameId);
+
+ uint32_t getResId(const String16& package,
+ const String16& type,
+ const String16& name,
+ bool onlyPublic = true) const;
+
+ uint32_t getResId(const String16& ref,
+ const String16* defType = NULL,
+ const String16* defPackage = NULL,
+ const char** outErrorMsg = NULL,
+ bool onlyPublic = true) const;
+
+ static bool isValidResourceName(const String16& s);
+
+ bool stringToValue(Res_value* outValue, StringPool* pool,
+ const String16& str,
+ bool preserveSpaces, bool coerceType,
+ uint32_t attrID,
+ const Vector<StringPool::entry_style_span>* style = NULL,
+ String16* outStr = NULL, void* accessorCookie = NULL,
+ uint32_t attrType = ResTable_map::TYPE_ANY,
+ const String8* configTypeName = NULL,
+ const ConfigDescription* config = NULL);
+
+ status_t assignResourceIds();
+ status_t addSymbols(const sp<AaptSymbols>& outSymbols = NULL);
+ void addLocalization(const String16& name, const String8& locale);
+ status_t validateLocalizations(void);
+
+ status_t flatten(Bundle*, const sp<AaptFile>& dest);
+
+ void writePublicDefinitions(const String16& package, FILE* fp);
+
+ virtual uint32_t getCustomResource(const String16& package,
+ const String16& type,
+ const String16& name) const;
+ virtual uint32_t getCustomResourceWithCreation(const String16& package,
+ const String16& type,
+ const String16& name,
+ const bool createIfNeeded);
+ virtual uint32_t getRemappedPackage(uint32_t origPackage) const;
+ virtual bool getAttributeType(uint32_t attrID, uint32_t* outType);
+ virtual bool getAttributeMin(uint32_t attrID, uint32_t* outMin);
+ virtual bool getAttributeMax(uint32_t attrID, uint32_t* outMax);
+ virtual bool getAttributeKeys(uint32_t attrID, Vector<String16>* outKeys);
+ virtual bool getAttributeEnum(uint32_t attrID,
+ const char16_t* name, size_t nameLen,
+ Res_value* outValue);
+ virtual bool getAttributeFlags(uint32_t attrID,
+ const char16_t* name, size_t nameLen,
+ Res_value* outValue);
+ virtual uint32_t getAttributeL10N(uint32_t attrID);
+
+ virtual bool getLocalizationSetting();
+ virtual void reportError(void* accessorCookie, const char* fmt, ...);
+
+ void setCurrentXmlPos(const SourcePos& pos) { mCurrentXmlPos = pos; }
+
+ class Item {
+ public:
+ Item() : isId(false), format(ResTable_map::TYPE_ANY), bagKeyId(0), evaluating(false)
+ { memset(&parsedValue, 0, sizeof(parsedValue)); }
+ Item(const SourcePos& pos,
+ bool _isId,
+ const String16& _value,
+ const Vector<StringPool::entry_style_span>* _style = NULL,
+ int32_t format = ResTable_map::TYPE_ANY);
+ Item(const Item& o) : sourcePos(o.sourcePos),
+ isId(o.isId), value(o.value), style(o.style),
+ format(o.format), bagKeyId(o.bagKeyId), evaluating(false) {
+ memset(&parsedValue, 0, sizeof(parsedValue));
+ }
+ ~Item() { }
+
+ Item& operator=(const Item& o) {
+ sourcePos = o.sourcePos;
+ isId = o.isId;
+ value = o.value;
+ style = o.style;
+ format = o.format;
+ bagKeyId = o.bagKeyId;
+ parsedValue = o.parsedValue;
+ return *this;
+ }
+
+ SourcePos sourcePos;
+ mutable bool isId;
+ String16 value;
+ Vector<StringPool::entry_style_span> style;
+ int32_t format;
+ uint32_t bagKeyId;
+ mutable bool evaluating;
+ Res_value parsedValue;
+ };
+
+ class Entry : public RefBase {
+ public:
+ Entry(const String16& name, const SourcePos& pos)
+ : mName(name), mType(TYPE_UNKNOWN),
+ mItemFormat(ResTable_map::TYPE_ANY), mNameIndex(-1), mPos(pos)
+ { }
+ virtual ~Entry() { }
+
+ enum type {
+ TYPE_UNKNOWN = 0,
+ TYPE_ITEM,
+ TYPE_BAG
+ };
+
+ String16 getName() const { return mName; }
+ type getType() const { return mType; }
+
+ void setParent(const String16& parent) { mParent = parent; }
+ String16 getParent() const { return mParent; }
+
+ status_t makeItABag(const SourcePos& sourcePos);
+
+ status_t emptyBag(const SourcePos& sourcePos);
+
+ status_t setItem(const SourcePos& pos,
+ const String16& value,
+ const Vector<StringPool::entry_style_span>* style = NULL,
+ int32_t format = ResTable_map::TYPE_ANY,
+ const bool overwrite = false);
+
+ status_t addToBag(const SourcePos& pos,
+ const String16& key, const String16& value,
+ const Vector<StringPool::entry_style_span>* style = NULL,
+ bool replace=false, bool isId = false,
+ int32_t format = ResTable_map::TYPE_ANY);
+
+ // Index of the entry's name string in the key pool.
+ int32_t getNameIndex() const { return mNameIndex; }
+ void setNameIndex(int32_t index) { mNameIndex = index; }
+
+ const Item* getItem() const { return mType == TYPE_ITEM ? &mItem : NULL; }
+ const KeyedVector<String16, Item>& getBag() const { return mBag; }
+
+ status_t generateAttributes(ResourceTable* table,
+ const String16& package);
+
+ status_t assignResourceIds(ResourceTable* table,
+ const String16& package);
+
+ status_t prepareFlatten(StringPool* strings, ResourceTable* table,
+ const String8* configTypeName, const ConfigDescription* config);
+
+ status_t remapStringValue(StringPool* strings);
+
+ ssize_t flatten(Bundle*, const sp<AaptFile>& data, bool isPublic);
+
+ const SourcePos& getPos() const { return mPos; }
+
+ private:
+ String16 mName;
+ String16 mParent;
+ type mType;
+ Item mItem;
+ int32_t mItemFormat;
+ KeyedVector<String16, Item> mBag;
+ int32_t mNameIndex;
+ uint32_t mParentId;
+ SourcePos mPos;
+ };
+
+ class ConfigList : public RefBase {
+ public:
+ ConfigList(const String16& name, const SourcePos& pos)
+ : mName(name), mPos(pos), mPublic(false), mEntryIndex(-1) { }
+ virtual ~ConfigList() { }
+
+ String16 getName() const { return mName; }
+ const SourcePos& getPos() const { return mPos; }
+
+ void appendComment(const String16& comment, bool onlyIfEmpty = false);
+ const String16& getComment() const { return mComment; }
+
+ void appendTypeComment(const String16& comment);
+ const String16& getTypeComment() const { return mTypeComment; }
+
+ // Index of this entry in its Type.
+ int32_t getEntryIndex() const { return mEntryIndex; }
+ void setEntryIndex(int32_t index) { mEntryIndex = index; }
+
+ void setPublic(bool pub) { mPublic = pub; }
+ bool getPublic() const { return mPublic; }
+ void setPublicSourcePos(const SourcePos& pos) { mPublicSourcePos = pos; }
+ const SourcePos& getPublicSourcePos() { return mPublicSourcePos; }
+
+ void addEntry(const ResTable_config& config, const sp<Entry>& entry) {
+ mEntries.add(config, entry);
+ }
+
+ const DefaultKeyedVector<ConfigDescription, sp<Entry> >& getEntries() const { return mEntries; }
+ private:
+ const String16 mName;
+ const SourcePos mPos;
+ String16 mComment;
+ String16 mTypeComment;
+ bool mPublic;
+ SourcePos mPublicSourcePos;
+ int32_t mEntryIndex;
+ DefaultKeyedVector<ConfigDescription, sp<Entry> > mEntries;
+ };
+
+ class Public {
+ public:
+ Public() : sourcePos(), ident(0) { }
+ Public(const SourcePos& pos,
+ const String16& _comment,
+ uint32_t _ident)
+ : sourcePos(pos),
+ comment(_comment), ident(_ident) { }
+ Public(const Public& o) : sourcePos(o.sourcePos),
+ comment(o.comment), ident(o.ident) { }
+ ~Public() { }
+
+ Public& operator=(const Public& o) {
+ sourcePos = o.sourcePos;
+ comment = o.comment;
+ ident = o.ident;
+ return *this;
+ }
+
+ SourcePos sourcePos;
+ String16 comment;
+ uint32_t ident;
+ };
+
+ class Type : public RefBase {
+ public:
+ Type(const String16& name, const SourcePos& pos)
+ : mName(name), mFirstPublicSourcePos(NULL), mPublicIndex(-1), mIndex(-1), mPos(pos)
+ { }
+ virtual ~Type() { delete mFirstPublicSourcePos; }
+
+ status_t addPublic(const SourcePos& pos,
+ const String16& name,
+ const uint32_t ident);
+
+ void canAddEntry(const String16& name);
+
+ String16 getName() const { return mName; }
+ sp<Entry> getEntry(const String16& entry,
+ const SourcePos& pos,
+ const ResTable_config* config = NULL,
+ bool doSetIndex = false,
+ bool overlay = false,
+ bool autoAddOverlay = false);
+
+ const SourcePos& getFirstPublicSourcePos() const { return *mFirstPublicSourcePos; }
+
+ int32_t getPublicIndex() const { return mPublicIndex; }
+
+ int32_t getIndex() const { return mIndex; }
+ void setIndex(int32_t index) { mIndex = index; }
+
+ status_t applyPublicEntryOrder();
+
+ const SortedVector<ConfigDescription>& getUniqueConfigs() const { return mUniqueConfigs; }
+
+ const DefaultKeyedVector<String16, sp<ConfigList> >& getConfigs() const { return mConfigs; }
+ const Vector<sp<ConfigList> >& getOrderedConfigs() const { return mOrderedConfigs; }
+
+ const SortedVector<String16>& getCanAddEntries() const { return mCanAddEntries; }
+
+ const SourcePos& getPos() const { return mPos; }
+ private:
+ String16 mName;
+ SourcePos* mFirstPublicSourcePos;
+ DefaultKeyedVector<String16, Public> mPublic;
+ SortedVector<ConfigDescription> mUniqueConfigs;
+ DefaultKeyedVector<String16, sp<ConfigList> > mConfigs;
+ Vector<sp<ConfigList> > mOrderedConfigs;
+ SortedVector<String16> mCanAddEntries;
+ int32_t mPublicIndex;
+ int32_t mIndex;
+ SourcePos mPos;
+ };
+
+ class Package : public RefBase {
+ public:
+ Package(const String16& name, ssize_t includedId=-1);
+ virtual ~Package() { }
+
+ String16 getName() const { return mName; }
+ sp<Type> getType(const String16& type,
+ const SourcePos& pos,
+ bool doSetIndex = false);
+
+ ssize_t getAssignedId() const { return mIncludedId; }
+
+ const ResStringPool& getTypeStrings() const { return mTypeStrings; }
+ uint32_t indexOfTypeString(const String16& s) const { return mTypeStringsMapping.valueFor(s); }
+ const sp<AaptFile> getTypeStringsData() const { return mTypeStringsData; }
+ status_t setTypeStrings(const sp<AaptFile>& data);
+
+ const ResStringPool& getKeyStrings() const { return mKeyStrings; }
+ uint32_t indexOfKeyString(const String16& s) const { return mKeyStringsMapping.valueFor(s); }
+ const sp<AaptFile> getKeyStringsData() const { return mKeyStringsData; }
+ status_t setKeyStrings(const sp<AaptFile>& data);
+
+ status_t applyPublicTypeOrder();
+
+ const DefaultKeyedVector<String16, sp<Type> >& getTypes() const { return mTypes; }
+ const Vector<sp<Type> >& getOrderedTypes() const { return mOrderedTypes; }
+
+ private:
+ status_t setStrings(const sp<AaptFile>& data,
+ ResStringPool* strings,
+ DefaultKeyedVector<String16, uint32_t>* mappings);
+
+ const String16 mName;
+ const ssize_t mIncludedId;
+ DefaultKeyedVector<String16, sp<Type> > mTypes;
+ Vector<sp<Type> > mOrderedTypes;
+ sp<AaptFile> mTypeStringsData;
+ sp<AaptFile> mKeyStringsData;
+ ResStringPool mTypeStrings;
+ ResStringPool mKeyStrings;
+ DefaultKeyedVector<String16, uint32_t> mTypeStringsMapping;
+ DefaultKeyedVector<String16, uint32_t> mKeyStringsMapping;
+ };
+
+private:
+ void writePublicDefinitions(const String16& package, FILE* fp, bool pub);
+ sp<Package> getPackage(const String16& package);
+ sp<Type> getType(const String16& package,
+ const String16& type,
+ const SourcePos& pos,
+ bool doSetIndex = false);
+ sp<Entry> getEntry(const String16& package,
+ const String16& type,
+ const String16& name,
+ const SourcePos& pos,
+ bool overlay,
+ const ResTable_config* config = NULL,
+ bool doSetIndex = false);
+ sp<const Entry> getEntry(uint32_t resID,
+ const ResTable_config* config = NULL) const;
+ const Item* getItem(uint32_t resID, uint32_t attrID) const;
+ bool getItemValue(uint32_t resID, uint32_t attrID,
+ Res_value* outValue);
+
+
+ String16 mAssetsPackage;
+ sp<AaptAssets> mAssets;
+ DefaultKeyedVector<String16, sp<Package> > mPackages;
+ Vector<sp<Package> > mOrderedPackages;
+ uint32_t mNextPackageId;
+ bool mHaveAppPackage;
+ bool mIsAppPackage;
+ size_t mNumLocal;
+ SourcePos mCurrentXmlPos;
+ Bundle* mBundle;
+
+ // key = string resource name, value = set of locales in which that name is defined
+ map<String16, set<String8> > mLocalizations;
+};
+
+#endif
diff --git a/tools/aapt/SourcePos.cpp b/tools/aapt/SourcePos.cpp
new file mode 100644
index 0000000..e2a921c
--- /dev/null
+++ b/tools/aapt/SourcePos.cpp
@@ -0,0 +1,171 @@
+#include "SourcePos.h"
+
+#include <stdarg.h>
+#include <vector>
+
+using namespace std;
+
+
+// ErrorPos
+// =============================================================================
+struct ErrorPos
+{
+ String8 file;
+ int line;
+ String8 error;
+ bool fatal;
+
+ ErrorPos();
+ ErrorPos(const ErrorPos& that);
+ ErrorPos(const String8& file, int line, const String8& error, bool fatal);
+ ~ErrorPos();
+ bool operator<(const ErrorPos& rhs) const;
+ bool operator==(const ErrorPos& rhs) const;
+ ErrorPos& operator=(const ErrorPos& rhs);
+
+ void print(FILE* to) const;
+};
+
+static vector<ErrorPos> g_errors;
+
+ErrorPos::ErrorPos()
+ :line(-1), fatal(false)
+{
+}
+
+ErrorPos::ErrorPos(const ErrorPos& that)
+ :file(that.file),
+ line(that.line),
+ error(that.error),
+ fatal(that.fatal)
+{
+}
+
+ErrorPos::ErrorPos(const String8& f, int l, const String8& e, bool fat)
+ :file(f),
+ line(l),
+ error(e),
+ fatal(fat)
+{
+}
+
+ErrorPos::~ErrorPos()
+{
+}
+
+bool
+ErrorPos::operator<(const ErrorPos& rhs) const
+{
+ if (this->file < rhs.file) return true;
+ if (this->file == rhs.file) {
+ if (this->line < rhs.line) return true;
+ if (this->line == rhs.line) {
+ if (this->error < rhs.error) return true;
+ }
+ }
+ return false;
+}
+
+bool
+ErrorPos::operator==(const ErrorPos& rhs) const
+{
+ return this->file == rhs.file
+ && this->line == rhs.line
+ && this->error == rhs.error;
+}
+
+ErrorPos&
+ErrorPos::operator=(const ErrorPos& rhs)
+{
+ this->file = rhs.file;
+ this->line = rhs.line;
+ this->error = rhs.error;
+ return *this;
+}
+
+void
+ErrorPos::print(FILE* to) const
+{
+ const char* type = fatal ? "error:" : "warning:";
+
+ if (this->line >= 0) {
+ fprintf(to, "%s:%d: %s %s\n", this->file.string(), this->line, type, this->error.string());
+ } else {
+ fprintf(to, "%s: %s %s\n", this->file.string(), type, this->error.string());
+ }
+}
+
+// SourcePos
+// =============================================================================
+SourcePos::SourcePos(const String8& f, int l)
+ : file(f), line(l)
+{
+}
+
+SourcePos::SourcePos(const SourcePos& that)
+ : file(that.file), line(that.line)
+{
+}
+
+SourcePos::SourcePos()
+ : file("???", 0), line(-1)
+{
+}
+
+SourcePos::~SourcePos()
+{
+}
+
+int
+SourcePos::error(const char* fmt, ...) const
+{
+ int retval=0;
+ char buf[1024];
+ va_list ap;
+ va_start(ap, fmt);
+ retval = vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+ char* p = buf + retval - 1;
+ while (p > buf && *p == '\n') {
+ *p = '\0';
+ p--;
+ }
+ g_errors.push_back(ErrorPos(this->file, this->line, String8(buf), true));
+ return retval;
+}
+
+int
+SourcePos::warning(const char* fmt, ...) const
+{
+ int retval=0;
+ char buf[1024];
+ va_list ap;
+ va_start(ap, fmt);
+ retval = vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+ char* p = buf + retval - 1;
+ while (p > buf && *p == '\n') {
+ *p = '\0';
+ p--;
+ }
+ ErrorPos(this->file, this->line, String8(buf), false).print(stderr);
+ return retval;
+}
+
+bool
+SourcePos::hasErrors()
+{
+ return g_errors.size() > 0;
+}
+
+void
+SourcePos::printErrors(FILE* to)
+{
+ vector<ErrorPos>::const_iterator it;
+ for (it=g_errors.begin(); it!=g_errors.end(); it++) {
+ it->print(to);
+ }
+}
+
+
+
diff --git a/tools/aapt/SourcePos.h b/tools/aapt/SourcePos.h
new file mode 100644
index 0000000..33f72a9
--- /dev/null
+++ b/tools/aapt/SourcePos.h
@@ -0,0 +1,28 @@
+#ifndef SOURCEPOS_H
+#define SOURCEPOS_H
+
+#include <utils/String8.h>
+#include <stdio.h>
+
+using namespace android;
+
+class SourcePos
+{
+public:
+ String8 file;
+ int line;
+
+ SourcePos(const String8& f, int l);
+ SourcePos(const SourcePos& that);
+ SourcePos();
+ ~SourcePos();
+
+ int error(const char* fmt, ...) const;
+ int warning(const char* fmt, ...) const;
+
+ static bool hasErrors();
+ static void printErrors(FILE* to);
+};
+
+
+#endif // SOURCEPOS_H
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
new file mode 100644
index 0000000..158b391
--- /dev/null
+++ b/tools/aapt/StringPool.cpp
@@ -0,0 +1,574 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#include "StringPool.h"
+#include "ResourceTable.h"
+
+#include <utils/ByteOrder.h>
+#include <utils/SortedVector.h>
+#include "qsort_r_compat.h"
+
+#if HAVE_PRINTF_ZD
+# define ZD "%zd"
+# define ZD_TYPE ssize_t
+#else
+# define ZD "%ld"
+# define ZD_TYPE long
+#endif
+
+#define NOISY(x) //x
+
+void strcpy16_htod(uint16_t* dst, const uint16_t* src)
+{
+ while (*src) {
+ char16_t s = htods(*src);
+ *dst++ = s;
+ src++;
+ }
+ *dst = 0;
+}
+
+void printStringPool(const ResStringPool* pool)
+{
+ SortedVector<const void*> uniqueStrings;
+ const size_t N = pool->size();
+ for (size_t i=0; i<N; i++) {
+ size_t len;
+ if (pool->isUTF8()) {
+ uniqueStrings.add(pool->string8At(i, &len));
+ } else {
+ uniqueStrings.add(pool->stringAt(i, &len));
+ }
+ }
+
+ printf("String pool of " ZD " unique %s %s strings, " ZD " entries and "
+ ZD " styles using " ZD " bytes:\n",
+ (ZD_TYPE)uniqueStrings.size(), pool->isUTF8() ? "UTF-8" : "UTF-16",
+ pool->isSorted() ? "sorted" : "non-sorted",
+ (ZD_TYPE)N, (ZD_TYPE)pool->styleCount(), (ZD_TYPE)pool->bytes());
+
+ const size_t NS = pool->size();
+ for (size_t s=0; s<NS; s++) {
+ String8 str = pool->string8ObjectAt(s);
+ printf("String #" ZD ": %s\n", (ZD_TYPE) s, str.string());
+ }
+}
+
+String8 StringPool::entry::makeConfigsString() const {
+ String8 configStr(configTypeName);
+ if (configStr.size() > 0) configStr.append(" ");
+ if (configs.size() > 0) {
+ for (size_t j=0; j<configs.size(); j++) {
+ if (j > 0) configStr.append(", ");
+ configStr.append(configs[j].toString());
+ }
+ } else {
+ configStr = "(none)";
+ }
+ return configStr;
+}
+
+int StringPool::entry::compare(const entry& o) const {
+ // Strings with styles go first, to reduce the size of the styles array.
+ // We don't care about the relative order of these strings.
+ if (hasStyles) {
+ return o.hasStyles ? 0 : -1;
+ }
+ if (o.hasStyles) {
+ return 1;
+ }
+
+ // Sort unstyled strings by type, then by logical configuration.
+ int comp = configTypeName.compare(o.configTypeName);
+ if (comp != 0) {
+ return comp;
+ }
+ const size_t LHN = configs.size();
+ const size_t RHN = o.configs.size();
+ size_t i=0;
+ while (i < LHN && i < RHN) {
+ comp = configs[i].compareLogical(o.configs[i]);
+ if (comp != 0) {
+ return comp;
+ }
+ i++;
+ }
+ if (LHN < RHN) return -1;
+ else if (LHN > RHN) return 1;
+ return 0;
+}
+
+StringPool::StringPool(bool utf8) :
+ mUTF8(utf8), mValues(-1)
+{
+}
+
+ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& spans,
+ const String8* configTypeName, const ResTable_config* config)
+{
+ ssize_t res = add(value, false, configTypeName, config);
+ if (res >= 0) {
+ addStyleSpans(res, spans);
+ }
+ return res;
+}
+
+ssize_t StringPool::add(const String16& value,
+ bool mergeDuplicates, const String8* configTypeName, const ResTable_config* config)
+{
+ ssize_t vidx = mValues.indexOfKey(value);
+ ssize_t pos = vidx >= 0 ? mValues.valueAt(vidx) : -1;
+ ssize_t eidx = pos >= 0 ? mEntryArray.itemAt(pos) : -1;
+ if (eidx < 0) {
+ eidx = mEntries.add(entry(value));
+ if (eidx < 0) {
+ fprintf(stderr, "Failure adding string %s\n", String8(value).string());
+ return eidx;
+ }
+ }
+
+ if (configTypeName != NULL) {
+ entry& ent = mEntries.editItemAt(eidx);
+ NOISY(printf("*** adding config type name %s, was %s\n",
+ configTypeName->string(), ent.configTypeName.string()));
+ if (ent.configTypeName.size() <= 0) {
+ ent.configTypeName = *configTypeName;
+ } else if (ent.configTypeName != *configTypeName) {
+ ent.configTypeName = " ";
+ }
+ }
+
+ if (config != NULL) {
+ // Add this to the set of configs associated with the string.
+ entry& ent = mEntries.editItemAt(eidx);
+ size_t addPos;
+ for (addPos=0; addPos<ent.configs.size(); addPos++) {
+ int cmp = ent.configs.itemAt(addPos).compareLogical(*config);
+ if (cmp >= 0) {
+ if (cmp > 0) {
+ NOISY(printf("*** inserting config: %s\n", config->toString().string()));
+ ent.configs.insertAt(*config, addPos);
+ }
+ break;
+ }
+ }
+ if (addPos >= ent.configs.size()) {
+ NOISY(printf("*** adding config: %s\n", config->toString().string()));
+ ent.configs.add(*config);
+ }
+ }
+
+ const bool first = vidx < 0;
+ const bool styled = (pos >= 0 && (size_t)pos < mEntryStyleArray.size()) ?
+ mEntryStyleArray[pos].spans.size() : 0;
+ if (first || styled || !mergeDuplicates) {
+ pos = mEntryArray.add(eidx);
+ if (first) {
+ vidx = mValues.add(value, pos);
+ }
+ entry& ent = mEntries.editItemAt(eidx);
+ ent.indices.add(pos);
+ }
+
+ NOISY(printf("Adding string %s to pool: pos=%d eidx=%d vidx=%d\n",
+ String8(value).string(), pos, eidx, vidx));
+
+ return pos;
+}
+
+status_t StringPool::addStyleSpan(size_t idx, const String16& name,
+ uint32_t start, uint32_t end)
+{
+ entry_style_span span;
+ span.name = name;
+ span.span.firstChar = start;
+ span.span.lastChar = end;
+ return addStyleSpan(idx, span);
+}
+
+status_t StringPool::addStyleSpans(size_t idx, const Vector<entry_style_span>& spans)
+{
+ const size_t N=spans.size();
+ for (size_t i=0; i<N; i++) {
+ status_t err = addStyleSpan(idx, spans[i]);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t StringPool::addStyleSpan(size_t idx, const entry_style_span& span)
+{
+ // Place blank entries in the span array up to this index.
+ while (mEntryStyleArray.size() <= idx) {
+ mEntryStyleArray.add();
+ }
+
+ entry_style& style = mEntryStyleArray.editItemAt(idx);
+ style.spans.add(span);
+ mEntries.editItemAt(mEntryArray[idx]).hasStyles = true;
+ return NO_ERROR;
+}
+
+int StringPool::config_sort(void* state, const void* lhs, const void* rhs)
+{
+ StringPool* pool = (StringPool*)state;
+ const entry& lhe = pool->mEntries[pool->mEntryArray[*static_cast<const size_t*>(lhs)]];
+ const entry& rhe = pool->mEntries[pool->mEntryArray[*static_cast<const size_t*>(rhs)]];
+ return lhe.compare(rhe);
+}
+
+void StringPool::sortByConfig()
+{
+ LOG_ALWAYS_FATAL_IF(mOriginalPosToNewPos.size() > 0, "Can't sort string pool after already sorted.");
+
+ const size_t N = mEntryArray.size();
+
+ // This is a vector that starts out with a 1:1 mapping to entries
+ // in the array, which we will sort to come up with the desired order.
+ // At that point it maps from the new position in the array to the
+ // original position the entry appeared.
+ Vector<size_t> newPosToOriginalPos;
+ newPosToOriginalPos.setCapacity(N);
+ for (size_t i=0; i < N; i++) {
+ newPosToOriginalPos.add(i);
+ }
+
+ // Sort the array.
+ NOISY(printf("SORTING STRINGS BY CONFIGURATION...\n"));
+ // Vector::sort uses insertion sort, which is very slow for this data set.
+ // Use quicksort instead because we don't need a stable sort here.
+ qsort_r_compat(newPosToOriginalPos.editArray(), N, sizeof(size_t), this, config_sort);
+ //newPosToOriginalPos.sort(config_sort, this);
+ NOISY(printf("DONE SORTING STRINGS BY CONFIGURATION.\n"));
+
+ // Create the reverse mapping from the original position in the array
+ // to the new position where it appears in the sorted array. This is
+ // so that clients can re-map any positions they had previously stored.
+ mOriginalPosToNewPos = newPosToOriginalPos;
+ for (size_t i=0; i<N; i++) {
+ mOriginalPosToNewPos.editItemAt(newPosToOriginalPos[i]) = i;
+ }
+
+#if 0
+ SortedVector<entry> entries;
+
+ for (size_t i=0; i<N; i++) {
+ printf("#%d was %d: %s\n", i, newPosToOriginalPos[i],
+ mEntries[mEntryArray[newPosToOriginalPos[i]]].makeConfigsString().string());
+ entries.add(mEntries[mEntryArray[i]]);
+ }
+
+ for (size_t i=0; i<entries.size(); i++) {
+ printf("Sorted config #%d: %s\n", i,
+ entries[i].makeConfigsString().string());
+ }
+#endif
+
+ // Now we rebuild the arrays.
+ Vector<entry> newEntries;
+ Vector<size_t> newEntryArray;
+ Vector<entry_style> newEntryStyleArray;
+ DefaultKeyedVector<size_t, size_t> origOffsetToNewOffset;
+
+ for (size_t i=0; i<N; i++) {
+ // We are filling in new offset 'i'; oldI is where we can find it
+ // in the original data structure.
+ size_t oldI = newPosToOriginalPos[i];
+ // This is the actual entry associated with the old offset.
+ const entry& oldEnt = mEntries[mEntryArray[oldI]];
+ // This is the same entry the last time we added it to the
+ // new entry array, if any.
+ ssize_t newIndexOfOffset = origOffsetToNewOffset.indexOfKey(oldI);
+ size_t newOffset;
+ if (newIndexOfOffset < 0) {
+ // This is the first time we have seen the entry, so add
+ // it.
+ newOffset = newEntries.add(oldEnt);
+ newEntries.editItemAt(newOffset).indices.clear();
+ } else {
+ // We have seen this entry before, use the existing one
+ // instead of adding it again.
+ newOffset = origOffsetToNewOffset.valueAt(newIndexOfOffset);
+ }
+ // Update the indices to include this new position.
+ newEntries.editItemAt(newOffset).indices.add(i);
+ // And add the offset of the entry to the new entry array.
+ newEntryArray.add(newOffset);
+ // Add any old style to the new style array.
+ if (mEntryStyleArray.size() > 0) {
+ if (oldI < mEntryStyleArray.size()) {
+ newEntryStyleArray.add(mEntryStyleArray[oldI]);
+ } else {
+ newEntryStyleArray.add(entry_style());
+ }
+ }
+ }
+
+ // Now trim any entries at the end of the new style array that are
+ // not needed.
+ for (ssize_t i=newEntryStyleArray.size()-1; i>=0; i--) {
+ const entry_style& style = newEntryStyleArray[i];
+ if (style.spans.size() > 0) {
+ // That's it.
+ break;
+ }
+ // This one is not needed; remove.
+ newEntryStyleArray.removeAt(i);
+ }
+
+ // All done, install the new data structures and upate mValues with
+ // the new positions.
+ mEntries = newEntries;
+ mEntryArray = newEntryArray;
+ mEntryStyleArray = newEntryStyleArray;
+ mValues.clear();
+ for (size_t i=0; i<mEntries.size(); i++) {
+ const entry& ent = mEntries[i];
+ mValues.add(ent.value, ent.indices[0]);
+ }
+
+#if 0
+ printf("FINAL SORTED STRING CONFIGS:\n");
+ for (size_t i=0; i<mEntries.size(); i++) {
+ const entry& ent = mEntries[i];
+ printf("#" ZD " %s: %s\n", (ZD_TYPE)i, ent.makeConfigsString().string(),
+ String8(ent.value).string());
+ }
+#endif
+}
+
+sp<AaptFile> StringPool::createStringBlock()
+{
+ sp<AaptFile> pool = new AaptFile(String8(), AaptGroupEntry(),
+ String8());
+ status_t err = writeStringBlock(pool);
+ return err == NO_ERROR ? pool : NULL;
+}
+
+#define ENCODE_LENGTH(str, chrsz, strSize) \
+{ \
+ size_t maxMask = 1 << ((chrsz*8)-1); \
+ size_t maxSize = maxMask-1; \
+ if (strSize > maxSize) { \
+ *str++ = maxMask | ((strSize>>(chrsz*8))&maxSize); \
+ } \
+ *str++ = strSize; \
+}
+
+status_t StringPool::writeStringBlock(const sp<AaptFile>& pool)
+{
+ // Allow appending. Sorry this is a little wacky.
+ if (pool->getSize() > 0) {
+ sp<AaptFile> block = createStringBlock();
+ if (block == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ ssize_t res = pool->writeData(block->getData(), block->getSize());
+ return (res >= 0) ? (status_t)NO_ERROR : res;
+ }
+
+ // First we need to add all style span names to the string pool.
+ // We do this now (instead of when the span is added) so that these
+ // will appear at the end of the pool, not disrupting the order
+ // our client placed their own strings in it.
+
+ const size_t STYLES = mEntryStyleArray.size();
+ size_t i;
+
+ for (i=0; i<STYLES; i++) {
+ entry_style& style = mEntryStyleArray.editItemAt(i);
+ const size_t N = style.spans.size();
+ for (size_t i=0; i<N; i++) {
+ entry_style_span& span = style.spans.editItemAt(i);
+ ssize_t idx = add(span.name, true);
+ if (idx < 0) {
+ fprintf(stderr, "Error adding span for style tag '%s'\n",
+ String8(span.name).string());
+ return idx;
+ }
+ span.span.name.index = (uint32_t)idx;
+ }
+ }
+
+ const size_t ENTRIES = mEntryArray.size();
+
+ // Now build the pool of unique strings.
+
+ const size_t STRINGS = mEntries.size();
+ const size_t preSize = sizeof(ResStringPool_header)
+ + (sizeof(uint32_t)*ENTRIES)
+ + (sizeof(uint32_t)*STYLES);
+ if (pool->editData(preSize) == NULL) {
+ fprintf(stderr, "ERROR: Out of memory for string pool\n");
+ return NO_MEMORY;
+ }
+
+ const size_t charSize = mUTF8 ? sizeof(uint8_t) : sizeof(char16_t);
+
+ size_t strPos = 0;
+ for (i=0; i<STRINGS; i++) {
+ entry& ent = mEntries.editItemAt(i);
+ const size_t strSize = (ent.value.size());
+ const size_t lenSize = strSize > (size_t)(1<<((charSize*8)-1))-1 ?
+ charSize*2 : charSize;
+
+ String8 encStr;
+ if (mUTF8) {
+ encStr = String8(ent.value);
+ }
+
+ const size_t encSize = mUTF8 ? encStr.size() : 0;
+ const size_t encLenSize = mUTF8 ?
+ (encSize > (size_t)(1<<((charSize*8)-1))-1 ?
+ charSize*2 : charSize) : 0;
+
+ ent.offset = strPos;
+
+ const size_t totalSize = lenSize + encLenSize +
+ ((mUTF8 ? encSize : strSize)+1)*charSize;
+
+ void* dat = (void*)pool->editData(preSize + strPos + totalSize);
+ if (dat == NULL) {
+ fprintf(stderr, "ERROR: Out of memory for string pool\n");
+ return NO_MEMORY;
+ }
+ dat = (uint8_t*)dat + preSize + strPos;
+ if (mUTF8) {
+ uint8_t* strings = (uint8_t*)dat;
+
+ ENCODE_LENGTH(strings, sizeof(uint8_t), strSize)
+
+ ENCODE_LENGTH(strings, sizeof(uint8_t), encSize)
+
+ strncpy((char*)strings, encStr, encSize+1);
+ } else {
+ uint16_t* strings = (uint16_t*)dat;
+
+ ENCODE_LENGTH(strings, sizeof(uint16_t), strSize)
+
+ strcpy16_htod(strings, ent.value);
+ }
+
+ strPos += totalSize;
+ }
+
+ // Pad ending string position up to a uint32_t boundary.
+
+ if (strPos&0x3) {
+ size_t padPos = ((strPos+3)&~0x3);
+ uint8_t* dat = (uint8_t*)pool->editData(preSize + padPos);
+ if (dat == NULL) {
+ fprintf(stderr, "ERROR: Out of memory padding string pool\n");
+ return NO_MEMORY;
+ }
+ memset(dat+preSize+strPos, 0, padPos-strPos);
+ strPos = padPos;
+ }
+
+ // Build the pool of style spans.
+
+ size_t styPos = strPos;
+ for (i=0; i<STYLES; i++) {
+ entry_style& ent = mEntryStyleArray.editItemAt(i);
+ const size_t N = ent.spans.size();
+ const size_t totalSize = (N*sizeof(ResStringPool_span))
+ + sizeof(ResStringPool_ref);
+
+ ent.offset = styPos-strPos;
+ uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + totalSize);
+ if (dat == NULL) {
+ fprintf(stderr, "ERROR: Out of memory for string styles\n");
+ return NO_MEMORY;
+ }
+ ResStringPool_span* span = (ResStringPool_span*)(dat+preSize+styPos);
+ for (size_t i=0; i<N; i++) {
+ span->name.index = htodl(ent.spans[i].span.name.index);
+ span->firstChar = htodl(ent.spans[i].span.firstChar);
+ span->lastChar = htodl(ent.spans[i].span.lastChar);
+ span++;
+ }
+ span->name.index = htodl(ResStringPool_span::END);
+
+ styPos += totalSize;
+ }
+
+ if (STYLES > 0) {
+ // Add full terminator at the end (when reading we validate that
+ // the end of the pool is fully terminated to simplify error
+ // checking).
+ size_t extra = sizeof(ResStringPool_span)-sizeof(ResStringPool_ref);
+ uint8_t* dat = (uint8_t*)pool->editData(preSize + styPos + extra);
+ if (dat == NULL) {
+ fprintf(stderr, "ERROR: Out of memory for string styles\n");
+ return NO_MEMORY;
+ }
+ uint32_t* p = (uint32_t*)(dat+preSize+styPos);
+ while (extra > 0) {
+ *p++ = htodl(ResStringPool_span::END);
+ extra -= sizeof(uint32_t);
+ }
+ styPos += extra;
+ }
+
+ // Write header.
+
+ ResStringPool_header* header =
+ (ResStringPool_header*)pool->padData(sizeof(uint32_t));
+ if (header == NULL) {
+ fprintf(stderr, "ERROR: Out of memory for string pool\n");
+ return NO_MEMORY;
+ }
+ memset(header, 0, sizeof(*header));
+ header->header.type = htods(RES_STRING_POOL_TYPE);
+ header->header.headerSize = htods(sizeof(*header));
+ header->header.size = htodl(pool->getSize());
+ header->stringCount = htodl(ENTRIES);
+ header->styleCount = htodl(STYLES);
+ if (mUTF8) {
+ header->flags |= htodl(ResStringPool_header::UTF8_FLAG);
+ }
+ header->stringsStart = htodl(preSize);
+ header->stylesStart = htodl(STYLES > 0 ? (preSize+strPos) : 0);
+
+ // Write string index array.
+
+ uint32_t* index = (uint32_t*)(header+1);
+ for (i=0; i<ENTRIES; i++) {
+ entry& ent = mEntries.editItemAt(mEntryArray[i]);
+ *index++ = htodl(ent.offset);
+ NOISY(printf("Writing entry #%d: \"%s\" ent=%d off=%d\n", i,
+ String8(ent.value).string(),
+ mEntryArray[i], ent.offset));
+ }
+
+ // Write style index array.
+
+ for (i=0; i<STYLES; i++) {
+ *index++ = htodl(mEntryStyleArray[i].offset);
+ }
+
+ return NO_ERROR;
+}
+
+ssize_t StringPool::offsetForString(const String16& val) const
+{
+ const Vector<size_t>* indices = offsetsForString(val);
+ ssize_t res = indices != NULL && indices->size() > 0 ? indices->itemAt(0) : -1;
+ NOISY(printf("Offset for string %s: %d (%s)\n", String8(val).string(), res,
+ res >= 0 ? String8(mEntries[mEntryArray[res]].value).string() : String8()));
+ return res;
+}
+
+const Vector<size_t>* StringPool::offsetsForString(const String16& val) const
+{
+ ssize_t pos = mValues.valueFor(val);
+ if (pos < 0) {
+ return NULL;
+ }
+ return &mEntries[mEntryArray[pos]].indices;
+}
diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h
new file mode 100644
index 0000000..1b3abfd
--- /dev/null
+++ b/tools/aapt/StringPool.h
@@ -0,0 +1,183 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef STRING_POOL_H
+#define STRING_POOL_H
+
+#include "Main.h"
+#include "AaptAssets.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/String16.h>
+#include <utils/TypeHelpers.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <libexpat/expat.h>
+
+using namespace android;
+
+#define PRINT_STRING_METRICS 0
+
+void strcpy16_htod(uint16_t* dst, const uint16_t* src);
+
+void printStringPool(const ResStringPool* pool);
+
+/**
+ * The StringPool class is used as an intermediate representation for
+ * generating the string pool resource data structure that can be parsed with
+ * ResStringPool in include/utils/ResourceTypes.h.
+ */
+class StringPool
+{
+public:
+ struct entry {
+ entry() : offset(0) { }
+ entry(const String16& _value) : value(_value), offset(0), hasStyles(false) { }
+ entry(const entry& o) : value(o.value), offset(o.offset),
+ hasStyles(o.hasStyles), indices(o.indices),
+ configTypeName(o.configTypeName), configs(o.configs) { }
+
+ String16 value;
+ size_t offset;
+ bool hasStyles;
+ Vector<size_t> indices;
+ String8 configTypeName;
+ Vector<ResTable_config> configs;
+
+ String8 makeConfigsString() const;
+
+ int compare(const entry& o) const;
+
+ inline bool operator<(const entry& o) const { return compare(o) < 0; }
+ inline bool operator<=(const entry& o) const { return compare(o) <= 0; }
+ inline bool operator==(const entry& o) const { return compare(o) == 0; }
+ inline bool operator!=(const entry& o) const { return compare(o) != 0; }
+ inline bool operator>=(const entry& o) const { return compare(o) >= 0; }
+ inline bool operator>(const entry& o) const { return compare(o) > 0; }
+ };
+
+ struct entry_style_span {
+ String16 name;
+ ResStringPool_span span;
+ };
+
+ struct entry_style {
+ entry_style() : offset(0) { }
+
+ entry_style(const entry_style& o) : offset(o.offset), spans(o.spans) { }
+
+ size_t offset;
+ Vector<entry_style_span> spans;
+ };
+
+ /**
+ * If 'utf8' is true, strings will be encoded with UTF-8 instead of
+ * left in Java's native UTF-16.
+ */
+ explicit StringPool(bool utf8 = false);
+
+ /**
+ * Add a new string to the pool. If mergeDuplicates is true, thenif
+ * the string already exists the existing entry for it will be used;
+ * otherwise, or if the value doesn't already exist, a new entry is
+ * created.
+ *
+ * Returns the index in the entry array of the new string entry.
+ */
+ ssize_t add(const String16& value, bool mergeDuplicates = false,
+ const String8* configTypeName = NULL, const ResTable_config* config = NULL);
+
+ ssize_t add(const String16& value, const Vector<entry_style_span>& spans,
+ const String8* configTypeName = NULL, const ResTable_config* config = NULL);
+
+ status_t addStyleSpan(size_t idx, const String16& name,
+ uint32_t start, uint32_t end);
+ status_t addStyleSpans(size_t idx, const Vector<entry_style_span>& spans);
+ status_t addStyleSpan(size_t idx, const entry_style_span& span);
+
+ // Sort the contents of the string block by the configuration associated
+ // with each item. After doing this you can use mapOriginalPosToNewPos()
+ // to find out the new position given the position originally returned by
+ // add().
+ void sortByConfig();
+
+ // For use after sortByConfig() to map from the original position of
+ // a string to its new sorted position.
+ size_t mapOriginalPosToNewPos(size_t originalPos) const {
+ return mOriginalPosToNewPos.itemAt(originalPos);
+ }
+
+ sp<AaptFile> createStringBlock();
+
+ status_t writeStringBlock(const sp<AaptFile>& pool);
+
+ /**
+ * Find out an offset in the pool for a particular string. If the string
+ * pool is sorted, this can not be called until after createStringBlock()
+ * or writeStringBlock() has been called
+ * (which determines the offsets). In the case of a string that appears
+ * multiple times in the pool, the first offset will be returned. Returns
+ * -1 if the string does not exist.
+ */
+ ssize_t offsetForString(const String16& val) const;
+
+ /**
+ * Find all of the offsets in the pool for a particular string. If the
+ * string pool is sorted, this can not be called until after
+ * createStringBlock() or writeStringBlock() has been called
+ * (which determines the offsets). Returns NULL if the string does not exist.
+ */
+ const Vector<size_t>* offsetsForString(const String16& val) const;
+
+private:
+ static int config_sort(void* state, const void* lhs, const void* rhs);
+
+ const bool mUTF8;
+
+ // The following data structures represent the actual structures
+ // that will be generated for the final string pool.
+
+ // Raw array of unique strings, in some arbitrary order. This is the
+ // actual strings that appear in the final string pool, in the order
+ // that they will be written.
+ Vector<entry> mEntries;
+ // Array of indices into mEntries, in the order they were
+ // added to the pool. This can be different than mEntries
+ // if the same string was added multiple times (it will appear
+ // once in mEntries, with multiple occurrences in this array).
+ // This is the lookup array that will be written for finding
+ // the string for each offset/position in the string pool.
+ Vector<size_t> mEntryArray;
+ // Optional style span information associated with each index of
+ // mEntryArray.
+ Vector<entry_style> mEntryStyleArray;
+
+ // The following data structures are used for book-keeping as the
+ // string pool is constructed.
+
+ // Unique set of all the strings added to the pool, mapped to
+ // the first index of mEntryArray where the value was added.
+ DefaultKeyedVector<String16, ssize_t> mValues;
+ // This array maps from the original position a string was placed at
+ // in mEntryArray to its new position after being sorted with sortByConfig().
+ Vector<size_t> mOriginalPosToNewPos;
+};
+
+// The entry types are trivially movable because all fields they contain, including
+// the vectors and strings, are trivially movable.
+namespace android {
+ ANDROID_TRIVIAL_MOVE_TRAIT(StringPool::entry);
+ ANDROID_TRIVIAL_MOVE_TRAIT(StringPool::entry_style_span);
+ ANDROID_TRIVIAL_MOVE_TRAIT(StringPool::entry_style);
+};
+
+#endif
+
diff --git a/tools/aapt/WorkQueue.cpp b/tools/aapt/WorkQueue.cpp
new file mode 100644
index 0000000..24a962f
--- /dev/null
+++ b/tools/aapt/WorkQueue.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "WorkQueue"
+
+#include <utils/Log.h>
+#include "WorkQueue.h"
+
+namespace android {
+
+// --- WorkQueue ---
+
+WorkQueue::WorkQueue(size_t maxThreads, bool canCallJava) :
+ mMaxThreads(maxThreads), mCanCallJava(canCallJava),
+ mCanceled(false), mFinished(false), mIdleThreads(0) {
+}
+
+WorkQueue::~WorkQueue() {
+ if (!cancel()) {
+ finish();
+ }
+}
+
+status_t WorkQueue::schedule(WorkUnit* workUnit, size_t backlog) {
+ AutoMutex _l(mLock);
+
+ if (mFinished || mCanceled) {
+ return INVALID_OPERATION;
+ }
+
+ if (mWorkThreads.size() < mMaxThreads
+ && mIdleThreads < mWorkUnits.size() + 1) {
+ sp<WorkThread> workThread = new WorkThread(this, mCanCallJava);
+ status_t status = workThread->run("WorkQueue::WorkThread");
+ if (status) {
+ return status;
+ }
+ mWorkThreads.add(workThread);
+ mIdleThreads += 1;
+ } else if (backlog) {
+ while (mWorkUnits.size() >= mMaxThreads * backlog) {
+ mWorkDequeuedCondition.wait(mLock);
+ if (mFinished || mCanceled) {
+ return INVALID_OPERATION;
+ }
+ }
+ }
+
+ mWorkUnits.add(workUnit);
+ mWorkChangedCondition.broadcast();
+ return OK;
+}
+
+status_t WorkQueue::cancel() {
+ AutoMutex _l(mLock);
+
+ return cancelLocked();
+}
+
+status_t WorkQueue::cancelLocked() {
+ if (mFinished) {
+ return INVALID_OPERATION;
+ }
+
+ if (!mCanceled) {
+ mCanceled = true;
+
+ size_t count = mWorkUnits.size();
+ for (size_t i = 0; i < count; i++) {
+ delete mWorkUnits.itemAt(i);
+ }
+ mWorkUnits.clear();
+ mWorkChangedCondition.broadcast();
+ mWorkDequeuedCondition.broadcast();
+ }
+ return OK;
+}
+
+status_t WorkQueue::finish() {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mFinished) {
+ return INVALID_OPERATION;
+ }
+
+ mFinished = true;
+ mWorkChangedCondition.broadcast();
+ } // release lock
+
+ // It is not possible for the list of work threads to change once the mFinished
+ // flag has been set, so we can access mWorkThreads outside of the lock here.
+ size_t count = mWorkThreads.size();
+ for (size_t i = 0; i < count; i++) {
+ mWorkThreads.itemAt(i)->join();
+ }
+ mWorkThreads.clear();
+ return OK;
+}
+
+bool WorkQueue::threadLoop() {
+ WorkUnit* workUnit;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ for (;;) {
+ if (mCanceled) {
+ return false;
+ }
+
+ if (!mWorkUnits.isEmpty()) {
+ workUnit = mWorkUnits.itemAt(0);
+ mWorkUnits.removeAt(0);
+ mIdleThreads -= 1;
+ mWorkDequeuedCondition.broadcast();
+ break;
+ }
+
+ if (mFinished) {
+ return false;
+ }
+
+ mWorkChangedCondition.wait(mLock);
+ }
+ } // release lock
+
+ bool shouldContinue = workUnit->run();
+ delete workUnit;
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ mIdleThreads += 1;
+
+ if (!shouldContinue) {
+ cancelLocked();
+ return false;
+ }
+ } // release lock
+
+ return true;
+}
+
+// --- WorkQueue::WorkThread ---
+
+WorkQueue::WorkThread::WorkThread(WorkQueue* workQueue, bool canCallJava) :
+ Thread(canCallJava), mWorkQueue(workQueue) {
+}
+
+WorkQueue::WorkThread::~WorkThread() {
+}
+
+bool WorkQueue::WorkThread::threadLoop() {
+ return mWorkQueue->threadLoop();
+}
+
+}; // namespace android
diff --git a/tools/aapt/WorkQueue.h b/tools/aapt/WorkQueue.h
new file mode 100644
index 0000000..d38f05d
--- /dev/null
+++ b/tools/aapt/WorkQueue.h
@@ -0,0 +1,119 @@
+/*]
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef AAPT_WORK_QUEUE_H
+#define AAPT_WORK_QUEUE_H
+
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+namespace android {
+
+/*
+ * A threaded work queue.
+ *
+ * This class is designed to make it easy to run a bunch of isolated work
+ * units in parallel, using up to the specified number of threads.
+ * To use it, write a loop to post work units to the work queue, then synchronize
+ * on the queue at the end.
+ */
+class WorkQueue {
+public:
+ class WorkUnit {
+ public:
+ WorkUnit() { }
+ virtual ~WorkUnit() { }
+
+ /*
+ * Runs the work unit.
+ * If the result is 'true' then the work queue continues scheduling work as usual.
+ * If the result is 'false' then the work queue is canceled.
+ */
+ virtual bool run() = 0;
+ };
+
+ /* Creates a work queue with the specified maximum number of work threads. */
+ WorkQueue(size_t maxThreads, bool canCallJava = true);
+
+ /* Destroys the work queue.
+ * Cancels pending work and waits for all remaining threads to complete.
+ */
+ ~WorkQueue();
+
+ /* Posts a work unit to run later.
+ * If the work queue has been canceled or is already finished, returns INVALID_OPERATION
+ * and does not take ownership of the work unit (caller must destroy it itself).
+ * Otherwise, returns OK and takes ownership of the work unit (the work queue will
+ * destroy it automatically).
+ *
+ * For flow control, this method blocks when the size of the pending work queue is more
+ * 'backlog' times the number of threads. This condition reduces the rate of entry into
+ * the pending work queue and prevents it from growing much more rapidly than the
+ * work threads can actually handle.
+ *
+ * If 'backlog' is 0, then no throttle is applied.
+ */
+ status_t schedule(WorkUnit* workUnit, size_t backlog = 2);
+
+ /* Cancels all pending work.
+ * If the work queue is already finished, returns INVALID_OPERATION.
+ * If the work queue is already canceled, returns OK and does nothing else.
+ * Otherwise, returns OK, discards all pending work units and prevents additional
+ * work units from being scheduled.
+ *
+ * Call finish() after cancel() to wait for all remaining work to complete.
+ */
+ status_t cancel();
+
+ /* Waits for all work to complete.
+ * If the work queue is already finished, returns INVALID_OPERATION.
+ * Otherwise, waits for all work to complete and returns OK.
+ */
+ status_t finish();
+
+private:
+ class WorkThread : public Thread {
+ public:
+ WorkThread(WorkQueue* workQueue, bool canCallJava);
+ virtual ~WorkThread();
+
+ private:
+ virtual bool threadLoop();
+
+ WorkQueue* const mWorkQueue;
+ };
+
+ status_t cancelLocked();
+ bool threadLoop(); // called from each work thread
+
+ const size_t mMaxThreads;
+ const bool mCanCallJava;
+
+ Mutex mLock;
+ Condition mWorkChangedCondition;
+ Condition mWorkDequeuedCondition;
+
+ bool mCanceled;
+ bool mFinished;
+ size_t mIdleThreads;
+ Vector<sp<WorkThread> > mWorkThreads;
+ Vector<WorkUnit*> mWorkUnits;
+};
+
+}; // namespace android
+
+#endif // AAPT_WORK_QUEUE_H
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
new file mode 100644
index 0000000..a663ad5
--- /dev/null
+++ b/tools/aapt/XMLNode.cpp
@@ -0,0 +1,1510 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#include "XMLNode.h"
+#include "ResourceTable.h"
+#include "pseudolocalize.h"
+
+#include <utils/ByteOrder.h>
+#include <errno.h>
+#include <string.h>
+
+#ifndef HAVE_MS_C_RUNTIME
+#define O_BINARY 0
+#endif
+
+#define NOISY(x) //x
+#define NOISY_PARSE(x) //x
+
+const char* const RESOURCES_ROOT_NAMESPACE = "http://schemas.android.com/apk/res/";
+const char* const RESOURCES_ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android";
+const char* const RESOURCES_AUTO_PACKAGE_NAMESPACE = "http://schemas.android.com/apk/res-auto";
+const char* const RESOURCES_ROOT_PRV_NAMESPACE = "http://schemas.android.com/apk/prv/res/";
+
+const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2";
+const char* const ALLOWED_XLIFF_ELEMENTS[] = {
+ "bpt",
+ "ept",
+ "it",
+ "ph",
+ "g",
+ "bx",
+ "ex",
+ "x"
+ };
+
+bool isWhitespace(const char16_t* str)
+{
+ while (*str != 0 && *str < 128 && isspace(*str)) {
+ str++;
+ }
+ return *str == 0;
+}
+
+static const String16 RESOURCES_PREFIX(RESOURCES_ROOT_NAMESPACE);
+static const String16 RESOURCES_PREFIX_AUTO_PACKAGE(RESOURCES_AUTO_PACKAGE_NAMESPACE);
+static const String16 RESOURCES_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE);
+static const String16 RESOURCES_TOOLS_NAMESPACE("http://schemas.android.com/tools");
+
+String16 getNamespaceResourcePackage(String16 appPackage, String16 namespaceUri, bool* outIsPublic)
+{
+ //printf("%s starts with %s?\n", String8(namespaceUri).string(),
+ // String8(RESOURCES_PREFIX).string());
+ size_t prefixSize;
+ bool isPublic = true;
+ if(namespaceUri.startsWith(RESOURCES_PREFIX_AUTO_PACKAGE)) {
+ NOISY(printf("Using default application package: %s -> %s\n", String8(namespaceUri).string(), String8(appPackage).string()));
+ isPublic = true;
+ return appPackage;
+ } else if (namespaceUri.startsWith(RESOURCES_PREFIX)) {
+ prefixSize = RESOURCES_PREFIX.size();
+ } else if (namespaceUri.startsWith(RESOURCES_PRV_PREFIX)) {
+ isPublic = false;
+ prefixSize = RESOURCES_PRV_PREFIX.size();
+ } else {
+ if (outIsPublic) *outIsPublic = isPublic; // = true
+ return String16();
+ }
+
+ //printf("YES!\n");
+ //printf("namespace: %s\n", String8(String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize)).string());
+ if (outIsPublic) *outIsPublic = isPublic;
+ return String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize);
+}
+
+status_t hasSubstitutionErrors(const char* fileName,
+ ResXMLTree* inXml,
+ String16 str16)
+{
+ const char16_t* str = str16.string();
+ const char16_t* p = str;
+ const char16_t* end = str + str16.size();
+
+ bool nonpositional = false;
+ int argCount = 0;
+
+ while (p < end) {
+ /*
+ * Look for the start of a Java-style substitution sequence.
+ */
+ if (*p == '%' && p + 1 < end) {
+ p++;
+
+ // A literal percent sign represented by %%
+ if (*p == '%') {
+ p++;
+ continue;
+ }
+
+ argCount++;
+
+ if (*p >= '0' && *p <= '9') {
+ do {
+ p++;
+ } while (*p >= '0' && *p <= '9');
+ if (*p != '$') {
+ // This must be a size specification instead of position.
+ nonpositional = true;
+ }
+ } else if (*p == '<') {
+ // Reusing last argument; bad idea since it can be re-arranged.
+ nonpositional = true;
+ p++;
+
+ // Optionally '$' can be specified at the end.
+ if (p < end && *p == '$') {
+ p++;
+ }
+ } else {
+ nonpositional = true;
+ }
+
+ // Ignore flags and widths
+ while (p < end && (*p == '-' ||
+ *p == '#' ||
+ *p == '+' ||
+ *p == ' ' ||
+ *p == ',' ||
+ *p == '(' ||
+ (*p >= '0' && *p <= '9'))) {
+ p++;
+ }
+
+ /*
+ * This is a shortcut to detect strings that are going to Time.format()
+ * instead of String.format()
+ *
+ * Comparison of String.format() and Time.format() args:
+ *
+ * String: ABC E GH ST X abcdefgh nost x
+ * Time: DEFGHKMS W Za d hkm s w yz
+ *
+ * Therefore we know it's definitely Time if we have:
+ * DFKMWZkmwyz
+ */
+ if (p < end) {
+ switch (*p) {
+ case 'D':
+ case 'F':
+ case 'K':
+ case 'M':
+ case 'W':
+ case 'Z':
+ case 'k':
+ case 'm':
+ case 'w':
+ case 'y':
+ case 'z':
+ return NO_ERROR;
+ }
+ }
+ }
+
+ p++;
+ }
+
+ /*
+ * If we have more than one substitution in this string and any of them
+ * are not in positional form, give the user an error.
+ */
+ if (argCount > 1 && nonpositional) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "Multiple substitutions specified in non-positional format; "
+ "did you mean to add the formatted=\"false\" attribute?\n");
+ return NOT_ENOUGH_DATA;
+ }
+
+ return NO_ERROR;
+}
+
+status_t parseStyledString(Bundle* bundle,
+ const char* fileName,
+ ResXMLTree* inXml,
+ const String16& endTag,
+ String16* outString,
+ Vector<StringPool::entry_style_span>* outSpans,
+ bool isFormatted,
+ bool pseudolocalize)
+{
+ Vector<StringPool::entry_style_span> spanStack;
+ String16 curString;
+ String16 rawString;
+ const char* errorMsg;
+ int xliffDepth = 0;
+ bool firstTime = true;
+
+ size_t len;
+ ResXMLTree::event_code_t code;
+ while ((code=inXml->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+
+ if (code == ResXMLTree::TEXT) {
+ String16 text(inXml->getText(&len));
+ if (firstTime && text.size() > 0) {
+ firstTime = false;
+ if (text.string()[0] == '@') {
+ // If this is a resource reference, don't do the pseudoloc.
+ pseudolocalize = false;
+ }
+ }
+ if (xliffDepth == 0 && pseudolocalize) {
+ std::string orig(String8(text).string());
+ std::string pseudo = pseudolocalize_string(orig);
+ curString.append(String16(String8(pseudo.c_str())));
+ } else {
+ if (isFormatted && hasSubstitutionErrors(fileName, inXml, text) != NO_ERROR) {
+ return UNKNOWN_ERROR;
+ } else {
+ curString.append(text);
+ }
+ }
+ } else if (code == ResXMLTree::START_TAG) {
+ const String16 element16(inXml->getElementName(&len));
+ const String8 element8(element16);
+
+ size_t nslen;
+ const uint16_t* ns = inXml->getElementNamespace(&nslen);
+ if (ns == NULL) {
+ ns = (const uint16_t*)"\0\0";
+ nslen = 0;
+ }
+ const String8 nspace(String16(ns, nslen));
+ if (nspace == XLIFF_XMLNS) {
+ const int N = sizeof(ALLOWED_XLIFF_ELEMENTS)/sizeof(ALLOWED_XLIFF_ELEMENTS[0]);
+ for (int i=0; i<N; i++) {
+ if (element8 == ALLOWED_XLIFF_ELEMENTS[i]) {
+ xliffDepth++;
+ // in this case, treat it like it was just text, in other words, do nothing
+ // here and silently drop this element
+ goto moveon;
+ }
+ }
+ {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "Found unsupported XLIFF tag <%s>\n",
+ element8.string());
+ return UNKNOWN_ERROR;
+ }
+moveon:
+ continue;
+ }
+
+ if (outSpans == NULL) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "Found style tag <%s> where styles are not allowed\n", element8.string());
+ return UNKNOWN_ERROR;
+ }
+
+ if (!ResTable::collectString(outString, curString.string(),
+ curString.size(), false, &errorMsg, true)) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n",
+ errorMsg, String8(curString).string());
+ return UNKNOWN_ERROR;
+ }
+ rawString.append(curString);
+ curString = String16();
+
+ StringPool::entry_style_span span;
+ span.name = element16;
+ for (size_t ai=0; ai<inXml->getAttributeCount(); ai++) {
+ span.name.append(String16(";"));
+ const char16_t* str = inXml->getAttributeName(ai, &len);
+ span.name.append(str, len);
+ span.name.append(String16("="));
+ str = inXml->getAttributeStringValue(ai, &len);
+ span.name.append(str, len);
+ }
+ //printf("Span: %s\n", String8(span.name).string());
+ span.span.firstChar = span.span.lastChar = outString->size();
+ spanStack.push(span);
+
+ } else if (code == ResXMLTree::END_TAG) {
+ size_t nslen;
+ const uint16_t* ns = inXml->getElementNamespace(&nslen);
+ if (ns == NULL) {
+ ns = (const uint16_t*)"\0\0";
+ nslen = 0;
+ }
+ const String8 nspace(String16(ns, nslen));
+ if (nspace == XLIFF_XMLNS) {
+ xliffDepth--;
+ continue;
+ }
+ if (!ResTable::collectString(outString, curString.string(),
+ curString.size(), false, &errorMsg, true)) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error("%s (in %s)\n",
+ errorMsg, String8(curString).string());
+ return UNKNOWN_ERROR;
+ }
+ rawString.append(curString);
+ curString = String16();
+
+ if (spanStack.size() == 0) {
+ if (strcmp16(inXml->getElementName(&len), endTag.string()) != 0) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "Found tag %s where <%s> close is expected\n",
+ String8(inXml->getElementName(&len)).string(),
+ String8(endTag).string());
+ return UNKNOWN_ERROR;
+ }
+ break;
+ }
+ StringPool::entry_style_span span = spanStack.top();
+ String16 spanTag;
+ ssize_t semi = span.name.findFirst(';');
+ if (semi >= 0) {
+ spanTag.setTo(span.name.string(), semi);
+ } else {
+ spanTag.setTo(span.name);
+ }
+ if (strcmp16(inXml->getElementName(&len), spanTag.string()) != 0) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "Found close tag %s where close tag %s is expected\n",
+ String8(inXml->getElementName(&len)).string(),
+ String8(spanTag).string());
+ return UNKNOWN_ERROR;
+ }
+ bool empty = true;
+ if (outString->size() > 0) {
+ span.span.lastChar = outString->size()-1;
+ if (span.span.lastChar >= span.span.firstChar) {
+ empty = false;
+ outSpans->add(span);
+ }
+ }
+ spanStack.pop();
+
+ /*
+ * This warning seems to be just an irritation to most people,
+ * since it is typically introduced by translators who then never
+ * see the warning.
+ */
+ if (0 && empty) {
+ fprintf(stderr, "%s:%d: warning: empty '%s' span found in text '%s'\n",
+ fileName, inXml->getLineNumber(),
+ String8(spanTag).string(), String8(*outString).string());
+
+ }
+ } else if (code == ResXMLTree::START_NAMESPACE) {
+ // nothing
+ }
+ }
+
+ if (code == ResXMLTree::BAD_DOCUMENT) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "Error parsing XML\n");
+ }
+
+ if (outSpans != NULL && outSpans->size() > 0) {
+ if (curString.size() > 0) {
+ if (!ResTable::collectString(outString, curString.string(),
+ curString.size(), false, &errorMsg, true)) {
+ SourcePos(String8(fileName), inXml->getLineNumber()).error(
+ "%s (in %s)\n",
+ errorMsg, String8(curString).string());
+ return UNKNOWN_ERROR;
+ }
+ }
+ } else {
+ // There is no style information, so string processing will happen
+ // later as part of the overall type conversion. Return to the
+ // client the raw unprocessed text.
+ rawString.append(curString);
+ outString->setTo(rawString);
+ }
+
+ return NO_ERROR;
+}
+
+struct namespace_entry {
+ String8 prefix;
+ String8 uri;
+};
+
+static String8 make_prefix(int depth)
+{
+ String8 prefix;
+ int i;
+ for (i=0; i<depth; i++) {
+ prefix.append(" ");
+ }
+ return prefix;
+}
+
+static String8 build_namespace(const Vector<namespace_entry>& namespaces,
+ const uint16_t* ns)
+{
+ String8 str;
+ if (ns != NULL) {
+ str = String8(ns);
+ const size_t N = namespaces.size();
+ for (size_t i=0; i<N; i++) {
+ const namespace_entry& ne = namespaces.itemAt(i);
+ if (ne.uri == str) {
+ str = ne.prefix;
+ break;
+ }
+ }
+ str.append(":");
+ }
+ return str;
+}
+
+void printXMLBlock(ResXMLTree* block)
+{
+ block->restart();
+
+ Vector<namespace_entry> namespaces;
+
+ ResXMLTree::event_code_t code;
+ int depth = 0;
+ while ((code=block->next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ String8 prefix = make_prefix(depth);
+ int i;
+ if (code == ResXMLTree::START_TAG) {
+ size_t len;
+ const uint16_t* ns16 = block->getElementNamespace(&len);
+ String8 elemNs = build_namespace(namespaces, ns16);
+ const uint16_t* com16 = block->getComment(&len);
+ if (com16) {
+ printf("%s <!-- %s -->\n", prefix.string(), String8(com16).string());
+ }
+ printf("%sE: %s%s (line=%d)\n", prefix.string(), elemNs.string(),
+ String8(block->getElementName(&len)).string(),
+ block->getLineNumber());
+ int N = block->getAttributeCount();
+ depth++;
+ prefix = make_prefix(depth);
+ for (i=0; i<N; i++) {
+ uint32_t res = block->getAttributeNameResID(i);
+ ns16 = block->getAttributeNamespace(i, &len);
+ String8 ns = build_namespace(namespaces, ns16);
+ String8 name(block->getAttributeName(i, &len));
+ printf("%sA: ", prefix.string());
+ if (res) {
+ printf("%s%s(0x%08x)", ns.string(), name.string(), res);
+ } else {
+ printf("%s%s", ns.string(), name.string());
+ }
+ Res_value value;
+ block->getAttributeValue(i, &value);
+ if (value.dataType == Res_value::TYPE_NULL) {
+ printf("=(null)");
+ } else if (value.dataType == Res_value::TYPE_REFERENCE) {
+ printf("=@0x%x", (int)value.data);
+ } else if (value.dataType == Res_value::TYPE_ATTRIBUTE) {
+ printf("=?0x%x", (int)value.data);
+ } else if (value.dataType == Res_value::TYPE_STRING) {
+ printf("=\"%s\"",
+ ResTable::normalizeForOutput(String8(block->getAttributeStringValue(i,
+ &len)).string()).string());
+ } else {
+ printf("=(type 0x%x)0x%x", (int)value.dataType, (int)value.data);
+ }
+ const char16_t* val = block->getAttributeStringValue(i, &len);
+ if (val != NULL) {
+ printf(" (Raw: \"%s\")", ResTable::normalizeForOutput(String8(val).string()).
+ string());
+ }
+ printf("\n");
+ }
+ } else if (code == ResXMLTree::END_TAG) {
+ depth--;
+ } else if (code == ResXMLTree::START_NAMESPACE) {
+ namespace_entry ns;
+ size_t len;
+ const uint16_t* prefix16 = block->getNamespacePrefix(&len);
+ if (prefix16) {
+ ns.prefix = String8(prefix16);
+ } else {
+ ns.prefix = "<DEF>";
+ }
+ ns.uri = String8(block->getNamespaceUri(&len));
+ namespaces.push(ns);
+ printf("%sN: %s=%s\n", prefix.string(), ns.prefix.string(),
+ ns.uri.string());
+ depth++;
+ } else if (code == ResXMLTree::END_NAMESPACE) {
+ depth--;
+ const namespace_entry& ns = namespaces.top();
+ size_t len;
+ const uint16_t* prefix16 = block->getNamespacePrefix(&len);
+ String8 pr;
+ if (prefix16) {
+ pr = String8(prefix16);
+ } else {
+ pr = "<DEF>";
+ }
+ if (ns.prefix != pr) {
+ prefix = make_prefix(depth);
+ printf("%s*** BAD END NS PREFIX: found=%s, expected=%s\n",
+ prefix.string(), pr.string(), ns.prefix.string());
+ }
+ String8 uri = String8(block->getNamespaceUri(&len));
+ if (ns.uri != uri) {
+ prefix = make_prefix(depth);
+ printf("%s *** BAD END NS URI: found=%s, expected=%s\n",
+ prefix.string(), uri.string(), ns.uri.string());
+ }
+ namespaces.pop();
+ } else if (code == ResXMLTree::TEXT) {
+ size_t len;
+ printf("%sC: \"%s\"\n", prefix.string(),
+ ResTable::normalizeForOutput(String8(block->getText(&len)).string()).string());
+ }
+ }
+
+ block->restart();
+}
+
+status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
+ bool stripAll, bool keepComments,
+ const char** cDataTags)
+{
+ sp<XMLNode> root = XMLNode::parse(file);
+ if (root == NULL) {
+ return UNKNOWN_ERROR;
+ }
+ root->removeWhitespace(stripAll, cDataTags);
+
+ NOISY(printf("Input XML from %s:\n", (const char*)file->getPrintableSource()));
+ NOISY(root->print());
+ sp<AaptFile> rsc = new AaptFile(String8(), AaptGroupEntry(), String8());
+ status_t err = root->flatten(rsc, !keepComments, false);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ err = outTree->setTo(rsc->getData(), rsc->getSize(), true);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ NOISY(printf("Output XML:\n"));
+ NOISY(printXMLBlock(outTree));
+
+ return NO_ERROR;
+}
+
+sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file)
+{
+ char buf[16384];
+ int fd = open(file->getSourceFile().string(), O_RDONLY | O_BINARY);
+ if (fd < 0) {
+ SourcePos(file->getSourceFile(), -1).error("Unable to open file for read: %s",
+ strerror(errno));
+ return NULL;
+ }
+
+ XML_Parser parser = XML_ParserCreateNS(NULL, 1);
+ ParseState state;
+ state.filename = file->getPrintableSource();
+ state.parser = parser;
+ XML_SetUserData(parser, &state);
+ XML_SetElementHandler(parser, startElement, endElement);
+ XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
+ XML_SetCharacterDataHandler(parser, characterData);
+ XML_SetCommentHandler(parser, commentData);
+
+ ssize_t len;
+ bool done;
+ do {
+ len = read(fd, buf, sizeof(buf));
+ done = len < (ssize_t)sizeof(buf);
+ if (len < 0) {
+ SourcePos(file->getSourceFile(), -1).error("Error reading file: %s\n", strerror(errno));
+ close(fd);
+ return NULL;
+ }
+ if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) {
+ SourcePos(file->getSourceFile(), (int)XML_GetCurrentLineNumber(parser)).error(
+ "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser)));
+ close(fd);
+ return NULL;
+ }
+ } while (!done);
+
+ XML_ParserFree(parser);
+ if (state.root == NULL) {
+ SourcePos(file->getSourceFile(), -1).error("No XML data generated when parsing");
+ }
+ close(fd);
+ return state.root;
+}
+
+XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace)
+ : mNextAttributeIndex(0x80000000)
+ , mFilename(filename)
+ , mStartLineNumber(0)
+ , mEndLineNumber(0)
+ , mUTF8(false)
+{
+ if (isNamespace) {
+ mNamespacePrefix = s1;
+ mNamespaceUri = s2;
+ } else {
+ mNamespaceUri = s1;
+ mElementName = s2;
+ }
+}
+
+XMLNode::XMLNode(const String8& filename)
+ : mFilename(filename)
+{
+ memset(&mCharsValue, 0, sizeof(mCharsValue));
+}
+
+XMLNode::type XMLNode::getType() const
+{
+ if (mElementName.size() != 0) {
+ return TYPE_ELEMENT;
+ }
+ if (mNamespaceUri.size() != 0) {
+ return TYPE_NAMESPACE;
+ }
+ return TYPE_CDATA;
+}
+
+const String16& XMLNode::getNamespacePrefix() const
+{
+ return mNamespacePrefix;
+}
+
+const String16& XMLNode::getNamespaceUri() const
+{
+ return mNamespaceUri;
+}
+
+const String16& XMLNode::getElementNamespace() const
+{
+ return mNamespaceUri;
+}
+
+const String16& XMLNode::getElementName() const
+{
+ return mElementName;
+}
+
+const Vector<sp<XMLNode> >& XMLNode::getChildren() const
+{
+ return mChildren;
+}
+
+const String8& XMLNode::getFilename() const
+{
+ return mFilename;
+}
+
+const Vector<XMLNode::attribute_entry>&
+ XMLNode::getAttributes() const
+{
+ return mAttributes;
+}
+
+const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns,
+ const String16& name) const
+{
+ for (size_t i=0; i<mAttributes.size(); i++) {
+ const attribute_entry& ae(mAttributes.itemAt(i));
+ if (ae.ns == ns && ae.name == name) {
+ return &ae;
+ }
+ }
+
+ return NULL;
+}
+
+XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns,
+ const String16& name)
+{
+ for (size_t i=0; i<mAttributes.size(); i++) {
+ attribute_entry * ae = &mAttributes.editItemAt(i);
+ if (ae->ns == ns && ae->name == name) {
+ return ae;
+ }
+ }
+
+ return NULL;
+}
+
+const String16& XMLNode::getCData() const
+{
+ return mChars;
+}
+
+const String16& XMLNode::getComment() const
+{
+ return mComment;
+}
+
+int32_t XMLNode::getStartLineNumber() const
+{
+ return mStartLineNumber;
+}
+
+int32_t XMLNode::getEndLineNumber() const
+{
+ return mEndLineNumber;
+}
+
+sp<XMLNode> XMLNode::searchElement(const String16& tagNamespace, const String16& tagName)
+{
+ if (getType() == XMLNode::TYPE_ELEMENT
+ && mNamespaceUri == tagNamespace
+ && mElementName == tagName) {
+ return this;
+ }
+
+ for (size_t i=0; i<mChildren.size(); i++) {
+ sp<XMLNode> found = mChildren.itemAt(i)->searchElement(tagNamespace, tagName);
+ if (found != NULL) {
+ return found;
+ }
+ }
+
+ return NULL;
+}
+
+sp<XMLNode> XMLNode::getChildElement(const String16& tagNamespace, const String16& tagName)
+{
+ for (size_t i=0; i<mChildren.size(); i++) {
+ sp<XMLNode> child = mChildren.itemAt(i);
+ if (child->getType() == XMLNode::TYPE_ELEMENT
+ && child->mNamespaceUri == tagNamespace
+ && child->mElementName == tagName) {
+ return child;
+ }
+ }
+
+ return NULL;
+}
+
+status_t XMLNode::addChild(const sp<XMLNode>& child)
+{
+ if (getType() == TYPE_CDATA) {
+ SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node.");
+ return UNKNOWN_ERROR;
+ }
+ //printf("Adding child %p to parent %p\n", child.get(), this);
+ mChildren.add(child);
+ return NO_ERROR;
+}
+
+status_t XMLNode::insertChildAt(const sp<XMLNode>& child, size_t index)
+{
+ if (getType() == TYPE_CDATA) {
+ SourcePos(mFilename, child->getStartLineNumber()).error("Child to CDATA node.");
+ return UNKNOWN_ERROR;
+ }
+ //printf("Adding child %p to parent %p\n", child.get(), this);
+ mChildren.insertAt(child, index);
+ return NO_ERROR;
+}
+
+status_t XMLNode::addAttribute(const String16& ns, const String16& name,
+ const String16& value)
+{
+ if (getType() == TYPE_CDATA) {
+ SourcePos(mFilename, getStartLineNumber()).error("Child to CDATA node.");
+ return UNKNOWN_ERROR;
+ }
+
+ if (ns != RESOURCES_TOOLS_NAMESPACE) {
+ attribute_entry e;
+ e.index = mNextAttributeIndex++;
+ e.ns = ns;
+ e.name = name;
+ e.string = value;
+ mAttributes.add(e);
+ mAttributeOrder.add(e.index, mAttributes.size()-1);
+ }
+ return NO_ERROR;
+}
+
+void XMLNode::setAttributeResID(size_t attrIdx, uint32_t resId)
+{
+ attribute_entry& e = mAttributes.editItemAt(attrIdx);
+ if (e.nameResId) {
+ mAttributeOrder.removeItem(e.nameResId);
+ } else {
+ mAttributeOrder.removeItem(e.index);
+ }
+ NOISY(printf("Elem %s %s=\"%s\": set res id = 0x%08x\n",
+ String8(getElementName()).string(),
+ String8(mAttributes.itemAt(attrIdx).name).string(),
+ String8(mAttributes.itemAt(attrIdx).string).string(),
+ resId));
+ mAttributes.editItemAt(attrIdx).nameResId = resId;
+ mAttributeOrder.add(resId, attrIdx);
+}
+
+status_t XMLNode::appendChars(const String16& chars)
+{
+ if (getType() != TYPE_CDATA) {
+ SourcePos(mFilename, getStartLineNumber()).error("Adding characters to element node.");
+ return UNKNOWN_ERROR;
+ }
+ mChars.append(chars);
+ return NO_ERROR;
+}
+
+status_t XMLNode::appendComment(const String16& comment)
+{
+ if (mComment.size() > 0) {
+ mComment.append(String16("\n"));
+ }
+ mComment.append(comment);
+ return NO_ERROR;
+}
+
+void XMLNode::setStartLineNumber(int32_t line)
+{
+ mStartLineNumber = line;
+}
+
+void XMLNode::setEndLineNumber(int32_t line)
+{
+ mEndLineNumber = line;
+}
+
+void XMLNode::removeWhitespace(bool stripAll, const char** cDataTags)
+{
+ //printf("Removing whitespace in %s\n", String8(mElementName).string());
+ size_t N = mChildren.size();
+ if (cDataTags) {
+ String8 tag(mElementName);
+ const char** p = cDataTags;
+ while (*p) {
+ if (tag == *p) {
+ stripAll = false;
+ break;
+ }
+ }
+ }
+ for (size_t i=0; i<N; i++) {
+ sp<XMLNode> node = mChildren.itemAt(i);
+ if (node->getType() == TYPE_CDATA) {
+ // This is a CDATA node...
+ const char16_t* p = node->mChars.string();
+ while (*p != 0 && *p < 128 && isspace(*p)) {
+ p++;
+ }
+ //printf("Space ends at %d in \"%s\"\n",
+ // (int)(p-node->mChars.string()),
+ // String8(node->mChars).string());
+ if (*p == 0) {
+ if (stripAll) {
+ // Remove this node!
+ mChildren.removeAt(i);
+ N--;
+ i--;
+ } else {
+ node->mChars = String16(" ");
+ }
+ } else {
+ // Compact leading/trailing whitespace.
+ const char16_t* e = node->mChars.string()+node->mChars.size()-1;
+ while (e > p && *e < 128 && isspace(*e)) {
+ e--;
+ }
+ if (p > node->mChars.string()) {
+ p--;
+ }
+ if (e < (node->mChars.string()+node->mChars.size()-1)) {
+ e++;
+ }
+ if (p > node->mChars.string() ||
+ e < (node->mChars.string()+node->mChars.size()-1)) {
+ String16 tmp(p, e-p+1);
+ node->mChars = tmp;
+ }
+ }
+ } else {
+ node->removeWhitespace(stripAll, cDataTags);
+ }
+ }
+}
+
+status_t XMLNode::parseValues(const sp<AaptAssets>& assets,
+ ResourceTable* table)
+{
+ bool hasErrors = false;
+
+ if (getType() == TYPE_ELEMENT) {
+ const size_t N = mAttributes.size();
+ String16 defPackage(assets->getPackage());
+ for (size_t i=0; i<N; i++) {
+ attribute_entry& e = mAttributes.editItemAt(i);
+ AccessorCookie ac(SourcePos(mFilename, getStartLineNumber()), String8(e.name),
+ String8(e.string));
+ table->setCurrentXmlPos(SourcePos(mFilename, getStartLineNumber()));
+ if (!assets->getIncludedResources()
+ .stringToValue(&e.value, &e.string,
+ e.string.string(), e.string.size(), true, true,
+ e.nameResId, NULL, &defPackage, table, &ac)) {
+ hasErrors = true;
+ }
+ NOISY(printf("Attr %s: type=0x%x, str=%s\n",
+ String8(e.name).string(), e.value.dataType,
+ String8(e.string).string()));
+ }
+ }
+ const size_t N = mChildren.size();
+ for (size_t i=0; i<N; i++) {
+ status_t err = mChildren.itemAt(i)->parseValues(assets, table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+status_t XMLNode::assignResourceIds(const sp<AaptAssets>& assets,
+ const ResourceTable* table)
+{
+ bool hasErrors = false;
+
+ if (getType() == TYPE_ELEMENT) {
+ String16 attr("attr");
+ const char* errorMsg;
+ const size_t N = mAttributes.size();
+ for (size_t i=0; i<N; i++) {
+ const attribute_entry& e = mAttributes.itemAt(i);
+ if (e.ns.size() <= 0) continue;
+ bool nsIsPublic;
+ String16 pkg(getNamespaceResourcePackage(String16(assets->getPackage()), e.ns, &nsIsPublic));
+ NOISY(printf("Elem %s %s=\"%s\": namespace(%s) %s ===> %s\n",
+ String8(getElementName()).string(),
+ String8(e.name).string(),
+ String8(e.string).string(),
+ String8(e.ns).string(),
+ (nsIsPublic) ? "public" : "private",
+ String8(pkg).string()));
+ if (pkg.size() <= 0) continue;
+ uint32_t res = table != NULL
+ ? table->getResId(e.name, &attr, &pkg, &errorMsg, nsIsPublic)
+ : assets->getIncludedResources().
+ identifierForName(e.name.string(), e.name.size(),
+ attr.string(), attr.size(),
+ pkg.string(), pkg.size());
+ if (res != 0) {
+ NOISY(printf("XML attribute name %s: resid=0x%08x\n",
+ String8(e.name).string(), res));
+ setAttributeResID(i, res);
+ } else {
+ SourcePos(mFilename, getStartLineNumber()).error(
+ "No resource identifier found for attribute '%s' in package '%s'\n",
+ String8(e.name).string(), String8(pkg).string());
+ hasErrors = true;
+ }
+ }
+ }
+ const size_t N = mChildren.size();
+ for (size_t i=0; i<N; i++) {
+ status_t err = mChildren.itemAt(i)->assignResourceIds(assets, table);
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
+ return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+}
+
+status_t XMLNode::flatten(const sp<AaptFile>& dest,
+ bool stripComments, bool stripRawValues) const
+{
+ StringPool strings(mUTF8);
+ Vector<uint32_t> resids;
+
+ // First collect just the strings for attribute names that have a
+ // resource ID assigned to them. This ensures that the resource ID
+ // array is compact, and makes it easier to deal with attribute names
+ // in different namespaces (and thus with different resource IDs).
+ collect_resid_strings(&strings, &resids);
+
+ // Next collect all remainibng strings.
+ collect_strings(&strings, &resids, stripComments, stripRawValues);
+
+#if 0 // No longer compiles
+ NOISY(printf("Found strings:\n");
+ const size_t N = strings.size();
+ for (size_t i=0; i<N; i++) {
+ printf("%s\n", String8(strings.entryAt(i).string).string());
+ }
+ );
+#endif
+
+ sp<AaptFile> stringPool = strings.createStringBlock();
+ NOISY(aout << "String pool:"
+ << HexDump(stringPool->getData(), stringPool->getSize()) << endl);
+
+ ResXMLTree_header header;
+ memset(&header, 0, sizeof(header));
+ header.header.type = htods(RES_XML_TYPE);
+ header.header.headerSize = htods(sizeof(header));
+
+ const size_t basePos = dest->getSize();
+ dest->writeData(&header, sizeof(header));
+ dest->writeData(stringPool->getData(), stringPool->getSize());
+
+ // If we have resource IDs, write them.
+ if (resids.size() > 0) {
+ const size_t resIdsPos = dest->getSize();
+ const size_t resIdsSize =
+ sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size());
+ ResChunk_header* idsHeader = (ResChunk_header*)
+ (((const uint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos);
+ idsHeader->type = htods(RES_XML_RESOURCE_MAP_TYPE);
+ idsHeader->headerSize = htods(sizeof(*idsHeader));
+ idsHeader->size = htodl(resIdsSize);
+ uint32_t* ids = (uint32_t*)(idsHeader+1);
+ for (size_t i=0; i<resids.size(); i++) {
+ *ids++ = htodl(resids[i]);
+ }
+ }
+
+ flatten_node(strings, dest, stripComments, stripRawValues);
+
+ void* data = dest->editData();
+ ResXMLTree_header* hd = (ResXMLTree_header*)(((uint8_t*)data)+basePos);
+ size_t size = dest->getSize()-basePos;
+ hd->header.size = htodl(dest->getSize()-basePos);
+
+ NOISY(aout << "XML resource:"
+ << HexDump(dest->getData(), dest->getSize()) << endl);
+
+ #if PRINT_STRING_METRICS
+ fprintf(stderr, "**** total xml size: %d / %d%% strings (in %s)\n",
+ dest->getSize(), (stringPool->getSize()*100)/dest->getSize(),
+ dest->getPath().string());
+ #endif
+
+ return NO_ERROR;
+}
+
+void XMLNode::print(int indent)
+{
+ String8 prefix;
+ int i;
+ for (i=0; i<indent; i++) {
+ prefix.append(" ");
+ }
+ if (getType() == TYPE_ELEMENT) {
+ String8 elemNs(getNamespaceUri());
+ if (elemNs.size() > 0) {
+ elemNs.append(":");
+ }
+ printf("%s E: %s%s", prefix.string(),
+ elemNs.string(), String8(getElementName()).string());
+ int N = mAttributes.size();
+ for (i=0; i<N; i++) {
+ ssize_t idx = mAttributeOrder.valueAt(i);
+ if (i == 0) {
+ printf(" / ");
+ } else {
+ printf(", ");
+ }
+ const attribute_entry& attr = mAttributes.itemAt(idx);
+ String8 attrNs(attr.ns);
+ if (attrNs.size() > 0) {
+ attrNs.append(":");
+ }
+ if (attr.nameResId) {
+ printf("%s%s(0x%08x)", attrNs.string(),
+ String8(attr.name).string(), attr.nameResId);
+ } else {
+ printf("%s%s", attrNs.string(), String8(attr.name).string());
+ }
+ printf("=%s", String8(attr.string).string());
+ }
+ printf("\n");
+ } else if (getType() == TYPE_NAMESPACE) {
+ printf("%s N: %s=%s\n", prefix.string(),
+ getNamespacePrefix().size() > 0
+ ? String8(getNamespacePrefix()).string() : "<DEF>",
+ String8(getNamespaceUri()).string());
+ } else {
+ printf("%s C: \"%s\"\n", prefix.string(), String8(getCData()).string());
+ }
+ int N = mChildren.size();
+ for (i=0; i<N; i++) {
+ mChildren.itemAt(i)->print(indent+1);
+ }
+}
+
+static void splitName(const char* name, String16* outNs, String16* outName)
+{
+ const char* p = name;
+ while (*p != 0 && *p != 1) {
+ p++;
+ }
+ if (*p == 0) {
+ *outNs = String16();
+ *outName = String16(name);
+ } else {
+ *outNs = String16(name, (p-name));
+ *outName = String16(p+1);
+ }
+}
+
+void XMLCALL
+XMLNode::startNamespace(void *userData, const char *prefix, const char *uri)
+{
+ NOISY_PARSE(printf("Start Namespace: %s %s\n", prefix, uri));
+ ParseState* st = (ParseState*)userData;
+ sp<XMLNode> node = XMLNode::newNamespace(st->filename,
+ String16(prefix != NULL ? prefix : ""), String16(uri));
+ node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
+ if (st->stack.size() > 0) {
+ st->stack.itemAt(st->stack.size()-1)->addChild(node);
+ } else {
+ st->root = node;
+ }
+ st->stack.push(node);
+}
+
+void XMLCALL
+XMLNode::startElement(void *userData, const char *name, const char **atts)
+{
+ NOISY_PARSE(printf("Start Element: %s\n", name));
+ ParseState* st = (ParseState*)userData;
+ String16 ns16, name16;
+ splitName(name, &ns16, &name16);
+ sp<XMLNode> node = XMLNode::newElement(st->filename, ns16, name16);
+ node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
+ if (st->pendingComment.size() > 0) {
+ node->appendComment(st->pendingComment);
+ st->pendingComment = String16();
+ }
+ if (st->stack.size() > 0) {
+ st->stack.itemAt(st->stack.size()-1)->addChild(node);
+ } else {
+ st->root = node;
+ }
+ st->stack.push(node);
+
+ for (int i = 0; atts[i]; i += 2) {
+ splitName(atts[i], &ns16, &name16);
+ node->addAttribute(ns16, name16, String16(atts[i+1]));
+ }
+}
+
+void XMLCALL
+XMLNode::characterData(void *userData, const XML_Char *s, int len)
+{
+ NOISY_PARSE(printf("CDATA: \"%s\"\n", String8(s, len).string()));
+ ParseState* st = (ParseState*)userData;
+ sp<XMLNode> node = NULL;
+ if (st->stack.size() == 0) {
+ return;
+ }
+ sp<XMLNode> parent = st->stack.itemAt(st->stack.size()-1);
+ if (parent != NULL && parent->getChildren().size() > 0) {
+ node = parent->getChildren()[parent->getChildren().size()-1];
+ if (node->getType() != TYPE_CDATA) {
+ // Last node is not CDATA, need to make a new node.
+ node = NULL;
+ }
+ }
+
+ if (node == NULL) {
+ node = XMLNode::newCData(st->filename);
+ node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
+ parent->addChild(node);
+ }
+
+ node->appendChars(String16(s, len));
+}
+
+void XMLCALL
+XMLNode::endElement(void *userData, const char *name)
+{
+ NOISY_PARSE(printf("End Element: %s\n", name));
+ ParseState* st = (ParseState*)userData;
+ sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1);
+ node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
+ if (st->pendingComment.size() > 0) {
+ node->appendComment(st->pendingComment);
+ st->pendingComment = String16();
+ }
+ String16 ns16, name16;
+ splitName(name, &ns16, &name16);
+ LOG_ALWAYS_FATAL_IF(node->getElementNamespace() != ns16
+ || node->getElementName() != name16,
+ "Bad end element %s", name);
+ st->stack.pop();
+}
+
+void XMLCALL
+XMLNode::endNamespace(void *userData, const char *prefix)
+{
+ const char* nonNullPrefix = prefix != NULL ? prefix : "";
+ NOISY_PARSE(printf("End Namespace: %s\n", prefix));
+ ParseState* st = (ParseState*)userData;
+ sp<XMLNode> node = st->stack.itemAt(st->stack.size()-1);
+ node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
+ LOG_ALWAYS_FATAL_IF(node->getNamespacePrefix() != String16(nonNullPrefix),
+ "Bad end namespace %s", prefix);
+ st->stack.pop();
+}
+
+void XMLCALL
+XMLNode::commentData(void *userData, const char *comment)
+{
+ NOISY_PARSE(printf("Comment: %s\n", comment));
+ ParseState* st = (ParseState*)userData;
+ if (st->pendingComment.size() > 0) {
+ st->pendingComment.append(String16("\n"));
+ }
+ st->pendingComment.append(String16(comment));
+}
+
+status_t XMLNode::collect_strings(StringPool* dest, Vector<uint32_t>* outResIds,
+ bool stripComments, bool stripRawValues) const
+{
+ collect_attr_strings(dest, outResIds, true);
+
+ int i;
+ if (RESOURCES_TOOLS_NAMESPACE != mNamespaceUri) {
+ if (mNamespacePrefix.size() > 0) {
+ dest->add(mNamespacePrefix, true);
+ }
+ if (mNamespaceUri.size() > 0) {
+ dest->add(mNamespaceUri, true);
+ }
+ }
+ if (mElementName.size() > 0) {
+ dest->add(mElementName, true);
+ }
+
+ if (!stripComments && mComment.size() > 0) {
+ dest->add(mComment, true);
+ }
+
+ const int NA = mAttributes.size();
+
+ for (i=0; i<NA; i++) {
+ const attribute_entry& ae = mAttributes.itemAt(i);
+ if (ae.ns.size() > 0) {
+ dest->add(ae.ns, true);
+ }
+ if (!stripRawValues || ae.needStringValue()) {
+ dest->add(ae.string, true);
+ }
+ /*
+ if (ae.value.dataType == Res_value::TYPE_NULL
+ || ae.value.dataType == Res_value::TYPE_STRING) {
+ dest->add(ae.string, true);
+ }
+ */
+ }
+
+ if (mElementName.size() == 0) {
+ // If not an element, include the CDATA, even if it is empty.
+ dest->add(mChars, true);
+ }
+
+ const int NC = mChildren.size();
+
+ for (i=0; i<NC; i++) {
+ mChildren.itemAt(i)->collect_strings(dest, outResIds,
+ stripComments, stripRawValues);
+ }
+
+ return NO_ERROR;
+}
+
+status_t XMLNode::collect_attr_strings(StringPool* outPool,
+ Vector<uint32_t>* outResIds, bool allAttrs) const {
+ const int NA = mAttributes.size();
+
+ for (int i=0; i<NA; i++) {
+ const attribute_entry& attr = mAttributes.itemAt(i);
+ uint32_t id = attr.nameResId;
+ if (id || allAttrs) {
+ // See if we have already assigned this resource ID to a pooled
+ // string...
+ const Vector<size_t>* indices = outPool->offsetsForString(attr.name);
+ ssize_t idx = -1;
+ if (indices != NULL) {
+ const int NJ = indices->size();
+ const size_t NR = outResIds->size();
+ for (int j=0; j<NJ; j++) {
+ size_t strIdx = indices->itemAt(j);
+ if (strIdx >= NR) {
+ if (id == 0) {
+ // We don't need to assign a resource ID for this one.
+ idx = strIdx;
+ break;
+ }
+ // Just ignore strings that are out of range of
+ // the currently assigned resource IDs... we add
+ // strings as we assign the first ID.
+ } else if (outResIds->itemAt(strIdx) == id) {
+ idx = strIdx;
+ break;
+ }
+ }
+ }
+ if (idx < 0) {
+ idx = outPool->add(attr.name);
+ NOISY(printf("Adding attr %s (resid 0x%08x) to pool: idx=%d\n",
+ String8(attr.name).string(), id, idx));
+ if (id != 0) {
+ while ((ssize_t)outResIds->size() <= idx) {
+ outResIds->add(0);
+ }
+ outResIds->replaceAt(id, idx);
+ }
+ }
+ attr.namePoolIdx = idx;
+ NOISY(printf("String %s offset=0x%08x\n",
+ String8(attr.name).string(), idx));
+ }
+ }
+
+ return NO_ERROR;
+}
+
+status_t XMLNode::collect_resid_strings(StringPool* outPool,
+ Vector<uint32_t>* outResIds) const
+{
+ collect_attr_strings(outPool, outResIds, false);
+
+ const int NC = mChildren.size();
+
+ for (int i=0; i<NC; i++) {
+ mChildren.itemAt(i)->collect_resid_strings(outPool, outResIds);
+ }
+
+ return NO_ERROR;
+}
+
+status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& dest,
+ bool stripComments, bool stripRawValues) const
+{
+ ResXMLTree_node node;
+ ResXMLTree_cdataExt cdataExt;
+ ResXMLTree_namespaceExt namespaceExt;
+ ResXMLTree_attrExt attrExt;
+ const void* extData = NULL;
+ size_t extSize = 0;
+ ResXMLTree_attribute attr;
+ bool writeCurrentNode = true;
+
+ const size_t NA = mAttributes.size();
+ const size_t NC = mChildren.size();
+ size_t i;
+
+ LOG_ALWAYS_FATAL_IF(NA != mAttributeOrder.size(), "Attributes messed up!");
+
+ const String16 id16("id");
+ const String16 class16("class");
+ const String16 style16("style");
+
+ const type type = getType();
+
+ memset(&node, 0, sizeof(node));
+ memset(&attr, 0, sizeof(attr));
+ node.header.headerSize = htods(sizeof(node));
+ node.lineNumber = htodl(getStartLineNumber());
+ if (!stripComments) {
+ node.comment.index = htodl(
+ mComment.size() > 0 ? strings.offsetForString(mComment) : -1);
+ //if (mComment.size() > 0) {
+ // printf("Flattening comment: %s\n", String8(mComment).string());
+ //}
+ } else {
+ node.comment.index = htodl((uint32_t)-1);
+ }
+ if (type == TYPE_ELEMENT) {
+ node.header.type = htods(RES_XML_START_ELEMENT_TYPE);
+ extData = &attrExt;
+ extSize = sizeof(attrExt);
+ memset(&attrExt, 0, sizeof(attrExt));
+ if (mNamespaceUri.size() > 0) {
+ attrExt.ns.index = htodl(strings.offsetForString(mNamespaceUri));
+ } else {
+ attrExt.ns.index = htodl((uint32_t)-1);
+ }
+ attrExt.name.index = htodl(strings.offsetForString(mElementName));
+ attrExt.attributeStart = htods(sizeof(attrExt));
+ attrExt.attributeSize = htods(sizeof(attr));
+ attrExt.attributeCount = htods(NA);
+ attrExt.idIndex = htods(0);
+ attrExt.classIndex = htods(0);
+ attrExt.styleIndex = htods(0);
+ for (i=0; i<NA; i++) {
+ ssize_t idx = mAttributeOrder.valueAt(i);
+ const attribute_entry& ae = mAttributes.itemAt(idx);
+ if (ae.ns.size() == 0) {
+ if (ae.name == id16) {
+ attrExt.idIndex = htods(i+1);
+ } else if (ae.name == class16) {
+ attrExt.classIndex = htods(i+1);
+ } else if (ae.name == style16) {
+ attrExt.styleIndex = htods(i+1);
+ }
+ }
+ }
+ } else if (type == TYPE_NAMESPACE) {
+ if (mNamespaceUri == RESOURCES_TOOLS_NAMESPACE) {
+ writeCurrentNode = false;
+ } else {
+ node.header.type = htods(RES_XML_START_NAMESPACE_TYPE);
+ extData = &namespaceExt;
+ extSize = sizeof(namespaceExt);
+ memset(&namespaceExt, 0, sizeof(namespaceExt));
+ if (mNamespacePrefix.size() > 0) {
+ namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
+ } else {
+ namespaceExt.prefix.index = htodl((uint32_t)-1);
+ }
+ namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
+ namespaceExt.uri.index = htodl(strings.offsetForString(mNamespaceUri));
+ }
+ LOG_ALWAYS_FATAL_IF(NA != 0, "Namespace nodes can't have attributes!");
+ } else if (type == TYPE_CDATA) {
+ node.header.type = htods(RES_XML_CDATA_TYPE);
+ extData = &cdataExt;
+ extSize = sizeof(cdataExt);
+ memset(&cdataExt, 0, sizeof(cdataExt));
+ cdataExt.data.index = htodl(strings.offsetForString(mChars));
+ cdataExt.typedData.size = htods(sizeof(cdataExt.typedData));
+ cdataExt.typedData.res0 = 0;
+ cdataExt.typedData.dataType = mCharsValue.dataType;
+ cdataExt.typedData.data = htodl(mCharsValue.data);
+ LOG_ALWAYS_FATAL_IF(NA != 0, "CDATA nodes can't have attributes!");
+ }
+
+ node.header.size = htodl(sizeof(node) + extSize + (sizeof(attr)*NA));
+
+ if (writeCurrentNode) {
+ dest->writeData(&node, sizeof(node));
+ if (extSize > 0) {
+ dest->writeData(extData, extSize);
+ }
+ }
+
+ for (i=0; i<NA; i++) {
+ ssize_t idx = mAttributeOrder.valueAt(i);
+ const attribute_entry& ae = mAttributes.itemAt(idx);
+ if (ae.ns.size() > 0) {
+ attr.ns.index = htodl(strings.offsetForString(ae.ns));
+ } else {
+ attr.ns.index = htodl((uint32_t)-1);
+ }
+ attr.name.index = htodl(ae.namePoolIdx);
+
+ if (!stripRawValues || ae.needStringValue()) {
+ attr.rawValue.index = htodl(strings.offsetForString(ae.string));
+ } else {
+ attr.rawValue.index = htodl((uint32_t)-1);
+ }
+ attr.typedValue.size = htods(sizeof(attr.typedValue));
+ if (ae.value.dataType == Res_value::TYPE_NULL
+ || ae.value.dataType == Res_value::TYPE_STRING) {
+ attr.typedValue.res0 = 0;
+ attr.typedValue.dataType = Res_value::TYPE_STRING;
+ attr.typedValue.data = htodl(strings.offsetForString(ae.string));
+ } else {
+ attr.typedValue.res0 = 0;
+ attr.typedValue.dataType = ae.value.dataType;
+ attr.typedValue.data = htodl(ae.value.data);
+ }
+ dest->writeData(&attr, sizeof(attr));
+ }
+
+ for (i=0; i<NC; i++) {
+ status_t err = mChildren.itemAt(i)->flatten_node(strings, dest,
+ stripComments, stripRawValues);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+
+ if (type == TYPE_ELEMENT) {
+ ResXMLTree_endElementExt endElementExt;
+ memset(&endElementExt, 0, sizeof(endElementExt));
+ node.header.type = htods(RES_XML_END_ELEMENT_TYPE);
+ node.header.size = htodl(sizeof(node)+sizeof(endElementExt));
+ node.lineNumber = htodl(getEndLineNumber());
+ node.comment.index = htodl((uint32_t)-1);
+ endElementExt.ns.index = attrExt.ns.index;
+ endElementExt.name.index = attrExt.name.index;
+ dest->writeData(&node, sizeof(node));
+ dest->writeData(&endElementExt, sizeof(endElementExt));
+ } else if (type == TYPE_NAMESPACE) {
+ if (writeCurrentNode) {
+ node.header.type = htods(RES_XML_END_NAMESPACE_TYPE);
+ node.lineNumber = htodl(getEndLineNumber());
+ node.comment.index = htodl((uint32_t)-1);
+ node.header.size = htodl(sizeof(node)+extSize);
+ dest->writeData(&node, sizeof(node));
+ dest->writeData(extData, extSize);
+ }
+ }
+
+ return NO_ERROR;
+}
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
new file mode 100644
index 0000000..05624b7
--- /dev/null
+++ b/tools/aapt/XMLNode.h
@@ -0,0 +1,202 @@
+//
+// Copyright 2006 The Android Open Source Project
+//
+// Build resource files from raw assets.
+//
+
+#ifndef XML_NODE_H
+#define XML_NODE_H
+
+#include "StringPool.h"
+#include "ResourceTable.h"
+
+class XMLNode;
+
+extern const char* const RESOURCES_ROOT_NAMESPACE;
+extern const char* const RESOURCES_ANDROID_NAMESPACE;
+
+bool isWhitespace(const char16_t* str);
+
+String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic = NULL);
+
+status_t parseStyledString(Bundle* bundle,
+ const char* fileName,
+ ResXMLTree* inXml,
+ const String16& endTag,
+ String16* outString,
+ Vector<StringPool::entry_style_span>* outSpans,
+ bool isFormatted,
+ bool isPseudolocalizable);
+
+void printXMLBlock(ResXMLTree* block);
+
+status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
+ bool stripAll=true, bool keepComments=false,
+ const char** cDataTags=NULL);
+
+class XMLNode : public RefBase
+{
+public:
+ static sp<XMLNode> parse(const sp<AaptFile>& file);
+
+ static inline
+ sp<XMLNode> newNamespace(const String8& filename, const String16& prefix, const String16& uri) {
+ return new XMLNode(filename, prefix, uri, true);
+ }
+
+ static inline
+ sp<XMLNode> newElement(const String8& filename, const String16& ns, const String16& name) {
+ return new XMLNode(filename, ns, name, false);
+ }
+
+ static inline
+ sp<XMLNode> newCData(const String8& filename) {
+ return new XMLNode(filename);
+ }
+
+ enum type {
+ TYPE_NAMESPACE,
+ TYPE_ELEMENT,
+ TYPE_CDATA
+ };
+
+ type getType() const;
+
+ const String16& getNamespacePrefix() const;
+ const String16& getNamespaceUri() const;
+
+ const String16& getElementNamespace() const;
+ const String16& getElementName() const;
+ const Vector<sp<XMLNode> >& getChildren() const;
+
+ const String8& getFilename() const;
+
+ struct attribute_entry {
+ attribute_entry() : index(~(uint32_t)0), nameResId(0)
+ {
+ value.dataType = Res_value::TYPE_NULL;
+ }
+
+ bool needStringValue() const {
+ return nameResId == 0
+ || value.dataType == Res_value::TYPE_NULL
+ || value.dataType == Res_value::TYPE_STRING;
+ }
+
+ String16 ns;
+ String16 name;
+ String16 string;
+ Res_value value;
+ uint32_t index;
+ uint32_t nameResId;
+ mutable uint32_t namePoolIdx;
+ };
+
+ const Vector<attribute_entry>& getAttributes() const;
+
+ const attribute_entry* getAttribute(const String16& ns, const String16& name) const;
+
+ attribute_entry* editAttribute(const String16& ns, const String16& name);
+
+ const String16& getCData() const;
+
+ const String16& getComment() const;
+
+ int32_t getStartLineNumber() const;
+ int32_t getEndLineNumber() const;
+
+ sp<XMLNode> searchElement(const String16& tagNamespace, const String16& tagName);
+
+ sp<XMLNode> getChildElement(const String16& tagNamespace, const String16& tagName);
+
+ status_t addChild(const sp<XMLNode>& child);
+
+ status_t insertChildAt(const sp<XMLNode>& child, size_t index);
+
+ status_t addAttribute(const String16& ns, const String16& name,
+ const String16& value);
+
+ void setAttributeResID(size_t attrIdx, uint32_t resId);
+
+ status_t appendChars(const String16& chars);
+
+ status_t appendComment(const String16& comment);
+
+ void setStartLineNumber(int32_t line);
+ void setEndLineNumber(int32_t line);
+
+ void removeWhitespace(bool stripAll=true, const char** cDataTags=NULL);
+
+ void setUTF8(bool val) { mUTF8 = val; }
+
+ status_t parseValues(const sp<AaptAssets>& assets, ResourceTable* table);
+
+ status_t assignResourceIds(const sp<AaptAssets>& assets,
+ const ResourceTable* table = NULL);
+
+ status_t flatten(const sp<AaptFile>& dest, bool stripComments,
+ bool stripRawValues) const;
+
+ void print(int indent=0);
+
+private:
+ struct ParseState
+ {
+ String8 filename;
+ XML_Parser parser;
+ sp<XMLNode> root;
+ Vector<sp<XMLNode> > stack;
+ String16 pendingComment;
+ };
+
+ static void XMLCALL
+ startNamespace(void *userData, const char *prefix, const char *uri);
+ static void XMLCALL
+ startElement(void *userData, const char *name, const char **atts);
+ static void XMLCALL
+ characterData(void *userData, const XML_Char *s, int len);
+ static void XMLCALL
+ endElement(void *userData, const char *name);
+ static void XMLCALL
+ endNamespace(void *userData, const char *prefix);
+
+ static void XMLCALL
+ commentData(void *userData, const char *comment);
+
+ // Creating an element node.
+ XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace);
+
+ // Creating a CDATA node.
+ XMLNode(const String8& filename);
+
+ status_t collect_strings(StringPool* dest, Vector<uint32_t>* outResIds,
+ bool stripComments, bool stripRawValues) const;
+
+ status_t collect_attr_strings(StringPool* outPool,
+ Vector<uint32_t>* outResIds, bool allAttrs) const;
+
+ status_t collect_resid_strings(StringPool* outPool,
+ Vector<uint32_t>* outResIds) const;
+
+ status_t flatten_node(const StringPool& strings, const sp<AaptFile>& dest,
+ bool stripComments, bool stripRawValues) const;
+
+ String16 mNamespacePrefix;
+ String16 mNamespaceUri;
+ String16 mElementName;
+ Vector<sp<XMLNode> > mChildren;
+ Vector<attribute_entry> mAttributes;
+ KeyedVector<uint32_t, uint32_t> mAttributeOrder;
+ uint32_t mNextAttributeIndex;
+ String16 mChars;
+ Res_value mCharsValue;
+ String16 mComment;
+ String8 mFilename;
+ int32_t mStartLineNumber;
+ int32_t mEndLineNumber;
+
+ // Encode compiled XML with UTF-8 StringPools?
+ bool mUTF8;
+};
+
+#endif
diff --git a/tools/aapt/ZipEntry.cpp b/tools/aapt/ZipEntry.cpp
new file mode 100644
index 0000000..b575988
--- /dev/null
+++ b/tools/aapt/ZipEntry.cpp
@@ -0,0 +1,696 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Access to entries in a Zip archive.
+//
+
+#define LOG_TAG "zip"
+
+#include "ZipEntry.h"
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+using namespace android;
+
+/*
+ * Initialize a new ZipEntry structure from a FILE* positioned at a
+ * CentralDirectoryEntry.
+ *
+ * On exit, the file pointer will be at the start of the next CDE or
+ * at the EOCD.
+ */
+status_t ZipEntry::initFromCDE(FILE* fp)
+{
+ status_t result;
+ long posn;
+ bool hasDD;
+
+ //ALOGV("initFromCDE ---\n");
+
+ /* read the CDE */
+ result = mCDE.read(fp);
+ if (result != NO_ERROR) {
+ ALOGD("mCDE.read failed\n");
+ return result;
+ }
+
+ //mCDE.dump();
+
+ /* using the info in the CDE, go load up the LFH */
+ posn = ftell(fp);
+ if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
+ ALOGD("local header seek failed (%ld)\n",
+ mCDE.mLocalHeaderRelOffset);
+ return UNKNOWN_ERROR;
+ }
+
+ result = mLFH.read(fp);
+ if (result != NO_ERROR) {
+ ALOGD("mLFH.read failed\n");
+ return result;
+ }
+
+ if (fseek(fp, posn, SEEK_SET) != 0)
+ return UNKNOWN_ERROR;
+
+ //mLFH.dump();
+
+ /*
+ * We *might* need to read the Data Descriptor at this point and
+ * integrate it into the LFH. If this bit is set, the CRC-32,
+ * compressed size, and uncompressed size will be zero. In practice
+ * these seem to be rare.
+ */
+ hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
+ if (hasDD) {
+ // do something clever
+ //ALOGD("+++ has data descriptor\n");
+ }
+
+ /*
+ * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr"
+ * flag is set, because the LFH is incomplete. (Not a problem, since we
+ * prefer the CDE values.)
+ */
+ if (!hasDD && !compareHeaders()) {
+ ALOGW("warning: header mismatch\n");
+ // keep going?
+ }
+
+ /*
+ * If the mVersionToExtract is greater than 20, we may have an
+ * issue unpacking the record -- could be encrypted, compressed
+ * with something we don't support, or use Zip64 extensions. We
+ * can defer worrying about that to when we're extracting data.
+ */
+
+ return NO_ERROR;
+}
+
+/*
+ * Initialize a new entry. Pass in the file name and an optional comment.
+ *
+ * Initializes the CDE and the LFH.
+ */
+void ZipEntry::initNew(const char* fileName, const char* comment)
+{
+ assert(fileName != NULL && *fileName != '\0'); // name required
+
+ /* most fields are properly initialized by constructor */
+ mCDE.mVersionMadeBy = kDefaultMadeBy;
+ mCDE.mVersionToExtract = kDefaultVersion;
+ mCDE.mCompressionMethod = kCompressStored;
+ mCDE.mFileNameLength = strlen(fileName);
+ if (comment != NULL)
+ mCDE.mFileCommentLength = strlen(comment);
+ mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does
+
+ if (mCDE.mFileNameLength > 0) {
+ mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
+ strcpy((char*) mCDE.mFileName, fileName);
+ }
+ if (mCDE.mFileCommentLength > 0) {
+ /* TODO: stop assuming null-terminated ASCII here? */
+ mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
+ strcpy((char*) mCDE.mFileComment, comment);
+ }
+
+ copyCDEtoLFH();
+}
+
+/*
+ * Initialize a new entry, starting with the ZipEntry from a different
+ * archive.
+ *
+ * Initializes the CDE and the LFH.
+ */
+status_t ZipEntry::initFromExternal(const ZipFile* pZipFile,
+ const ZipEntry* pEntry)
+{
+ /*
+ * Copy everything in the CDE over, then fix up the hairy bits.
+ */
+ memcpy(&mCDE, &pEntry->mCDE, sizeof(mCDE));
+
+ if (mCDE.mFileNameLength > 0) {
+ mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
+ if (mCDE.mFileName == NULL)
+ return NO_MEMORY;
+ strcpy((char*) mCDE.mFileName, (char*)pEntry->mCDE.mFileName);
+ }
+ if (mCDE.mFileCommentLength > 0) {
+ mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
+ if (mCDE.mFileComment == NULL)
+ return NO_MEMORY;
+ strcpy((char*) mCDE.mFileComment, (char*)pEntry->mCDE.mFileComment);
+ }
+ if (mCDE.mExtraFieldLength > 0) {
+ /* we null-terminate this, though it may not be a string */
+ mCDE.mExtraField = new unsigned char[mCDE.mExtraFieldLength+1];
+ if (mCDE.mExtraField == NULL)
+ return NO_MEMORY;
+ memcpy(mCDE.mExtraField, pEntry->mCDE.mExtraField,
+ mCDE.mExtraFieldLength+1);
+ }
+
+ /* construct the LFH from the CDE */
+ copyCDEtoLFH();
+
+ /*
+ * The LFH "extra" field is independent of the CDE "extra", so we
+ * handle it here.
+ */
+ assert(mLFH.mExtraField == NULL);
+ mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
+ if (mLFH.mExtraFieldLength > 0) {
+ mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
+ if (mLFH.mExtraField == NULL)
+ return NO_MEMORY;
+ memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
+ mLFH.mExtraFieldLength+1);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Insert pad bytes in the LFH by tweaking the "extra" field. This will
+ * potentially confuse something that put "extra" data in here earlier,
+ * but I can't find an actual problem.
+ */
+status_t ZipEntry::addPadding(int padding)
+{
+ if (padding <= 0)
+ return INVALID_OPERATION;
+
+ //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
+ // padding, mLFH.mExtraFieldLength, mCDE.mFileName);
+
+ if (mLFH.mExtraFieldLength > 0) {
+ /* extend existing field */
+ unsigned char* newExtra;
+
+ newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
+ if (newExtra == NULL)
+ return NO_MEMORY;
+ memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
+ memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
+
+ delete[] mLFH.mExtraField;
+ mLFH.mExtraField = newExtra;
+ mLFH.mExtraFieldLength += padding;
+ } else {
+ /* create new field */
+ mLFH.mExtraField = new unsigned char[padding];
+ memset(mLFH.mExtraField, 0, padding);
+ mLFH.mExtraFieldLength = padding;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Set the fields in the LFH equal to the corresponding fields in the CDE.
+ *
+ * This does not touch the LFH "extra" field.
+ */
+void ZipEntry::copyCDEtoLFH(void)
+{
+ mLFH.mVersionToExtract = mCDE.mVersionToExtract;
+ mLFH.mGPBitFlag = mCDE.mGPBitFlag;
+ mLFH.mCompressionMethod = mCDE.mCompressionMethod;
+ mLFH.mLastModFileTime = mCDE.mLastModFileTime;
+ mLFH.mLastModFileDate = mCDE.mLastModFileDate;
+ mLFH.mCRC32 = mCDE.mCRC32;
+ mLFH.mCompressedSize = mCDE.mCompressedSize;
+ mLFH.mUncompressedSize = mCDE.mUncompressedSize;
+ mLFH.mFileNameLength = mCDE.mFileNameLength;
+ // the "extra field" is independent
+
+ delete[] mLFH.mFileName;
+ if (mLFH.mFileNameLength > 0) {
+ mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
+ strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
+ } else {
+ mLFH.mFileName = NULL;
+ }
+}
+
+/*
+ * Set some information about a file after we add it.
+ */
+void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod)
+{
+ mCDE.mCompressionMethod = compressionMethod;
+ mCDE.mCRC32 = crc32;
+ mCDE.mCompressedSize = compLen;
+ mCDE.mUncompressedSize = uncompLen;
+ mCDE.mCompressionMethod = compressionMethod;
+ if (compressionMethod == kCompressDeflated) {
+ mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used
+ }
+ copyCDEtoLFH();
+}
+
+/*
+ * See if the data in mCDE and mLFH match up. This is mostly useful for
+ * debugging these classes, but it can be used to identify damaged
+ * archives.
+ *
+ * Returns "false" if they differ.
+ */
+bool ZipEntry::compareHeaders(void) const
+{
+ if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
+ ALOGV("cmp: VersionToExtract\n");
+ return false;
+ }
+ if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
+ ALOGV("cmp: GPBitFlag\n");
+ return false;
+ }
+ if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
+ ALOGV("cmp: CompressionMethod\n");
+ return false;
+ }
+ if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
+ ALOGV("cmp: LastModFileTime\n");
+ return false;
+ }
+ if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
+ ALOGV("cmp: LastModFileDate\n");
+ return false;
+ }
+ if (mCDE.mCRC32 != mLFH.mCRC32) {
+ ALOGV("cmp: CRC32\n");
+ return false;
+ }
+ if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
+ ALOGV("cmp: CompressedSize\n");
+ return false;
+ }
+ if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
+ ALOGV("cmp: UncompressedSize\n");
+ return false;
+ }
+ if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
+ ALOGV("cmp: FileNameLength\n");
+ return false;
+ }
+#if 0 // this seems to be used for padding, not real data
+ if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
+ ALOGV("cmp: ExtraFieldLength\n");
+ return false;
+ }
+#endif
+ if (mCDE.mFileName != NULL) {
+ if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
+ ALOGV("cmp: FileName\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/*
+ * Convert the DOS date/time stamp into a UNIX time stamp.
+ */
+time_t ZipEntry::getModWhen(void) const
+{
+ struct tm parts;
+
+ parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
+ parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
+ parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
+ parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
+ parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
+ parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
+ parts.tm_wday = parts.tm_yday = 0;
+ parts.tm_isdst = -1; // DST info "not available"
+
+ return mktime(&parts);
+}
+
+/*
+ * Set the CDE/LFH timestamp from UNIX time.
+ */
+void ZipEntry::setModWhen(time_t when)
+{
+#ifdef HAVE_LOCALTIME_R
+ struct tm tmResult;
+#endif
+ time_t even;
+ unsigned short zdate, ztime;
+
+ struct tm* ptm;
+
+ /* round up to an even number of seconds */
+ even = (time_t)(((unsigned long)(when) + 1) & (~1));
+
+ /* expand */
+#ifdef HAVE_LOCALTIME_R
+ ptm = localtime_r(&even, &tmResult);
+#else
+ ptm = localtime(&even);
+#endif
+
+ int year;
+ year = ptm->tm_year;
+ if (year < 80)
+ year = 80;
+
+ zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
+ ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
+
+ mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
+ mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
+}
+
+
+/*
+ * ===========================================================================
+ * ZipEntry::LocalFileHeader
+ * ===========================================================================
+ */
+
+/*
+ * Read a local file header.
+ *
+ * On entry, "fp" points to the signature at the start of the header.
+ * On exit, "fp" points to the start of data.
+ */
+status_t ZipEntry::LocalFileHeader::read(FILE* fp)
+{
+ status_t result = NO_ERROR;
+ unsigned char buf[kLFHLen];
+
+ assert(mFileName == NULL);
+ assert(mExtraField == NULL);
+
+ if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ ALOGD("whoops: didn't find expected signature\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
+ mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
+ mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
+ mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
+ mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
+ mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
+ mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
+ mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
+ mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
+ mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
+
+ // TODO: validate sizes
+
+ /* grab filename */
+ if (mFileNameLength != 0) {
+ mFileName = new unsigned char[mFileNameLength+1];
+ if (mFileName == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileName[mFileNameLength] = '\0';
+ }
+
+ /* grab extra field */
+ if (mExtraFieldLength != 0) {
+ mExtraField = new unsigned char[mExtraFieldLength+1];
+ if (mExtraField == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mExtraField[mExtraFieldLength] = '\0';
+ }
+
+bail:
+ return result;
+}
+
+/*
+ * Write a local file header.
+ */
+status_t ZipEntry::LocalFileHeader::write(FILE* fp)
+{
+ unsigned char buf[kLFHLen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
+ ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
+ ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
+ ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
+ ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
+ ZipEntry::putLongLE(&buf[0x0e], mCRC32);
+ ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
+ ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
+ ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
+ ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
+
+ if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
+ return UNKNOWN_ERROR;
+
+ /* write filename */
+ if (mFileNameLength != 0) {
+ if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write "extra field" */
+ if (mExtraFieldLength != 0) {
+ if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
+/*
+ * Dump the contents of a LocalFileHeader object.
+ */
+void ZipEntry::LocalFileHeader::dump(void) const
+{
+ ALOGD(" LocalFileHeader contents:\n");
+ ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n",
+ mVersionToExtract, mGPBitFlag, mCompressionMethod);
+ ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+ mLastModFileTime, mLastModFileDate, mCRC32);
+ ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
+ mCompressedSize, mUncompressedSize);
+ ALOGD(" filenameLen=%u extraLen=%u\n",
+ mFileNameLength, mExtraFieldLength);
+ if (mFileName != NULL)
+ ALOGD(" filename: '%s'\n", mFileName);
+}
+
+
+/*
+ * ===========================================================================
+ * ZipEntry::CentralDirEntry
+ * ===========================================================================
+ */
+
+/*
+ * Read the central dir entry that appears next in the file.
+ *
+ * On entry, "fp" should be positioned on the signature bytes for the
+ * entry. On exit, "fp" will point at the signature word for the next
+ * entry or for the EOCD.
+ */
+status_t ZipEntry::CentralDirEntry::read(FILE* fp)
+{
+ status_t result = NO_ERROR;
+ unsigned char buf[kCDELen];
+
+ /* no re-use */
+ assert(mFileName == NULL);
+ assert(mExtraField == NULL);
+ assert(mFileComment == NULL);
+
+ if (fread(buf, 1, kCDELen, fp) != kCDELen) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ ALOGD("Whoops: didn't find expected signature\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
+ mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
+ mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
+ mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
+ mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
+ mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
+ mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
+ mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
+ mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
+ mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
+ mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
+ mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
+ mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
+ mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
+ mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
+ mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
+
+ // TODO: validate sizes and offsets
+
+ /* grab filename */
+ if (mFileNameLength != 0) {
+ mFileName = new unsigned char[mFileNameLength+1];
+ if (mFileName == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileName[mFileNameLength] = '\0';
+ }
+
+ /* read "extra field" */
+ if (mExtraFieldLength != 0) {
+ mExtraField = new unsigned char[mExtraFieldLength+1];
+ if (mExtraField == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mExtraField[mExtraFieldLength] = '\0';
+ }
+
+
+ /* grab comment, if any */
+ if (mFileCommentLength != 0) {
+ mFileComment = new unsigned char[mFileCommentLength+1];
+ if (mFileComment == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+ {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileComment[mFileCommentLength] = '\0';
+ }
+
+bail:
+ return result;
+}
+
+/*
+ * Write a central dir entry.
+ */
+status_t ZipEntry::CentralDirEntry::write(FILE* fp)
+{
+ unsigned char buf[kCDELen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
+ ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
+ ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
+ ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
+ ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
+ ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
+ ZipEntry::putLongLE(&buf[0x10], mCRC32);
+ ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
+ ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
+ ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
+ ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
+ ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
+ ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
+ ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
+ ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
+ ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
+
+ if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
+ return UNKNOWN_ERROR;
+
+ /* write filename */
+ if (mFileNameLength != 0) {
+ if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write "extra field" */
+ if (mExtraFieldLength != 0) {
+ if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write comment */
+ if (mFileCommentLength != 0) {
+ if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Dump the contents of a CentralDirEntry object.
+ */
+void ZipEntry::CentralDirEntry::dump(void) const
+{
+ ALOGD(" CentralDirEntry contents:\n");
+ ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
+ mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
+ ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+ mLastModFileTime, mLastModFileDate, mCRC32);
+ ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
+ mCompressedSize, mUncompressedSize);
+ ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n",
+ mFileNameLength, mExtraFieldLength, mFileCommentLength);
+ ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
+ mDiskNumberStart, mInternalAttrs, mExternalAttrs,
+ mLocalHeaderRelOffset);
+
+ if (mFileName != NULL)
+ ALOGD(" filename: '%s'\n", mFileName);
+ if (mFileComment != NULL)
+ ALOGD(" comment: '%s'\n", mFileComment);
+}
+
diff --git a/tools/aapt/ZipEntry.h b/tools/aapt/ZipEntry.h
new file mode 100644
index 0000000..c2f3227
--- /dev/null
+++ b/tools/aapt/ZipEntry.h
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Zip archive entries.
+//
+// The ZipEntry class is tightly meshed with the ZipFile class.
+//
+#ifndef __LIBS_ZIPENTRY_H
+#define __LIBS_ZIPENTRY_H
+
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+namespace android {
+
+class ZipFile;
+
+/*
+ * ZipEntry objects represent a single entry in a Zip archive.
+ *
+ * You can use one of these to get or set information about an entry, but
+ * there are no functions here for accessing the data itself. (We could
+ * tuck a pointer to the ZipFile in here for convenience, but that raises
+ * the likelihood of using ZipEntry objects after discarding the ZipFile.)
+ *
+ * File information is stored in two places: next to the file data (the Local
+ * File Header, and possibly a Data Descriptor), and at the end of the file
+ * (the Central Directory Entry). The two must be kept in sync.
+ */
+class ZipEntry {
+public:
+ friend class ZipFile;
+
+ ZipEntry(void)
+ : mDeleted(false), mMarked(false)
+ {}
+ ~ZipEntry(void) {}
+
+ /*
+ * Returns "true" if the data is compressed.
+ */
+ bool isCompressed(void) const {
+ return mCDE.mCompressionMethod != kCompressStored;
+ }
+ int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
+
+ /*
+ * Return the uncompressed length.
+ */
+ off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
+
+ /*
+ * Return the compressed length. For uncompressed data, this returns
+ * the same thing as getUncompresesdLen().
+ */
+ off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
+
+ /*
+ * Return the offset of the local file header.
+ */
+ off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
+
+ /*
+ * Return the absolute file offset of the start of the compressed or
+ * uncompressed data.
+ */
+ off_t getFileOffset(void) const {
+ return mCDE.mLocalHeaderRelOffset +
+ LocalFileHeader::kLFHLen +
+ mLFH.mFileNameLength +
+ mLFH.mExtraFieldLength;
+ }
+
+ /*
+ * Return the data CRC.
+ */
+ unsigned long getCRC32(void) const { return mCDE.mCRC32; }
+
+ /*
+ * Return file modification time in UNIX seconds-since-epoch.
+ */
+ time_t getModWhen(void) const;
+
+ /*
+ * Return the archived file name.
+ */
+ const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
+
+ /*
+ * Application-defined "mark". Can be useful when synchronizing the
+ * contents of an archive with contents on disk.
+ */
+ bool getMarked(void) const { return mMarked; }
+ void setMarked(bool val) { mMarked = val; }
+
+ /*
+ * Some basic functions for raw data manipulation. "LE" means
+ * Little Endian.
+ */
+ static inline unsigned short getShortLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8);
+ }
+ static inline unsigned long getLongLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ }
+ static inline void putShortLE(unsigned char* buf, short val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ }
+ static inline void putLongLE(unsigned char* buf, long val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ buf[2] = (unsigned char) (val >> 16);
+ buf[3] = (unsigned char) (val >> 24);
+ }
+
+ /* defined for Zip archives */
+ enum {
+ kCompressStored = 0, // no compression
+ // shrunk = 1,
+ // reduced 1 = 2,
+ // reduced 2 = 3,
+ // reduced 3 = 4,
+ // reduced 4 = 5,
+ // imploded = 6,
+ // tokenized = 7,
+ kCompressDeflated = 8, // standard deflate
+ // Deflate64 = 9,
+ // lib imploded = 10,
+ // reserved = 11,
+ // bzip2 = 12,
+ };
+
+ /*
+ * Deletion flag. If set, the entry will be removed on the next
+ * call to "flush".
+ */
+ bool getDeleted(void) const { return mDeleted; }
+
+protected:
+ /*
+ * Initialize the structure from the file, which is pointing at
+ * our Central Directory entry.
+ */
+ status_t initFromCDE(FILE* fp);
+
+ /*
+ * Initialize the structure for a new file. We need the filename
+ * and comment so that we can properly size the LFH area. The
+ * filename is mandatory, the comment is optional.
+ */
+ void initNew(const char* fileName, const char* comment);
+
+ /*
+ * Initialize the structure with the contents of a ZipEntry from
+ * another file.
+ */
+ status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry);
+
+ /*
+ * Add some pad bytes to the LFH. We do this by adding or resizing
+ * the "extra" field.
+ */
+ status_t addPadding(int padding);
+
+ /*
+ * Set information about the data for this entry.
+ */
+ void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod);
+
+ /*
+ * Set the modification date.
+ */
+ void setModWhen(time_t when);
+
+ /*
+ * Set the offset of the local file header, relative to the start of
+ * the current file.
+ */
+ void setLFHOffset(off_t offset) {
+ mCDE.mLocalHeaderRelOffset = (long) offset;
+ }
+
+ /* mark for deletion; used by ZipFile::remove() */
+ void setDeleted(void) { mDeleted = true; }
+
+private:
+ /* these are private and not defined */
+ ZipEntry(const ZipEntry& src);
+ ZipEntry& operator=(const ZipEntry& src);
+
+ /* returns "true" if the CDE and the LFH agree */
+ bool compareHeaders(void) const;
+ void copyCDEtoLFH(void);
+
+ bool mDeleted; // set if entry is pending deletion
+ bool mMarked; // app-defined marker
+
+ /*
+ * Every entry in the Zip archive starts off with one of these.
+ */
+ class LocalFileHeader {
+ public:
+ LocalFileHeader(void) :
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileName(NULL),
+ mExtraField(NULL)
+ {}
+ virtual ~LocalFileHeader(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ // unsigned long mSignature;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+
+ enum {
+ kSignature = 0x04034b50,
+ kLFHLen = 30, // LocalFileHdr len, excl. var fields
+ };
+
+ void dump(void) const;
+ };
+
+ /*
+ * Every entry in the Zip archive has one of these in the "central
+ * directory" at the end of the file.
+ */
+ class CentralDirEntry {
+ public:
+ CentralDirEntry(void) :
+ mVersionMadeBy(0),
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileCommentLength(0),
+ mDiskNumberStart(0),
+ mInternalAttrs(0),
+ mExternalAttrs(0),
+ mLocalHeaderRelOffset(0),
+ mFileName(NULL),
+ mExtraField(NULL),
+ mFileComment(NULL)
+ {}
+ virtual ~CentralDirEntry(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ delete[] mFileComment;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ // unsigned long mSignature;
+ unsigned short mVersionMadeBy;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned short mFileCommentLength;
+ unsigned short mDiskNumberStart;
+ unsigned short mInternalAttrs;
+ unsigned long mExternalAttrs;
+ unsigned long mLocalHeaderRelOffset;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+ unsigned char* mFileComment;
+
+ void dump(void) const;
+
+ enum {
+ kSignature = 0x02014b50,
+ kCDELen = 46, // CentralDirEnt len, excl. var fields
+ };
+ };
+
+ enum {
+ //kDataDescriptorSignature = 0x08074b50, // currently unused
+ kDataDescriptorLen = 16, // four 32-bit fields
+
+ kDefaultVersion = 20, // need deflate, nothing much else
+ kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3
+ kUsesDataDescr = 0x0008, // GPBitFlag bit 3
+ };
+
+ LocalFileHeader mLFH;
+ CentralDirEntry mCDE;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPENTRY_H
diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp
new file mode 100644
index 0000000..8057068
--- /dev/null
+++ b/tools/aapt/ZipFile.cpp
@@ -0,0 +1,1297 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Access to Zip archives.
+//
+
+#define LOG_TAG "zip"
+
+#include <androidfw/ZipUtils.h>
+#include <utils/Log.h>
+
+#include "ZipFile.h"
+
+#include <zlib.h>
+#define DEF_MEM_LEVEL 8 // normally in zutil.h?
+
+#include <memory.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <assert.h>
+
+using namespace android;
+
+/*
+ * Some environments require the "b", some choke on it.
+ */
+#define FILE_OPEN_RO "rb"
+#define FILE_OPEN_RW "r+b"
+#define FILE_OPEN_RW_CREATE "w+b"
+
+/* should live somewhere else? */
+static status_t errnoToStatus(int err)
+{
+ if (err == ENOENT)
+ return NAME_NOT_FOUND;
+ else if (err == EACCES)
+ return PERMISSION_DENIED;
+ else
+ return UNKNOWN_ERROR;
+}
+
+/*
+ * Open a file and parse its guts.
+ */
+status_t ZipFile::open(const char* zipFileName, int flags)
+{
+ bool newArchive = false;
+
+ assert(mZipFp == NULL); // no reopen
+
+ if ((flags & kOpenTruncate))
+ flags |= kOpenCreate; // trunc implies create
+
+ if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
+ return INVALID_OPERATION; // not both
+ if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
+ return INVALID_OPERATION; // not neither
+ if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
+ return INVALID_OPERATION; // create requires write
+
+ if (flags & kOpenTruncate) {
+ newArchive = true;
+ } else {
+ newArchive = (access(zipFileName, F_OK) != 0);
+ if (!(flags & kOpenCreate) && newArchive) {
+ /* not creating, must already exist */
+ ALOGD("File %s does not exist", zipFileName);
+ return NAME_NOT_FOUND;
+ }
+ }
+
+ /* open the file */
+ const char* openflags;
+ if (flags & kOpenReadWrite) {
+ if (newArchive)
+ openflags = FILE_OPEN_RW_CREATE;
+ else
+ openflags = FILE_OPEN_RW;
+ } else {
+ openflags = FILE_OPEN_RO;
+ }
+ mZipFp = fopen(zipFileName, openflags);
+ if (mZipFp == NULL) {
+ int err = errno;
+ ALOGD("fopen failed: %d\n", err);
+ return errnoToStatus(err);
+ }
+
+ status_t result;
+ if (!newArchive) {
+ /*
+ * Load the central directory. If that fails, then this probably
+ * isn't a Zip archive.
+ */
+ result = readCentralDir();
+ } else {
+ /*
+ * Newly-created. The EndOfCentralDir constructor actually
+ * sets everything to be the way we want it (all zeroes). We
+ * set mNeedCDRewrite so that we create *something* if the
+ * caller doesn't add any files. (We could also just unlink
+ * the file if it's brand new and nothing was added, but that's
+ * probably doing more than we really should -- the user might
+ * have a need for empty zip files.)
+ */
+ mNeedCDRewrite = true;
+ result = NO_ERROR;
+ }
+
+ if (flags & kOpenReadOnly)
+ mReadOnly = true;
+ else
+ assert(!mReadOnly);
+
+ return result;
+}
+
+/*
+ * Return the Nth entry in the archive.
+ */
+ZipEntry* ZipFile::getEntryByIndex(int idx) const
+{
+ if (idx < 0 || idx >= (int) mEntries.size())
+ return NULL;
+
+ return mEntries[idx];
+}
+
+/*
+ * Find an entry by name.
+ */
+ZipEntry* ZipFile::getEntryByName(const char* fileName) const
+{
+ /*
+ * Do a stupid linear string-compare search.
+ *
+ * There are various ways to speed this up, especially since it's rare
+ * to intermingle changes to the archive with "get by name" calls. We
+ * don't want to sort the mEntries vector itself, however, because
+ * it's used to recreate the Central Directory.
+ *
+ * (Hash table works, parallel list of pointers in sorted order is good.)
+ */
+ int idx;
+
+ for (idx = mEntries.size()-1; idx >= 0; idx--) {
+ ZipEntry* pEntry = mEntries[idx];
+ if (!pEntry->getDeleted() &&
+ strcmp(fileName, pEntry->getFileName()) == 0)
+ {
+ return pEntry;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Empty the mEntries vector.
+ */
+void ZipFile::discardEntries(void)
+{
+ int count = mEntries.size();
+
+ while (--count >= 0)
+ delete mEntries[count];
+
+ mEntries.clear();
+}
+
+
+/*
+ * Find the central directory and read the contents.
+ *
+ * The fun thing about ZIP archives is that they may or may not be
+ * readable from start to end. In some cases, notably for archives
+ * that were written to stdout, the only length information is in the
+ * central directory at the end of the file.
+ *
+ * Of course, the central directory can be followed by a variable-length
+ * comment field, so we have to scan through it backwards. The comment
+ * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
+ * itself, plus apparently sometimes people throw random junk on the end
+ * just for the fun of it.
+ *
+ * This is all a little wobbly. If the wrong value ends up in the EOCD
+ * area, we're hosed. This appears to be the way that everbody handles
+ * it though, so we're in pretty good company if this fails.
+ */
+status_t ZipFile::readCentralDir(void)
+{
+ status_t result = NO_ERROR;
+ unsigned char* buf = NULL;
+ off_t fileLength, seekStart;
+ long readAmount;
+ int i;
+
+ fseek(mZipFp, 0, SEEK_END);
+ fileLength = ftell(mZipFp);
+ rewind(mZipFp);
+
+ /* too small to be a ZIP archive? */
+ if (fileLength < EndOfCentralDir::kEOCDLen) {
+ ALOGD("Length is %ld -- too small\n", (long)fileLength);
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch];
+ if (buf == NULL) {
+ ALOGD("Failure allocating %d bytes for EOCD search",
+ EndOfCentralDir::kMaxEOCDSearch);
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
+ seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
+ readAmount = EndOfCentralDir::kMaxEOCDSearch;
+ } else {
+ seekStart = 0;
+ readAmount = (long) fileLength;
+ }
+ if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
+ ALOGD("Failure seeking to end of zip at %ld", (long) seekStart);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /* read the last part of the file into the buffer */
+ if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
+ ALOGD("short file? wanted %ld\n", readAmount);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /* find the end-of-central-dir magic */
+ for (i = readAmount - 4; i >= 0; i--) {
+ if (buf[i] == 0x50 &&
+ ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
+ {
+ ALOGV("+++ Found EOCD at buf+%d\n", i);
+ break;
+ }
+ }
+ if (i < 0) {
+ ALOGD("EOCD not found, not Zip\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ /* extract eocd values */
+ result = mEOCD.readBuf(buf + i, readAmount - i);
+ if (result != NO_ERROR) {
+ ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i);
+ goto bail;
+ }
+ //mEOCD.dump();
+
+ if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 ||
+ mEOCD.mNumEntries != mEOCD.mTotalNumEntries)
+ {
+ ALOGD("Archive spanning not supported\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ /*
+ * So far so good. "mCentralDirSize" is the size in bytes of the
+ * central directory, so we can just seek back that far to find it.
+ * We can also seek forward mCentralDirOffset bytes from the
+ * start of the file.
+ *
+ * We're not guaranteed to have the rest of the central dir in the
+ * buffer, nor are we guaranteed that the central dir will have any
+ * sort of convenient size. We need to skip to the start of it and
+ * read the header, then the other goodies.
+ *
+ * The only thing we really need right now is the file comment, which
+ * we're hoping to preserve.
+ */
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ ALOGD("Failure seeking to central dir offset %ld\n",
+ mEOCD.mCentralDirOffset);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * Loop through and read the central dir entries.
+ */
+ ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries);
+ int entry;
+ for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
+ ZipEntry* pEntry = new ZipEntry;
+
+ result = pEntry->initFromCDE(mZipFp);
+ if (result != NO_ERROR) {
+ ALOGD("initFromCDE failed\n");
+ delete pEntry;
+ goto bail;
+ }
+
+ mEntries.add(pEntry);
+ }
+
+
+ /*
+ * If all went well, we should now be back at the EOCD.
+ */
+ {
+ unsigned char checkBuf[4];
+ if (fread(checkBuf, 1, 4, mZipFp) != 4) {
+ ALOGD("EOCD check read failed\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+ if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
+ ALOGD("EOCD read check failed\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ ALOGV("+++ EOCD read check passed\n");
+ }
+
+bail:
+ delete[] buf;
+ return result;
+}
+
+
+/*
+ * Add a new file to the archive.
+ *
+ * This requires creating and populating a ZipEntry structure, and copying
+ * the data into the file at the appropriate position. The "appropriate
+ * position" is the current location of the central directory, which we
+ * casually overwrite (we can put it back later).
+ *
+ * If we were concerned about safety, we would want to make all changes
+ * in a temp file and then overwrite the original after everything was
+ * safely written. Not really a concern for us.
+ */
+status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry)
+{
+ ZipEntry* pEntry = NULL;
+ status_t result = NO_ERROR;
+ long lfhPosn, startPosn, endPosn, uncompressedLen;
+ FILE* inputFp = NULL;
+ unsigned long crc;
+ time_t modWhen;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+
+ assert(compressionMethod == ZipEntry::kCompressDeflated ||
+ compressionMethod == ZipEntry::kCompressStored);
+
+ /* make sure we're in a reasonable state */
+ assert(mZipFp != NULL);
+ assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+ /* make sure it doesn't already exist */
+ if (getEntryByName(storageName) != NULL)
+ return ALREADY_EXISTS;
+
+ if (!data) {
+ inputFp = fopen(fileName, FILE_OPEN_RO);
+ if (inputFp == NULL)
+ return errnoToStatus(errno);
+ }
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ pEntry = new ZipEntry;
+ pEntry->initNew(storageName, NULL);
+
+ /*
+ * From here on out, failures are more interesting.
+ */
+ mNeedCDRewrite = true;
+
+ /*
+ * Write the LFH, even though it's still mostly blank. We need it
+ * as a place-holder. In theory the LFH isn't necessary, but in
+ * practice some utilities demand it.
+ */
+ lfhPosn = ftell(mZipFp);
+ pEntry->mLFH.write(mZipFp);
+ startPosn = ftell(mZipFp);
+
+ /*
+ * Copy the data in, possibly compressing it as we go.
+ */
+ if (sourceType == ZipEntry::kCompressStored) {
+ if (compressionMethod == ZipEntry::kCompressDeflated) {
+ bool failed = false;
+ result = compressFpToFp(mZipFp, inputFp, data, size, &crc);
+ if (result != NO_ERROR) {
+ ALOGD("compression failed, storing\n");
+ failed = true;
+ } else {
+ /*
+ * Make sure it has compressed "enough". This probably ought
+ * to be set through an API call, but I don't expect our
+ * criteria to change over time.
+ */
+ long src = inputFp ? ftell(inputFp) : size;
+ long dst = ftell(mZipFp) - startPosn;
+ if (dst + (dst / 10) > src) {
+ ALOGD("insufficient compression (src=%ld dst=%ld), storing\n",
+ src, dst);
+ failed = true;
+ }
+ }
+
+ if (failed) {
+ compressionMethod = ZipEntry::kCompressStored;
+ if (inputFp) rewind(inputFp);
+ fseek(mZipFp, startPosn, SEEK_SET);
+ /* fall through to kCompressStored case */
+ }
+ }
+ /* handle "no compression" request, or failed compression from above */
+ if (compressionMethod == ZipEntry::kCompressStored) {
+ if (inputFp) {
+ result = copyFpToFp(mZipFp, inputFp, &crc);
+ } else {
+ result = copyDataToFp(mZipFp, data, size, &crc);
+ }
+ if (result != NO_ERROR) {
+ // don't need to truncate; happens in CDE rewrite
+ ALOGD("failed copying data in\n");
+ goto bail;
+ }
+ }
+
+ // currently seeked to end of file
+ uncompressedLen = inputFp ? ftell(inputFp) : size;
+ } else if (sourceType == ZipEntry::kCompressDeflated) {
+ /* we should support uncompressed-from-compressed, but it's not
+ * important right now */
+ assert(compressionMethod == ZipEntry::kCompressDeflated);
+
+ bool scanResult;
+ int method;
+ long compressedLen;
+
+ scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen,
+ &compressedLen, &crc);
+ if (!scanResult || method != ZipEntry::kCompressDeflated) {
+ ALOGD("this isn't a deflated gzip file?");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL);
+ if (result != NO_ERROR) {
+ ALOGD("failed copying gzip data in\n");
+ goto bail;
+ }
+ } else {
+ assert(false);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * We could write the "Data Descriptor", but there doesn't seem to
+ * be any point since we're going to go back and write the LFH.
+ *
+ * Update file offsets.
+ */
+ endPosn = ftell(mZipFp); // seeked to end of compressed data
+
+ /*
+ * Success! Fill out new values.
+ */
+ pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
+ compressionMethod);
+ modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
+ pEntry->setModWhen(modWhen);
+ pEntry->setLFHOffset(lfhPosn);
+ mEOCD.mNumEntries++;
+ mEOCD.mTotalNumEntries++;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+ mEOCD.mCentralDirOffset = endPosn;
+
+ /*
+ * Go back and write the LFH.
+ */
+ if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ pEntry->mLFH.write(mZipFp);
+
+ /*
+ * Add pEntry to the list.
+ */
+ mEntries.add(pEntry);
+ if (ppEntry != NULL)
+ *ppEntry = pEntry;
+ pEntry = NULL;
+
+bail:
+ if (inputFp != NULL)
+ fclose(inputFp);
+ delete pEntry;
+ return result;
+}
+
+/*
+ * Add an entry by copying it from another zip file. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ int padding, ZipEntry** ppEntry)
+{
+ ZipEntry* pEntry = NULL;
+ status_t result;
+ long lfhPosn, endPosn;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+
+ /* make sure we're in a reasonable state */
+ assert(mZipFp != NULL);
+ assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ pEntry = new ZipEntry;
+ if (pEntry == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ result = pEntry->initFromExternal(pSourceZip, pSourceEntry);
+ if (result != NO_ERROR)
+ goto bail;
+ if (padding != 0) {
+ result = pEntry->addPadding(padding);
+ if (result != NO_ERROR)
+ goto bail;
+ }
+
+ /*
+ * From here on out, failures are more interesting.
+ */
+ mNeedCDRewrite = true;
+
+ /*
+ * Write the LFH. Since we're not recompressing the data, we already
+ * have all of the fields filled out.
+ */
+ lfhPosn = ftell(mZipFp);
+ pEntry->mLFH.write(mZipFp);
+
+ /*
+ * Copy the data over.
+ *
+ * If the "has data descriptor" flag is set, we want to copy the DD
+ * fields as well. This is a fixed-size area immediately following
+ * the data.
+ */
+ if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0)
+ {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ off_t copyLen;
+ copyLen = pSourceEntry->getCompressedLen();
+ if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
+ copyLen += ZipEntry::kDataDescriptorLen;
+
+ if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
+ != NO_ERROR)
+ {
+ ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * Update file offsets.
+ */
+ endPosn = ftell(mZipFp);
+
+ /*
+ * Success! Fill out new values.
+ */
+ pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset
+ mEOCD.mNumEntries++;
+ mEOCD.mTotalNumEntries++;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+ mEOCD.mCentralDirOffset = endPosn;
+
+ /*
+ * Add pEntry to the list.
+ */
+ mEntries.add(pEntry);
+ if (ppEntry != NULL)
+ *ppEntry = pEntry;
+ pEntry = NULL;
+
+ result = NO_ERROR;
+
+bail:
+ delete pEntry;
+ return result;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data.
+ */
+status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32)
+{
+ unsigned char tmpBuf[32768];
+ size_t count;
+
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+
+ while (1) {
+ count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp);
+ if (ferror(srcFp) || ferror(dstFp))
+ return errnoToStatus(errno);
+ if (count == 0)
+ break;
+
+ *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+ if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+ ALOGD("fwrite %d bytes failed\n", (int) count);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "dstFp" will be seeked immediately past the data.
+ */
+status_t ZipFile::copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32)
+{
+ size_t count;
+
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+ if (size > 0) {
+ *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size);
+ if (fwrite(data, 1, size, dstFp) != size) {
+ ALOGD("fwrite %d bytes failed\n", (int) size);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Copy some of the bytes in "src" to "dst".
+ *
+ * If "pCRC32" is NULL, the CRC will not be computed.
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data just written.
+ */
+status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32)
+{
+ unsigned char tmpBuf[32768];
+ size_t count;
+
+ if (pCRC32 != NULL)
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+
+ while (length) {
+ long readSize;
+
+ readSize = sizeof(tmpBuf);
+ if (readSize > length)
+ readSize = length;
+
+ count = fread(tmpBuf, 1, readSize, srcFp);
+ if ((long) count != readSize) { // error or unexpected EOF
+ ALOGD("fread %d bytes failed\n", (int) readSize);
+ return UNKNOWN_ERROR;
+ }
+
+ if (pCRC32 != NULL)
+ *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+ if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+ ALOGD("fwrite %d bytes failed\n", (int) count);
+ return UNKNOWN_ERROR;
+ }
+
+ length -= readSize;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Compress all of the data in "srcFp" and write it to "dstFp".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the compressed data.
+ */
+status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32)
+{
+ status_t result = NO_ERROR;
+ const size_t kBufSize = 32768;
+ unsigned char* inBuf = NULL;
+ unsigned char* outBuf = NULL;
+ z_stream zstream;
+ bool atEof = false; // no feof() aviailable yet
+ unsigned long crc;
+ int zerr;
+
+ /*
+ * Create an input buffer and an output buffer.
+ */
+ inBuf = new unsigned char[kBufSize];
+ outBuf = new unsigned char[kBufSize];
+ if (inBuf == NULL || outBuf == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ /*
+ * Initialize the zlib stream.
+ */
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.zalloc = Z_NULL;
+ zstream.zfree = Z_NULL;
+ zstream.opaque = Z_NULL;
+ zstream.next_in = NULL;
+ zstream.avail_in = 0;
+ zstream.next_out = outBuf;
+ zstream.avail_out = kBufSize;
+ zstream.data_type = Z_UNKNOWN;
+
+ zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION,
+ Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+ if (zerr != Z_OK) {
+ result = UNKNOWN_ERROR;
+ if (zerr == Z_VERSION_ERROR) {
+ ALOGE("Installed zlib is not compatible with linked version (%s)\n",
+ ZLIB_VERSION);
+ } else {
+ ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr);
+ }
+ goto bail;
+ }
+
+ crc = crc32(0L, Z_NULL, 0);
+
+ /*
+ * Loop while we have data.
+ */
+ do {
+ size_t getSize;
+ int flush;
+
+ /* only read if the input buffer is empty */
+ if (zstream.avail_in == 0 && !atEof) {
+ ALOGV("+++ reading %d bytes\n", (int)kBufSize);
+ if (data) {
+ getSize = size > kBufSize ? kBufSize : size;
+ memcpy(inBuf, data, getSize);
+ data = ((const char*)data) + getSize;
+ size -= getSize;
+ } else {
+ getSize = fread(inBuf, 1, kBufSize, srcFp);
+ if (ferror(srcFp)) {
+ ALOGD("deflate read failed (errno=%d)\n", errno);
+ goto z_bail;
+ }
+ }
+ if (getSize < kBufSize) {
+ ALOGV("+++ got %d bytes, EOF reached\n",
+ (int)getSize);
+ atEof = true;
+ }
+
+ crc = crc32(crc, inBuf, getSize);
+
+ zstream.next_in = inBuf;
+ zstream.avail_in = getSize;
+ }
+
+ if (atEof)
+ flush = Z_FINISH; /* tell zlib that we're done */
+ else
+ flush = Z_NO_FLUSH; /* more to come! */
+
+ zerr = deflate(&zstream, flush);
+ if (zerr != Z_OK && zerr != Z_STREAM_END) {
+ ALOGD("zlib deflate call failed (zerr=%d)\n", zerr);
+ result = UNKNOWN_ERROR;
+ goto z_bail;
+ }
+
+ /* write when we're full or when we're done */
+ if (zstream.avail_out == 0 ||
+ (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize))
+ {
+ ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf));
+ if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) !=
+ (size_t)(zstream.next_out - outBuf))
+ {
+ ALOGD("write %d failed in deflate\n",
+ (int) (zstream.next_out - outBuf));
+ goto z_bail;
+ }
+
+ zstream.next_out = outBuf;
+ zstream.avail_out = kBufSize;
+ }
+ } while (zerr == Z_OK);
+
+ assert(zerr == Z_STREAM_END); /* other errors should've been caught */
+
+ *pCRC32 = crc;
+
+z_bail:
+ deflateEnd(&zstream); /* free up any allocated structures */
+
+bail:
+ delete[] inBuf;
+ delete[] outBuf;
+
+ return result;
+}
+
+/*
+ * Mark an entry as deleted.
+ *
+ * We will eventually need to crunch the file down, but if several files
+ * are being removed (perhaps as part of an "update" process) we can make
+ * things considerably faster by deferring the removal to "flush" time.
+ */
+status_t ZipFile::remove(ZipEntry* pEntry)
+{
+ /*
+ * Should verify that pEntry is actually part of this archive, and
+ * not some stray ZipEntry from a different file.
+ */
+
+ /* mark entry as deleted, and mark archive as dirty */
+ pEntry->setDeleted();
+ mNeedCDRewrite = true;
+ return NO_ERROR;
+}
+
+/*
+ * Flush any pending writes.
+ *
+ * In particular, this will crunch out deleted entries, and write the
+ * Central Directory and EOCD if we have stomped on them.
+ */
+status_t ZipFile::flush(void)
+{
+ status_t result = NO_ERROR;
+ long eocdPosn;
+ int i, count;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+ if (!mNeedCDRewrite)
+ return NO_ERROR;
+
+ assert(mZipFp != NULL);
+
+ result = crunchArchive();
+ if (result != NO_ERROR)
+ return result;
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0)
+ return UNKNOWN_ERROR;
+
+ count = mEntries.size();
+ for (i = 0; i < count; i++) {
+ ZipEntry* pEntry = mEntries[i];
+ pEntry->mCDE.write(mZipFp);
+ }
+
+ eocdPosn = ftell(mZipFp);
+ mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset;
+
+ mEOCD.write(mZipFp);
+
+ /*
+ * If we had some stuff bloat up during compression and get replaced
+ * with plain files, or if we deleted some entries, there's a lot
+ * of wasted space at the end of the file. Remove it now.
+ */
+ if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) {
+ ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno));
+ // not fatal
+ }
+
+ /* should we clear the "newly added" flag in all entries now? */
+
+ mNeedCDRewrite = false;
+ return NO_ERROR;
+}
+
+/*
+ * Crunch deleted files out of an archive by shifting the later files down.
+ *
+ * Because we're not using a temp file, we do the operation inside the
+ * current file.
+ */
+status_t ZipFile::crunchArchive(void)
+{
+ status_t result = NO_ERROR;
+ int i, count;
+ long delCount, adjust;
+
+#if 0
+ printf("CONTENTS:\n");
+ for (i = 0; i < (int) mEntries.size(); i++) {
+ printf(" %d: lfhOff=%ld del=%d\n",
+ i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted());
+ }
+ printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset);
+#endif
+
+ /*
+ * Roll through the set of files, shifting them as appropriate. We
+ * could probably get a slight performance improvement by sliding
+ * multiple files down at once (because we could use larger reads
+ * when operating on batches of small files), but it's not that useful.
+ */
+ count = mEntries.size();
+ delCount = adjust = 0;
+ for (i = 0; i < count; i++) {
+ ZipEntry* pEntry = mEntries[i];
+ long span;
+
+ if (pEntry->getLFHOffset() != 0) {
+ long nextOffset;
+
+ /* Get the length of this entry by finding the offset
+ * of the next entry. Directory entries don't have
+ * file offsets, so we need to find the next non-directory
+ * entry.
+ */
+ nextOffset = 0;
+ for (int ii = i+1; nextOffset == 0 && ii < count; ii++)
+ nextOffset = mEntries[ii]->getLFHOffset();
+ if (nextOffset == 0)
+ nextOffset = mEOCD.mCentralDirOffset;
+ span = nextOffset - pEntry->getLFHOffset();
+
+ assert(span >= ZipEntry::LocalFileHeader::kLFHLen);
+ } else {
+ /* This is a directory entry. It doesn't have
+ * any actual file contents, so there's no need to
+ * move anything.
+ */
+ span = 0;
+ }
+
+ //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n",
+ // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count);
+
+ if (pEntry->getDeleted()) {
+ adjust += span;
+ delCount++;
+
+ delete pEntry;
+ mEntries.removeAt(i);
+
+ /* adjust loop control */
+ count--;
+ i--;
+ } else if (span != 0 && adjust > 0) {
+ /* shuffle this entry back */
+ //printf("+++ Shuffling '%s' back %ld\n",
+ // pEntry->getFileName(), adjust);
+ result = filemove(mZipFp, pEntry->getLFHOffset() - adjust,
+ pEntry->getLFHOffset(), span);
+ if (result != NO_ERROR) {
+ /* this is why you use a temp file */
+ ALOGE("error during crunch - archive is toast\n");
+ return result;
+ }
+
+ pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust);
+ }
+ }
+
+ /*
+ * Fix EOCD info. We have to wait until the end to do some of this
+ * because we use mCentralDirOffset to determine "span" for the
+ * last entry.
+ */
+ mEOCD.mCentralDirOffset -= adjust;
+ mEOCD.mNumEntries -= delCount;
+ mEOCD.mTotalNumEntries -= delCount;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+
+ assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries);
+ assert(mEOCD.mNumEntries == count);
+
+ return result;
+}
+
+/*
+ * Works like memmove(), but on pieces of a file.
+ */
+status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n)
+{
+ if (dst == src || n <= 0)
+ return NO_ERROR;
+
+ unsigned char readBuf[32768];
+
+ if (dst < src) {
+ /* shift stuff toward start of file; must read from start */
+ while (n != 0) {
+ size_t getSize = sizeof(readBuf);
+ if (getSize > n)
+ getSize = n;
+
+ if (fseek(fp, (long) src, SEEK_SET) != 0) {
+ ALOGD("filemove src seek %ld failed\n", (long) src);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fread(readBuf, 1, getSize, fp) != getSize) {
+ ALOGD("filemove read %ld off=%ld failed\n",
+ (long) getSize, (long) src);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fseek(fp, (long) dst, SEEK_SET) != 0) {
+ ALOGD("filemove dst seek %ld failed\n", (long) dst);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fwrite(readBuf, 1, getSize, fp) != getSize) {
+ ALOGD("filemove write %ld off=%ld failed\n",
+ (long) getSize, (long) dst);
+ return UNKNOWN_ERROR;
+ }
+
+ src += getSize;
+ dst += getSize;
+ n -= getSize;
+ }
+ } else {
+ /* shift stuff toward end of file; must read from end */
+ assert(false); // write this someday, maybe
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
+/*
+ * Get the modification time from a file descriptor.
+ */
+time_t ZipFile::getModTime(int fd)
+{
+ struct stat sb;
+
+ if (fstat(fd, &sb) < 0) {
+ ALOGD("HEY: fstat on fd %d failed\n", fd);
+ return (time_t) -1;
+ }
+
+ return sb.st_mtime;
+}
+
+
+#if 0 /* this is a bad idea */
+/*
+ * Get a copy of the Zip file descriptor.
+ *
+ * We don't allow this if the file was opened read-write because we tend
+ * to leave the file contents in an uncertain state between calls to
+ * flush(). The duplicated file descriptor should only be valid for reads.
+ */
+int ZipFile::getZipFd(void) const
+{
+ if (!mReadOnly)
+ return INVALID_OPERATION;
+ assert(mZipFp != NULL);
+
+ int fd;
+ fd = dup(fileno(mZipFp));
+ if (fd < 0) {
+ ALOGD("didn't work, errno=%d\n", errno);
+ }
+
+ return fd;
+}
+#endif
+
+
+#if 0
+/*
+ * Expand data.
+ */
+bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const
+{
+ return false;
+}
+#endif
+
+// free the memory when you're done
+void* ZipFile::uncompress(const ZipEntry* entry)
+{
+ size_t unlen = entry->getUncompressedLen();
+ size_t clen = entry->getCompressedLen();
+
+ void* buf = malloc(unlen);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ fseek(mZipFp, 0, SEEK_SET);
+
+ off_t offset = entry->getFileOffset();
+ if (fseek(mZipFp, offset, SEEK_SET) != 0) {
+ goto bail;
+ }
+
+ switch (entry->getCompressionMethod())
+ {
+ case ZipEntry::kCompressStored: {
+ ssize_t amt = fread(buf, 1, unlen, mZipFp);
+ if (amt != (ssize_t)unlen) {
+ goto bail;
+ }
+#if 0
+ printf("data...\n");
+ const unsigned char* p = (unsigned char*)buf;
+ const unsigned char* end = p+unlen;
+ for (int i=0; i<32 && p < end; i++) {
+ printf("0x%08x ", (int)(offset+(i*0x10)));
+ for (int j=0; j<0x10 && p < end; j++) {
+ printf(" %02x", *p);
+ p++;
+ }
+ printf("\n");
+ }
+#endif
+
+ }
+ break;
+ case ZipEntry::kCompressDeflated: {
+ if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) {
+ goto bail;
+ }
+ }
+ break;
+ default:
+ goto bail;
+ }
+ return buf;
+
+bail:
+ free(buf);
+ return NULL;
+}
+
+
+/*
+ * ===========================================================================
+ * ZipFile::EndOfCentralDir
+ * ===========================================================================
+ */
+
+/*
+ * Read the end-of-central-dir fields.
+ *
+ * "buf" should be positioned at the EOCD signature, and should contain
+ * the entire EOCD area including the comment.
+ */
+status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len)
+{
+ /* don't allow re-use */
+ assert(mComment == NULL);
+
+ if (len < kEOCDLen) {
+ /* looks like ZIP file got truncated */
+ ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n",
+ kEOCDLen, len);
+ return INVALID_OPERATION;
+ }
+
+ /* this should probably be an assert() */
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
+ return UNKNOWN_ERROR;
+
+ mDiskNumber = ZipEntry::getShortLE(&buf[0x04]);
+ mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
+ mNumEntries = ZipEntry::getShortLE(&buf[0x08]);
+ mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
+ mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]);
+ mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
+ mCommentLen = ZipEntry::getShortLE(&buf[0x14]);
+
+ // TODO: validate mCentralDirOffset
+
+ if (mCommentLen > 0) {
+ if (kEOCDLen + mCommentLen > len) {
+ ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n",
+ kEOCDLen, mCommentLen, len);
+ return UNKNOWN_ERROR;
+ }
+ mComment = new unsigned char[mCommentLen];
+ memcpy(mComment, buf + kEOCDLen, mCommentLen);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Write an end-of-central-directory section.
+ */
+status_t ZipFile::EndOfCentralDir::write(FILE* fp)
+{
+ unsigned char buf[kEOCDLen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mDiskNumber);
+ ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir);
+ ZipEntry::putShortLE(&buf[0x08], mNumEntries);
+ ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries);
+ ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize);
+ ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset);
+ ZipEntry::putShortLE(&buf[0x14], mCommentLen);
+
+ if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen)
+ return UNKNOWN_ERROR;
+ if (mCommentLen > 0) {
+ assert(mComment != NULL);
+ if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Dump the contents of an EndOfCentralDir object.
+ */
+void ZipFile::EndOfCentralDir::dump(void) const
+{
+ ALOGD(" EndOfCentralDir contents:\n");
+ ALOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n",
+ mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries);
+ ALOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n",
+ mCentralDirSize, mCentralDirOffset, mCommentLen);
+}
+
diff --git a/tools/aapt/ZipFile.h b/tools/aapt/ZipFile.h
new file mode 100644
index 0000000..7877550
--- /dev/null
+++ b/tools/aapt/ZipFile.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// General-purpose Zip archive access. This class allows both reading and
+// writing to Zip archives, including deletion of existing entries.
+//
+#ifndef __LIBS_ZIPFILE_H
+#define __LIBS_ZIPFILE_H
+
+#include <utils/Vector.h>
+#include <utils/Errors.h>
+#include <stdio.h>
+
+#include "ZipEntry.h"
+
+namespace android {
+
+/*
+ * Manipulate a Zip archive.
+ *
+ * Some changes will not be visible in the until until "flush" is called.
+ *
+ * The correct way to update a file archive is to make all changes to a
+ * copy of the archive in a temporary file, and then unlink/rename over
+ * the original after everything completes. Because we're only interested
+ * in using this for packaging, we don't worry about such things. Crashing
+ * after making changes and before flush() completes could leave us with
+ * an unusable Zip archive.
+ */
+class ZipFile {
+public:
+ ZipFile(void)
+ : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
+ {}
+ ~ZipFile(void) {
+ if (!mReadOnly)
+ flush();
+ if (mZipFp != NULL)
+ fclose(mZipFp);
+ discardEntries();
+ }
+
+ /*
+ * Open a new or existing archive.
+ */
+ enum {
+ kOpenReadOnly = 0x01,
+ kOpenReadWrite = 0x02,
+ kOpenCreate = 0x04, // create if it doesn't exist
+ kOpenTruncate = 0x08, // if it exists, empty it
+ };
+ status_t open(const char* zipFileName, int flags);
+
+ /*
+ * Add a file to the end of the archive. Specify whether you want the
+ * library to try to store it compressed.
+ *
+ * If "storageName" is specified, the archive will use that instead
+ * of "fileName".
+ *
+ * If there is already an entry with the same name, the call fails.
+ * Existing entries with the same name must be removed first.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const char* fileName, int compressionMethod,
+ ZipEntry** ppEntry)
+ {
+ return add(fileName, fileName, compressionMethod, ppEntry);
+ }
+ status_t add(const char* fileName, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ /*
+ * Add a file that is already compressed with gzip.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t addGzip(const char* fileName, const char* storageName,
+ ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressDeflated,
+ ZipEntry::kCompressDeflated, ppEntry);
+ }
+
+ /*
+ * Add a file from an in-memory data buffer.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const void* data, size_t size, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(NULL, data, size, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ /*
+ * Add an entry by copying it from another zip file. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ int padding, ZipEntry** ppEntry);
+
+ /*
+ * Mark an entry as having been removed. It is not actually deleted
+ * from the archive or our internal data structures until flush() is
+ * called.
+ */
+ status_t remove(ZipEntry* pEntry);
+
+ /*
+ * Flush changes. If mNeedCDRewrite is set, this writes the central dir.
+ */
+ status_t flush(void);
+
+ /*
+ * Expand the data into the buffer provided. The buffer must hold
+ * at least <uncompressed len> bytes. Variation expands directly
+ * to a file.
+ *
+ * Returns "false" if an error was encountered in the compressed data.
+ */
+ //bool uncompress(const ZipEntry* pEntry, void* buf) const;
+ //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
+ void* uncompress(const ZipEntry* pEntry);
+
+ /*
+ * Get an entry, by name. Returns NULL if not found.
+ *
+ * Does not return entries pending deletion.
+ */
+ ZipEntry* getEntryByName(const char* fileName) const;
+
+ /*
+ * Get the Nth entry in the archive.
+ *
+ * This will return an entry that is pending deletion.
+ */
+ int getNumEntries(void) const { return mEntries.size(); }
+ ZipEntry* getEntryByIndex(int idx) const;
+
+private:
+ /* these are private and not defined */
+ ZipFile(const ZipFile& src);
+ ZipFile& operator=(const ZipFile& src);
+
+ class EndOfCentralDir {
+ public:
+ EndOfCentralDir(void) :
+ mDiskNumber(0),
+ mDiskWithCentralDir(0),
+ mNumEntries(0),
+ mTotalNumEntries(0),
+ mCentralDirSize(0),
+ mCentralDirOffset(0),
+ mCommentLen(0),
+ mComment(NULL)
+ {}
+ virtual ~EndOfCentralDir(void) {
+ delete[] mComment;
+ }
+
+ status_t readBuf(const unsigned char* buf, int len);
+ status_t write(FILE* fp);
+
+ //unsigned long mSignature;
+ unsigned short mDiskNumber;
+ unsigned short mDiskWithCentralDir;
+ unsigned short mNumEntries;
+ unsigned short mTotalNumEntries;
+ unsigned long mCentralDirSize;
+ unsigned long mCentralDirOffset; // offset from first disk
+ unsigned short mCommentLen;
+ unsigned char* mComment;
+
+ enum {
+ kSignature = 0x06054b50,
+ kEOCDLen = 22, // EndOfCentralDir len, excl. comment
+
+ kMaxCommentLen = 65535, // longest possible in ushort
+ kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
+
+ };
+
+ void dump(void) const;
+ };
+
+
+ /* read all entries in the central dir */
+ status_t readCentralDir(void);
+
+ /* crunch deleted entries out */
+ status_t crunchArchive(void);
+
+ /* clean up mEntries */
+ void discardEntries(void);
+
+ /* common handler for all "add" functions */
+ status_t addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry);
+
+ /* copy all of "srcFp" into "dstFp" */
+ status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
+ /* copy all of "data" into "dstFp" */
+ status_t copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+ /* copy some of "srcFp" into "dstFp" */
+ status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32);
+ /* like memmove(), but on parts of a single file */
+ status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
+ /* compress all of "srcFp" into "dstFp", using Deflate */
+ status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+
+ /* get modification date from a file descriptor */
+ time_t getModTime(int fd);
+
+ /*
+ * We use stdio FILE*, which gives us buffering but makes dealing
+ * with files >2GB awkward. Until we support Zip64, we're fine.
+ */
+ FILE* mZipFp; // Zip file pointer
+
+ /* one of these per file */
+ EndOfCentralDir mEOCD;
+
+ /* did we open this read-only? */
+ bool mReadOnly;
+
+ /* set this when we trash the central dir */
+ bool mNeedCDRewrite;
+
+ /*
+ * One ZipEntry per entry in the zip file. I'm using pointers instead
+ * of objects because it's easier than making operator= work for the
+ * classes and sub-classes.
+ */
+ Vector<ZipEntry*> mEntries;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPFILE_H
diff --git a/tools/aapt/printapk.cpp b/tools/aapt/printapk.cpp
new file mode 100644
index 0000000..4cf73d8
--- /dev/null
+++ b/tools/aapt/printapk.cpp
@@ -0,0 +1,127 @@
+#include <utils/ResourceTypes.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include <zipfile/zipfile.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+using namespace android;
+
+static int
+usage()
+{
+ fprintf(stderr,
+ "usage: apk APKFILE\n"
+ "\n"
+ "APKFILE an android packge file produced by aapt.\n"
+ );
+ return 1;
+}
+
+
+int
+main(int argc, char** argv)
+{
+ const char* filename;
+ int fd;
+ ssize_t amt;
+ off_t size;
+ void* buf;
+ zipfile_t zip;
+ zipentry_t entry;
+ void* cookie;
+ void* resfile;
+ int bufsize;
+ int err;
+
+ if (argc != 2) {
+ return usage();
+ }
+
+ filename = argv[1];
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ fprintf(stderr, "apk: couldn't open file for read: %s\n", filename);
+ return 1;
+ }
+
+ size = lseek(fd, 0, SEEK_END);
+ amt = lseek(fd, 0, SEEK_SET);
+
+ if (size < 0 || amt < 0) {
+ fprintf(stderr, "apk: error determining file size: %s\n", filename);
+ return 1;
+ }
+
+ buf = malloc(size);
+ if (buf == NULL) {
+ fprintf(stderr, "apk: file too big: %s\n", filename);
+ return 1;
+ }
+
+ amt = read(fd, buf, size);
+ if (amt != size) {
+ fprintf(stderr, "apk: error reading file: %s\n", filename);
+ return 1;
+ }
+
+ close(fd);
+
+ zip = init_zipfile(buf, size);
+ if (zip == NULL) {
+ fprintf(stderr, "apk: file doesn't seem to be a zip file: %s\n",
+ filename);
+ return 1;
+ }
+
+ printf("files:\n");
+ cookie = NULL;
+ while ((entry = iterate_zipfile(zip, &cookie))) {
+ char* name = get_zipentry_name(entry);
+ printf(" %s\n", name);
+ free(name);
+ }
+
+ entry = lookup_zipentry(zip, "resources.arsc");
+ if (entry != NULL) {
+ size = get_zipentry_size(entry);
+ bufsize = size + (size / 1000) + 1;
+ resfile = malloc(bufsize);
+
+ err = decompress_zipentry(entry, resfile, bufsize);
+ if (err != 0) {
+ fprintf(stderr, "apk: error decompressing resources.arsc");
+ return 1;
+ }
+
+ ResTable res(resfile, size, resfile);
+ res.print();
+#if 0
+ size_t tableCount = res.getTableCount();
+ printf("Tables: %d\n", (int)tableCount);
+ for (size_t tableIndex=0; tableIndex<tableCount; tableIndex++) {
+ const ResStringPool* strings = res.getTableStringBlock(tableIndex);
+ size_t stringCount = strings->size();
+ for (size_t stringIndex=0; stringIndex<stringCount; stringIndex++) {
+ size_t len;
+ const char16_t* ch = strings->stringAt(stringIndex, &len);
+ String8 s(String16(ch, len));
+ printf(" [%3d] %s\n", (int)stringIndex, s.string());
+ }
+ }
+
+ size_t basePackageCount = res.getBasePackageCount();
+ printf("Base Packages: %d\n", (int)basePackageCount);
+ for (size_t bpIndex=0; bpIndex<basePackageCount; bpIndex++) {
+ const char16_t* ch = res.getBasePackageName(bpIndex);
+ String8 s = String8(String16(ch));
+ printf(" [%3d] %s\n", (int)bpIndex, s.string());
+ }
+#endif
+ }
+
+
+ return 0;
+}
diff --git a/tools/aapt/pseudolocalize.cpp b/tools/aapt/pseudolocalize.cpp
new file mode 100644
index 0000000..9e50c5a
--- /dev/null
+++ b/tools/aapt/pseudolocalize.cpp
@@ -0,0 +1,119 @@
+#include "pseudolocalize.h"
+
+using namespace std;
+
+static const char*
+pseudolocalize_char(char c)
+{
+ switch (c) {
+ case 'a': return "\xc4\x83";
+ case 'b': return "\xcf\x84";
+ case 'c': return "\xc4\x8b";
+ case 'd': return "\xc4\x8f";
+ case 'e': return "\xc4\x99";
+ case 'f': return "\xc6\x92";
+ case 'g': return "\xc4\x9d";
+ case 'h': return "\xd1\x9b";
+ case 'i': return "\xcf\x8a";
+ case 'j': return "\xc4\xb5";
+ case 'k': return "\xc4\xb8";
+ case 'l': return "\xc4\xba";
+ case 'm': return "\xe1\xb8\xbf";
+ case 'n': return "\xd0\xb8";
+ case 'o': return "\xcf\x8c";
+ case 'p': return "\xcf\x81";
+ case 'q': return "\x51";
+ case 'r': return "\xd2\x91";
+ case 's': return "\xc5\xa1";
+ case 't': return "\xd1\x82";
+ case 'u': return "\xce\xb0";
+ case 'v': return "\x56";
+ case 'w': return "\xe1\xba\x85";
+ case 'x': return "\xd1\x85";
+ case 'y': return "\xe1\xbb\xb3";
+ case 'z': return "\xc5\xba";
+ case 'A': return "\xc3\x85";
+ case 'B': return "\xce\xb2";
+ case 'C': return "\xc4\x88";
+ case 'D': return "\xc4\x90";
+ case 'E': return "\xd0\x84";
+ case 'F': return "\xce\x93";
+ case 'G': return "\xc4\x9e";
+ case 'H': return "\xc4\xa6";
+ case 'I': return "\xd0\x87";
+ case 'J': return "\xc4\xb5";
+ case 'K': return "\xc4\xb6";
+ case 'L': return "\xc5\x81";
+ case 'M': return "\xe1\xb8\xbe";
+ case 'N': return "\xc5\x83";
+ case 'O': return "\xce\x98";
+ case 'P': return "\xcf\x81";
+ case 'Q': return "\x71";
+ case 'R': return "\xd0\xaf";
+ case 'S': return "\xc8\x98";
+ case 'T': return "\xc5\xa6";
+ case 'U': return "\xc5\xa8";
+ case 'V': return "\xce\xbd";
+ case 'W': return "\xe1\xba\x84";
+ case 'X': return "\xc3\x97";
+ case 'Y': return "\xc2\xa5";
+ case 'Z': return "\xc5\xbd";
+ default: return NULL;
+ }
+}
+
+/**
+ * Converts characters so they look like they've been localized.
+ *
+ * Note: This leaves escape sequences untouched so they can later be
+ * processed by ResTable::collectString in the normal way.
+ */
+string
+pseudolocalize_string(const string& source)
+{
+ const char* s = source.c_str();
+ string result;
+ const size_t I = source.length();
+ for (size_t i=0; i<I; i++) {
+ char c = s[i];
+ if (c == '\\') {
+ if (i<I-1) {
+ result += '\\';
+ i++;
+ c = s[i];
+ switch (c) {
+ case 'u':
+ // this one takes up 5 chars
+ result += string(s+i, 5);
+ i += 4;
+ break;
+ case 't':
+ case 'n':
+ case '#':
+ case '@':
+ case '?':
+ case '"':
+ case '\'':
+ case '\\':
+ default:
+ result += c;
+ break;
+ }
+ } else {
+ result += c;
+ }
+ } else {
+ const char* p = pseudolocalize_char(c);
+ if (p != NULL) {
+ result += p;
+ } else {
+ result += c;
+ }
+ }
+ }
+
+ //printf("result=\'%s\'\n", result.c_str());
+ return result;
+}
+
+
diff --git a/tools/aapt/pseudolocalize.h b/tools/aapt/pseudolocalize.h
new file mode 100644
index 0000000..94cb034
--- /dev/null
+++ b/tools/aapt/pseudolocalize.h
@@ -0,0 +1,9 @@
+#ifndef HOST_PSEUDOLOCALIZE_H
+#define HOST_PSEUDOLOCALIZE_H
+
+#include <string>
+
+std::string pseudolocalize_string(const std::string& source);
+
+#endif // HOST_PSEUDOLOCALIZE_H
+
diff --git a/tools/aapt/qsort_r_compat.c b/tools/aapt/qsort_r_compat.c
new file mode 100644
index 0000000..2a8dbe8
--- /dev/null
+++ b/tools/aapt/qsort_r_compat.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <stdlib.h>
+#include "qsort_r_compat.h"
+
+/*
+ * Note: This code is only used on the host, and is primarily here for
+ * Mac OS compatibility. Apparently, glibc and Apple's libc disagree on
+ * the parameter order for qsort_r.
+ */
+
+#if HAVE_BSD_QSORT_R
+
+/*
+ * BSD qsort_r parameter order is as we have defined here.
+ */
+
+void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk,
+ int (*compar)(void*, const void* , const void*)) {
+ qsort_r(base, nel, width, thunk, compar);
+}
+
+#elif HAVE_GNU_QSORT_R
+
+/*
+ * GNU qsort_r parameter order places the thunk parameter last.
+ */
+
+struct compar_data {
+ void* thunk;
+ int (*compar)(void*, const void* , const void*);
+};
+
+static int compar_wrapper(const void* a, const void* b, void* data) {
+ struct compar_data* compar_data = (struct compar_data*)data;
+ return compar_data->compar(compar_data->thunk, a, b);
+}
+
+void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk,
+ int (*compar)(void*, const void* , const void*)) {
+ struct compar_data compar_data;
+ compar_data.thunk = thunk;
+ compar_data.compar = compar;
+ qsort_r(base, nel, width, compar_wrapper, &compar_data);
+}
+
+#else
+
+/*
+ * Emulate qsort_r using thread local storage to access the thunk data.
+ */
+
+#include <cutils/threads.h>
+
+static thread_store_t compar_data_key = THREAD_STORE_INITIALIZER;
+
+struct compar_data {
+ void* thunk;
+ int (*compar)(void*, const void* , const void*);
+};
+
+static int compar_wrapper(const void* a, const void* b) {
+ struct compar_data* compar_data = (struct compar_data*)thread_store_get(&compar_data_key);
+ return compar_data->compar(compar_data->thunk, a, b);
+}
+
+void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk,
+ int (*compar)(void*, const void* , const void*)) {
+ struct compar_data compar_data;
+ compar_data.thunk = thunk;
+ compar_data.compar = compar;
+ thread_store_set(&compar_data_key, &compar_data, NULL);
+ qsort(base, nel, width, compar_wrapper);
+}
+
+#endif
diff --git a/tools/aapt/qsort_r_compat.h b/tools/aapt/qsort_r_compat.h
new file mode 100644
index 0000000..e14f999
--- /dev/null
+++ b/tools/aapt/qsort_r_compat.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * Provides a portable version of qsort_r, called qsort_r_compat, which is a
+ * reentrant variant of qsort that passes a user data pointer to its comparator.
+ * This implementation follows the BSD parameter convention.
+ */
+
+#ifndef ___QSORT_R_COMPAT_H
+#define ___QSORT_R_COMPAT_H
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void qsort_r_compat(void* base, size_t nel, size_t width, void* thunk,
+ int (*compar)(void*, const void* , const void* ));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ___QSORT_R_COMPAT_H
diff --git a/tools/aapt/tests/CrunchCache_test.cpp b/tools/aapt/tests/CrunchCache_test.cpp
new file mode 100644
index 0000000..20b5022
--- /dev/null
+++ b/tools/aapt/tests/CrunchCache_test.cpp
@@ -0,0 +1,97 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+#include <utils/String8.h>
+#include <iostream>
+#include <errno.h>
+
+#include "CrunchCache.h"
+#include "FileFinder.h"
+#include "MockFileFinder.h"
+#include "CacheUpdater.h"
+#include "MockCacheUpdater.h"
+
+using namespace android;
+using std::cout;
+using std::endl;
+
+void expectEqual(int got, int expected, const char* desc) {
+ cout << "Checking " << desc << ": ";
+ cout << "Got " << got << ", expected " << expected << "...";
+ cout << ( (got == expected) ? "PASSED" : "FAILED") << endl;
+ errno += ((got == expected) ? 0 : 1);
+}
+
+int main() {
+
+ errno = 0;
+
+ String8 source("res");
+ String8 dest("res2");
+
+ // Create data for MockFileFinder to feed to the cache
+ KeyedVector<String8, time_t> sourceData;
+ // This shouldn't be updated
+ sourceData.add(String8("res/drawable/hello.png"),3);
+ // This should be updated
+ sourceData.add(String8("res/drawable/world.png"),5);
+ // This should cause make directory to be called
+ sourceData.add(String8("res/drawable-cool/hello.png"),3);
+
+ KeyedVector<String8, time_t> destData;
+ destData.add(String8("res2/drawable/hello.png"),3);
+ destData.add(String8("res2/drawable/world.png"),3);
+ // this should call delete
+ destData.add(String8("res2/drawable/dead.png"),3);
+
+ // Package up data and create mock file finder
+ KeyedVector<String8, KeyedVector<String8,time_t> > data;
+ data.add(source,sourceData);
+ data.add(dest,destData);
+ FileFinder* ff = new MockFileFinder(data);
+ CrunchCache cc(source,dest,ff);
+
+ MockCacheUpdater* mcu = new MockCacheUpdater();
+ CacheUpdater* cu(mcu);
+
+ cout << "Running Crunch...";
+ int result = cc.crunch(cu);
+ cout << ((result > 0) ? "PASSED" : "FAILED") << endl;
+ errno += ((result > 0) ? 0 : 1);
+
+ const int EXPECTED_RESULT = 2;
+ expectEqual(result, EXPECTED_RESULT, "number of files touched");
+
+ cout << "Checking calls to deleteFile and processImage:" << endl;
+ const int EXPECTED_DELETES = 1;
+ const int EXPECTED_PROCESSED = 2;
+ // Deletes
+ expectEqual(mcu->deleteCount, EXPECTED_DELETES, "deleteFile");
+ // processImage
+ expectEqual(mcu->processCount, EXPECTED_PROCESSED, "processImage");
+
+ const int EXPECTED_OVERWRITES = 3;
+ result = cc.crunch(cu, true);
+ expectEqual(result, EXPECTED_OVERWRITES, "number of files touched with overwrite");
+ \
+
+ if (errno == 0)
+ cout << "ALL TESTS PASSED!" << endl;
+ else
+ cout << errno << " TESTS FAILED" << endl;
+
+ delete ff;
+ delete cu;
+
+ // TESTS BELOW WILL GO AWAY SOON
+
+ String8 source2("ApiDemos/res");
+ String8 dest2("ApiDemos/res2");
+
+ FileFinder* sff = new SystemFileFinder();
+ CacheUpdater* scu = new SystemCacheUpdater();
+
+ CrunchCache scc(source2,dest2,sff);
+
+ scc.crunch(scu);
+}
\ No newline at end of file
diff --git a/tools/aapt/tests/FileFinder_test.cpp b/tools/aapt/tests/FileFinder_test.cpp
new file mode 100644
index 0000000..07bd665
--- /dev/null
+++ b/tools/aapt/tests/FileFinder_test.cpp
@@ -0,0 +1,101 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <iostream>
+#include <cassert>
+#include <utils/String8.h>
+#include <utility>
+
+#include "DirectoryWalker.h"
+#include "MockDirectoryWalker.h"
+#include "FileFinder.h"
+
+using namespace android;
+
+using std::pair;
+using std::cout;
+using std::endl;
+
+
+
+int main()
+{
+
+ cout << "\n\n STARTING FILE FINDER TESTS" << endl;
+ String8 path("ApiDemos");
+
+ // Storage to pass to findFiles()
+ KeyedVector<String8,time_t> testStorage;
+
+ // Mock Directory Walker initialization. First data, then sdw
+ Vector< pair<String8,time_t> > data;
+ data.push( pair<String8,time_t>(String8("hello.png"),3) );
+ data.push( pair<String8,time_t>(String8("world.PNG"),3) );
+ data.push( pair<String8,time_t>(String8("foo.pNg"),3) );
+ // Neither of these should be found
+ data.push( pair<String8,time_t>(String8("hello.jpg"),3) );
+ data.push( pair<String8,time_t>(String8(".hidden.png"),3));
+
+ DirectoryWalker* sdw = new StringDirectoryWalker(path,data);
+
+ // Extensions to look for
+ Vector<String8> exts;
+ exts.push(String8(".png"));
+
+ errno = 0;
+
+ // Make sure we get a valid mock directory walker
+ // Make sure we finish without errors
+ cout << "Checking DirectoryWalker...";
+ assert(sdw != NULL);
+ cout << "PASSED" << endl;
+
+ // Make sure we finish without errors
+ cout << "Running findFiles()...";
+ bool findStatus = FileFinder::findFiles(path,exts, testStorage, sdw);
+ assert(findStatus);
+ cout << "PASSED" << endl;
+
+ const size_t SIZE_EXPECTED = 3;
+ // Check to make sure we have the right number of things in our storage
+ cout << "Running size comparison: Size is " << testStorage.size() << ", ";
+ cout << "Expected " << SIZE_EXPECTED << "...";
+ if(testStorage.size() == SIZE_EXPECTED)
+ cout << "PASSED" << endl;
+ else {
+ cout << "FAILED" << endl;
+ errno++;
+ }
+
+ // Check to make sure that each of our found items has the right extension
+ cout << "Checking Returned Extensions...";
+ bool extsOkay = true;
+ String8 wrongExts;
+ for (size_t i = 0; i < SIZE_EXPECTED; ++i) {
+ String8 testExt(testStorage.keyAt(i).getPathExtension());
+ testExt.toLower();
+ if (testExt != ".png") {
+ wrongExts += testStorage.keyAt(i);
+ wrongExts += "\n";
+ extsOkay = false;
+ }
+ }
+ if (extsOkay)
+ cout << "PASSED" << endl;
+ else {
+ cout << "FAILED" << endl;
+ cout << "The following extensions didn't check out" << endl << wrongExts;
+ }
+
+ // Clean up
+ delete sdw;
+
+ if(errno == 0) {
+ cout << "ALL TESTS PASSED" << endl;
+ } else {
+ cout << errno << " TESTS FAILED" << endl;
+ }
+ return errno;
+}
\ No newline at end of file
diff --git a/tools/aapt/tests/MockCacheUpdater.h b/tools/aapt/tests/MockCacheUpdater.h
new file mode 100644
index 0000000..c7f4bd7
--- /dev/null
+++ b/tools/aapt/tests/MockCacheUpdater.h
@@ -0,0 +1,40 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+#ifndef MOCKCACHEUPDATER_H
+#define MOCKCACHEUPDATER_H
+
+#include <utils/String8.h>
+#include "CacheUpdater.h"
+
+using namespace android;
+
+class MockCacheUpdater : public CacheUpdater {
+public:
+
+ MockCacheUpdater()
+ : deleteCount(0), processCount(0) { };
+
+ // Make sure all the directories along this path exist
+ virtual void ensureDirectoriesExist(String8 path)
+ {
+ // Nothing to do
+ };
+
+ // Delete a file
+ virtual void deleteFile(String8 path) {
+ deleteCount++;
+ };
+
+ // Process an image from source out to dest
+ virtual void processImage(String8 source, String8 dest) {
+ processCount++;
+ };
+
+ // DATA MEMBERS
+ int deleteCount;
+ int processCount;
+private:
+};
+
+#endif // MOCKCACHEUPDATER_H
\ No newline at end of file
diff --git a/tools/aapt/tests/MockDirectoryWalker.h b/tools/aapt/tests/MockDirectoryWalker.h
new file mode 100644
index 0000000..5900cf3
--- /dev/null
+++ b/tools/aapt/tests/MockDirectoryWalker.h
@@ -0,0 +1,85 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+#ifndef MOCKDIRECTORYWALKER_H
+#define MOCKDIRECTORYWALKER_H
+
+#include <utils/Vector.h>
+#include <utils/String8.h>
+#include <utility>
+#include "DirectoryWalker.h"
+
+using namespace android;
+using std::pair;
+
+// String8 Directory Walker
+// This is an implementation of the Directory Walker abstraction that is built
+// for testing.
+// Instead of system calls it queries a private data structure for the directory
+// entries. It takes a path and a map of filenames and their modification times.
+// functions are inlined since they are short and simple
+
+class StringDirectoryWalker : public DirectoryWalker {
+public:
+ StringDirectoryWalker(String8& path, Vector< pair<String8,time_t> >& data)
+ : mPos(0), mBasePath(path), mData(data) {
+ //fprintf(stdout,"StringDW built to mimic %s with %d files\n",
+ // mBasePath.string());
+ };
+ // Default copy constructor, and destructor are fine
+
+ virtual bool openDir(String8 path) {
+ // If the user is trying to query the "directory" that this
+ // walker was initialized with, then return success. Else fail.
+ return path == mBasePath;
+ };
+ virtual bool openDir(const char* path) {
+ String8 p(path);
+ openDir(p);
+ return true;
+ };
+ // Advance to next entry in the Vector
+ virtual struct dirent* nextEntry() {
+ // Advance position and check to see if we're done
+ if (mPos >= mData.size())
+ return NULL;
+
+ // Place data in the entry descriptor. This class only returns files.
+ mEntry.d_type = DT_REG;
+ mEntry.d_ino = mPos;
+ // Copy chars from the string name to the entry name
+ size_t i = 0;
+ for (i; i < mData[mPos].first.size(); ++i)
+ mEntry.d_name[i] = mData[mPos].first[i];
+ mEntry.d_name[i] = '\0';
+
+ // Place data in stats
+ mStats.st_ino = mPos;
+ mStats.st_mtime = mData[mPos].second;
+
+ // Get ready to move to the next entry
+ mPos++;
+
+ return &mEntry;
+ };
+ // Get the stats for the current entry
+ virtual struct stat* entryStats() {
+ return &mStats;
+ };
+ // Nothing to do in clean up
+ virtual void closeDir() {
+ // Nothing to do
+ };
+ virtual DirectoryWalker* clone() {
+ return new StringDirectoryWalker(*this);
+ };
+private:
+ // Current position in the Vector
+ size_t mPos;
+ // Base path
+ String8 mBasePath;
+ // Data to simulate a directory full of files.
+ Vector< pair<String8,time_t> > mData;
+};
+
+#endif // MOCKDIRECTORYWALKER_H
\ No newline at end of file
diff --git a/tools/aapt/tests/MockFileFinder.h b/tools/aapt/tests/MockFileFinder.h
new file mode 100644
index 0000000..da5ea4f
--- /dev/null
+++ b/tools/aapt/tests/MockFileFinder.h
@@ -0,0 +1,55 @@
+//
+// Copyright 2011 The Android Open Source Project
+//
+
+#ifndef MOCKFILEFINDER_H
+#define MOCKFILEFINDER_H
+
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+
+#include "DirectoryWalker.h"
+
+using namespace android;
+
+class MockFileFinder : public FileFinder {
+public:
+ MockFileFinder (KeyedVector<String8, KeyedVector<String8,time_t> >& files)
+ : mFiles(files)
+ {
+ // Nothing left to do
+ };
+
+ /**
+ * findFiles implementation for the abstraction.
+ * PRECONDITIONS:
+ * No checking is done, so there MUST be an entry in mFiles with
+ * path matching basePath.
+ *
+ * POSTCONDITIONS:
+ * fileStore is filled with a copy of the data in mFiles corresponding
+ * to the basePath.
+ */
+
+ virtual bool findFiles(String8 basePath, Vector<String8>& extensions,
+ KeyedVector<String8,time_t>& fileStore,
+ DirectoryWalker* dw)
+ {
+ const KeyedVector<String8,time_t>* payload(&mFiles.valueFor(basePath));
+ // Since KeyedVector doesn't implement swap
+ // (who doesn't use swap??) we loop and add one at a time.
+ for (size_t i = 0; i < payload->size(); ++i) {
+ fileStore.add(payload->keyAt(i),payload->valueAt(i));
+ }
+ return true;
+ }
+
+private:
+ // Virtual mapping between "directories" and the "files" contained
+ // in them
+ KeyedVector<String8, KeyedVector<String8,time_t> > mFiles;
+};
+
+
+#endif // MOCKFILEFINDER_H
\ No newline at end of file
diff --git a/tools/aapt/tests/plurals/AndroidManifest.xml b/tools/aapt/tests/plurals/AndroidManifest.xml
new file mode 100644
index 0000000..c721dee
--- /dev/null
+++ b/tools/aapt/tests/plurals/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.aapt.test.plurals">
+
+</manifest>
diff --git a/tools/aapt/tests/plurals/res/values/strings.xml b/tools/aapt/tests/plurals/res/values/strings.xml
new file mode 100644
index 0000000..1c1fc19
--- /dev/null
+++ b/tools/aapt/tests/plurals/res/values/strings.xml
@@ -0,0 +1,7 @@
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="ok">OK</string>
+ <plurals name="a_plural">
+ <item quantity="one">A dog</item>
+ <item quantity="other">Some dogs</item>
+ </plurals>
+</resources>
diff --git a/tools/aapt/tests/plurals/run.sh b/tools/aapt/tests/plurals/run.sh
new file mode 100755
index 0000000..4d39e10
--- /dev/null
+++ b/tools/aapt/tests/plurals/run.sh
@@ -0,0 +1,16 @@
+TEST_DIR=tools/aapt/tests/plurals
+TEST_OUT_DIR=out/plurals_test
+
+rm -rf $TEST_OUT_DIR
+mkdir -p $TEST_OUT_DIR
+mkdir -p $TEST_OUT_DIR/java
+
+#gdb --args \
+aapt package -v -x -m -z -J $TEST_OUT_DIR/java -M $TEST_DIR/AndroidManifest.xml \
+ -I out/target/common/obj/APPS/framework-res_intermediates/package-export.apk \
+ -P $TEST_OUT_DIR/public_resources.xml \
+ -S $TEST_DIR/res
+
+echo
+echo "==================== FILES CREATED ==================== "
+find $TEST_OUT_DIR -type f
diff --git a/tools/aidl/AST.cpp b/tools/aidl/AST.cpp
new file mode 100644
index 0000000..bfa6765
--- /dev/null
+++ b/tools/aidl/AST.cpp
@@ -0,0 +1,912 @@
+#include "AST.h"
+#include "Type.h"
+
+void
+WriteModifiers(FILE* to, int mod, int mask)
+{
+ int m = mod & mask;
+
+ if (m & OVERRIDE) {
+ fprintf(to, "@Override ");
+ }
+
+ if ((m & SCOPE_MASK) == PUBLIC) {
+ fprintf(to, "public ");
+ }
+ else if ((m & SCOPE_MASK) == PRIVATE) {
+ fprintf(to, "private ");
+ }
+ else if ((m & SCOPE_MASK) == PROTECTED) {
+ fprintf(to, "protected ");
+ }
+
+ if (m & STATIC) {
+ fprintf(to, "static ");
+ }
+
+ if (m & FINAL) {
+ fprintf(to, "final ");
+ }
+
+ if (m & ABSTRACT) {
+ fprintf(to, "abstract ");
+ }
+}
+
+void
+WriteArgumentList(FILE* to, const vector<Expression*>& arguments)
+{
+ size_t N = arguments.size();
+ for (size_t i=0; i<N; i++) {
+ arguments[i]->Write(to);
+ if (i != N-1) {
+ fprintf(to, ", ");
+ }
+ }
+}
+
+ClassElement::ClassElement()
+{
+}
+
+ClassElement::~ClassElement()
+{
+}
+
+Field::Field()
+ :ClassElement(),
+ modifiers(0),
+ variable(NULL)
+{
+}
+
+Field::Field(int m, Variable* v)
+ :ClassElement(),
+ modifiers(m),
+ variable(v)
+{
+}
+
+Field::~Field()
+{
+}
+
+void
+Field::GatherTypes(set<Type*>* types) const
+{
+ types->insert(this->variable->type);
+}
+
+void
+Field::Write(FILE* to)
+{
+ if (this->comment.length() != 0) {
+ fprintf(to, "%s\n", this->comment.c_str());
+ }
+ WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | FINAL | OVERRIDE);
+ fprintf(to, "%s %s", this->variable->type->QualifiedName().c_str(),
+ this->variable->name.c_str());
+ if (this->value.length() != 0) {
+ fprintf(to, " = %s", this->value.c_str());
+ }
+ fprintf(to, ";\n");
+}
+
+Expression::~Expression()
+{
+}
+
+LiteralExpression::LiteralExpression(const string& v)
+ :value(v)
+{
+}
+
+LiteralExpression::~LiteralExpression()
+{
+}
+
+void
+LiteralExpression::Write(FILE* to)
+{
+ fprintf(to, "%s", this->value.c_str());
+}
+
+StringLiteralExpression::StringLiteralExpression(const string& v)
+ :value(v)
+{
+}
+
+StringLiteralExpression::~StringLiteralExpression()
+{
+}
+
+void
+StringLiteralExpression::Write(FILE* to)
+{
+ fprintf(to, "\"%s\"", this->value.c_str());
+}
+
+Variable::Variable()
+ :type(NULL),
+ name(),
+ dimension(0)
+{
+}
+
+Variable::Variable(Type* t, const string& n)
+ :type(t),
+ name(n),
+ dimension(0)
+{
+}
+
+Variable::Variable(Type* t, const string& n, int d)
+ :type(t),
+ name(n),
+ dimension(d)
+{
+}
+
+Variable::~Variable()
+{
+}
+
+void
+Variable::GatherTypes(set<Type*>* types) const
+{
+ types->insert(this->type);
+}
+
+void
+Variable::WriteDeclaration(FILE* to)
+{
+ string dim;
+ for (int i=0; i<this->dimension; i++) {
+ dim += "[]";
+ }
+ fprintf(to, "%s%s %s", this->type->QualifiedName().c_str(), dim.c_str(),
+ this->name.c_str());
+}
+
+void
+Variable::Write(FILE* to)
+{
+ fprintf(to, "%s", name.c_str());
+}
+
+FieldVariable::FieldVariable(Expression* o, const string& n)
+ :object(o),
+ clazz(NULL),
+ name(n)
+{
+}
+
+FieldVariable::FieldVariable(Type* c, const string& n)
+ :object(NULL),
+ clazz(c),
+ name(n)
+{
+}
+
+FieldVariable::~FieldVariable()
+{
+}
+
+void
+FieldVariable::Write(FILE* to)
+{
+ if (this->object != NULL) {
+ this->object->Write(to);
+ }
+ else if (this->clazz != NULL) {
+ fprintf(to, "%s", this->clazz->QualifiedName().c_str());
+ }
+ fprintf(to, ".%s", name.c_str());
+}
+
+
+Statement::~Statement()
+{
+}
+
+StatementBlock::StatementBlock()
+{
+}
+
+StatementBlock::~StatementBlock()
+{
+}
+
+void
+StatementBlock::Write(FILE* to)
+{
+ fprintf(to, "{\n");
+ int N = this->statements.size();
+ for (int i=0; i<N; i++) {
+ this->statements[i]->Write(to);
+ }
+ fprintf(to, "}\n");
+}
+
+void
+StatementBlock::Add(Statement* statement)
+{
+ this->statements.push_back(statement);
+}
+
+void
+StatementBlock::Add(Expression* expression)
+{
+ this->statements.push_back(new ExpressionStatement(expression));
+}
+
+ExpressionStatement::ExpressionStatement(Expression* e)
+ :expression(e)
+{
+}
+
+ExpressionStatement::~ExpressionStatement()
+{
+}
+
+void
+ExpressionStatement::Write(FILE* to)
+{
+ this->expression->Write(to);
+ fprintf(to, ";\n");
+}
+
+Assignment::Assignment(Variable* l, Expression* r)
+ :lvalue(l),
+ rvalue(r),
+ cast(NULL)
+{
+}
+
+Assignment::Assignment(Variable* l, Expression* r, Type* c)
+ :lvalue(l),
+ rvalue(r),
+ cast(c)
+{
+}
+
+Assignment::~Assignment()
+{
+}
+
+void
+Assignment::Write(FILE* to)
+{
+ this->lvalue->Write(to);
+ fprintf(to, " = ");
+ if (this->cast != NULL) {
+ fprintf(to, "(%s)", this->cast->QualifiedName().c_str());
+ }
+ this->rvalue->Write(to);
+}
+
+MethodCall::MethodCall(const string& n)
+ :obj(NULL),
+ clazz(NULL),
+ name(n)
+{
+}
+
+MethodCall::MethodCall(const string& n, int argc = 0, ...)
+ :obj(NULL),
+ clazz(NULL),
+ name(n)
+{
+ va_list args;
+ va_start(args, argc);
+ init(argc, args);
+ va_end(args);
+}
+
+MethodCall::MethodCall(Expression* o, const string& n)
+ :obj(o),
+ clazz(NULL),
+ name(n)
+{
+}
+
+MethodCall::MethodCall(Type* t, const string& n)
+ :obj(NULL),
+ clazz(t),
+ name(n)
+{
+}
+
+MethodCall::MethodCall(Expression* o, const string& n, int argc = 0, ...)
+ :obj(o),
+ clazz(NULL),
+ name(n)
+{
+ va_list args;
+ va_start(args, argc);
+ init(argc, args);
+ va_end(args);
+}
+
+MethodCall::MethodCall(Type* t, const string& n, int argc = 0, ...)
+ :obj(NULL),
+ clazz(t),
+ name(n)
+{
+ va_list args;
+ va_start(args, argc);
+ init(argc, args);
+ va_end(args);
+}
+
+MethodCall::~MethodCall()
+{
+}
+
+void
+MethodCall::init(int n, va_list args)
+{
+ for (int i=0; i<n; i++) {
+ Expression* expression = (Expression*)va_arg(args, void*);
+ this->arguments.push_back(expression);
+ }
+}
+
+void
+MethodCall::Write(FILE* to)
+{
+ if (this->obj != NULL) {
+ this->obj->Write(to);
+ fprintf(to, ".");
+ }
+ else if (this->clazz != NULL) {
+ fprintf(to, "%s.", this->clazz->QualifiedName().c_str());
+ }
+ fprintf(to, "%s(", this->name.c_str());
+ WriteArgumentList(to, this->arguments);
+ fprintf(to, ")");
+}
+
+Comparison::Comparison(Expression* l, const string& o, Expression* r)
+ :lvalue(l),
+ op(o),
+ rvalue(r)
+{
+}
+
+Comparison::~Comparison()
+{
+}
+
+void
+Comparison::Write(FILE* to)
+{
+ fprintf(to, "(");
+ this->lvalue->Write(to);
+ fprintf(to, "%s", this->op.c_str());
+ this->rvalue->Write(to);
+ fprintf(to, ")");
+}
+
+NewExpression::NewExpression(Type* t)
+ :type(t)
+{
+}
+
+NewExpression::NewExpression(Type* t, int argc = 0, ...)
+ :type(t)
+{
+ va_list args;
+ va_start(args, argc);
+ init(argc, args);
+ va_end(args);
+}
+
+NewExpression::~NewExpression()
+{
+}
+
+void
+NewExpression::init(int n, va_list args)
+{
+ for (int i=0; i<n; i++) {
+ Expression* expression = (Expression*)va_arg(args, void*);
+ this->arguments.push_back(expression);
+ }
+}
+
+void
+NewExpression::Write(FILE* to)
+{
+ fprintf(to, "new %s(", this->type->InstantiableName().c_str());
+ WriteArgumentList(to, this->arguments);
+ fprintf(to, ")");
+}
+
+NewArrayExpression::NewArrayExpression(Type* t, Expression* s)
+ :type(t),
+ size(s)
+{
+}
+
+NewArrayExpression::~NewArrayExpression()
+{
+}
+
+void
+NewArrayExpression::Write(FILE* to)
+{
+ fprintf(to, "new %s[", this->type->QualifiedName().c_str());
+ size->Write(to);
+ fprintf(to, "]");
+}
+
+Ternary::Ternary()
+ :condition(NULL),
+ ifpart(NULL),
+ elsepart(NULL)
+{
+}
+
+Ternary::Ternary(Expression* a, Expression* b, Expression* c)
+ :condition(a),
+ ifpart(b),
+ elsepart(c)
+{
+}
+
+Ternary::~Ternary()
+{
+}
+
+void
+Ternary::Write(FILE* to)
+{
+ fprintf(to, "((");
+ this->condition->Write(to);
+ fprintf(to, ")?(");
+ this->ifpart->Write(to);
+ fprintf(to, "):(");
+ this->elsepart->Write(to);
+ fprintf(to, "))");
+}
+
+Cast::Cast()
+ :type(NULL),
+ expression(NULL)
+{
+}
+
+Cast::Cast(Type* t, Expression* e)
+ :type(t),
+ expression(e)
+{
+}
+
+Cast::~Cast()
+{
+}
+
+void
+Cast::Write(FILE* to)
+{
+ fprintf(to, "((%s)", this->type->QualifiedName().c_str());
+ expression->Write(to);
+ fprintf(to, ")");
+}
+
+VariableDeclaration::VariableDeclaration(Variable* l, Expression* r, Type* c)
+ :lvalue(l),
+ cast(c),
+ rvalue(r)
+{
+}
+
+VariableDeclaration::VariableDeclaration(Variable* l)
+ :lvalue(l),
+ cast(NULL),
+ rvalue(NULL)
+{
+}
+
+VariableDeclaration::~VariableDeclaration()
+{
+}
+
+void
+VariableDeclaration::Write(FILE* to)
+{
+ this->lvalue->WriteDeclaration(to);
+ if (this->rvalue != NULL) {
+ fprintf(to, " = ");
+ if (this->cast != NULL) {
+ fprintf(to, "(%s)", this->cast->QualifiedName().c_str());
+ }
+ this->rvalue->Write(to);
+ }
+ fprintf(to, ";\n");
+}
+
+IfStatement::IfStatement()
+ :expression(NULL),
+ statements(new StatementBlock),
+ elseif(NULL)
+{
+}
+
+IfStatement::~IfStatement()
+{
+}
+
+void
+IfStatement::Write(FILE* to)
+{
+ if (this->expression != NULL) {
+ fprintf(to, "if (");
+ this->expression->Write(to);
+ fprintf(to, ") ");
+ }
+ this->statements->Write(to);
+ if (this->elseif != NULL) {
+ fprintf(to, "else ");
+ this->elseif->Write(to);
+ }
+}
+
+ReturnStatement::ReturnStatement(Expression* e)
+ :expression(e)
+{
+}
+
+ReturnStatement::~ReturnStatement()
+{
+}
+
+void
+ReturnStatement::Write(FILE* to)
+{
+ fprintf(to, "return ");
+ this->expression->Write(to);
+ fprintf(to, ";\n");
+}
+
+TryStatement::TryStatement()
+ :statements(new StatementBlock)
+{
+}
+
+TryStatement::~TryStatement()
+{
+}
+
+void
+TryStatement::Write(FILE* to)
+{
+ fprintf(to, "try ");
+ this->statements->Write(to);
+}
+
+CatchStatement::CatchStatement(Variable* e)
+ :statements(new StatementBlock),
+ exception(e)
+{
+}
+
+CatchStatement::~CatchStatement()
+{
+}
+
+void
+CatchStatement::Write(FILE* to)
+{
+ fprintf(to, "catch ");
+ if (this->exception != NULL) {
+ fprintf(to, "(");
+ this->exception->WriteDeclaration(to);
+ fprintf(to, ") ");
+ }
+ this->statements->Write(to);
+}
+
+FinallyStatement::FinallyStatement()
+ :statements(new StatementBlock)
+{
+}
+
+FinallyStatement::~FinallyStatement()
+{
+}
+
+void
+FinallyStatement::Write(FILE* to)
+{
+ fprintf(to, "finally ");
+ this->statements->Write(to);
+}
+
+Case::Case()
+ :statements(new StatementBlock)
+{
+}
+
+Case::Case(const string& c)
+ :statements(new StatementBlock)
+{
+ cases.push_back(c);
+}
+
+Case::~Case()
+{
+}
+
+void
+Case::Write(FILE* to)
+{
+ int N = this->cases.size();
+ if (N > 0) {
+ for (int i=0; i<N; i++) {
+ string s = this->cases[i];
+ if (s.length() != 0) {
+ fprintf(to, "case %s:\n", s.c_str());
+ } else {
+ fprintf(to, "default:\n");
+ }
+ }
+ } else {
+ fprintf(to, "default:\n");
+ }
+ statements->Write(to);
+}
+
+SwitchStatement::SwitchStatement(Expression* e)
+ :expression(e)
+{
+}
+
+SwitchStatement::~SwitchStatement()
+{
+}
+
+void
+SwitchStatement::Write(FILE* to)
+{
+ fprintf(to, "switch (");
+ this->expression->Write(to);
+ fprintf(to, ")\n{\n");
+ int N = this->cases.size();
+ for (int i=0; i<N; i++) {
+ this->cases[i]->Write(to);
+ }
+ fprintf(to, "}\n");
+}
+
+Break::Break()
+{
+}
+
+Break::~Break()
+{
+}
+
+void
+Break::Write(FILE* to)
+{
+ fprintf(to, "break;\n");
+}
+
+Method::Method()
+ :ClassElement(),
+ modifiers(0),
+ returnType(NULL), // (NULL means constructor)
+ returnTypeDimension(0),
+ statements(NULL)
+{
+}
+
+Method::~Method()
+{
+}
+
+void
+Method::GatherTypes(set<Type*>* types) const
+{
+ size_t N, i;
+
+ if (this->returnType) {
+ types->insert(this->returnType);
+ }
+
+ N = this->parameters.size();
+ for (i=0; i<N; i++) {
+ this->parameters[i]->GatherTypes(types);
+ }
+
+ N = this->exceptions.size();
+ for (i=0; i<N; i++) {
+ types->insert(this->exceptions[i]);
+ }
+}
+
+void
+Method::Write(FILE* to)
+{
+ size_t N, i;
+
+ if (this->comment.length() != 0) {
+ fprintf(to, "%s\n", this->comment.c_str());
+ }
+
+ WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | ABSTRACT | FINAL | OVERRIDE);
+
+ if (this->returnType != NULL) {
+ string dim;
+ for (i=0; i<this->returnTypeDimension; i++) {
+ dim += "[]";
+ }
+ fprintf(to, "%s%s ", this->returnType->QualifiedName().c_str(),
+ dim.c_str());
+ }
+
+ fprintf(to, "%s(", this->name.c_str());
+
+ N = this->parameters.size();
+ for (i=0; i<N; i++) {
+ this->parameters[i]->WriteDeclaration(to);
+ if (i != N-1) {
+ fprintf(to, ", ");
+ }
+ }
+
+ fprintf(to, ")");
+
+ N = this->exceptions.size();
+ for (i=0; i<N; i++) {
+ if (i == 0) {
+ fprintf(to, " throws ");
+ } else {
+ fprintf(to, ", ");
+ }
+ fprintf(to, "%s", this->exceptions[i]->QualifiedName().c_str());
+ }
+
+ if (this->statements == NULL) {
+ fprintf(to, ";\n");
+ } else {
+ fprintf(to, "\n");
+ this->statements->Write(to);
+ }
+}
+
+Class::Class()
+ :modifiers(0),
+ what(CLASS),
+ type(NULL),
+ extends(NULL)
+{
+}
+
+Class::~Class()
+{
+}
+
+void
+Class::GatherTypes(set<Type*>* types) const
+{
+ int N, i;
+
+ types->insert(this->type);
+ if (this->extends != NULL) {
+ types->insert(this->extends);
+ }
+
+ N = this->interfaces.size();
+ for (i=0; i<N; i++) {
+ types->insert(this->interfaces[i]);
+ }
+
+ N = this->elements.size();
+ for (i=0; i<N; i++) {
+ this->elements[i]->GatherTypes(types);
+ }
+}
+
+void
+Class::Write(FILE* to)
+{
+ size_t N, i;
+
+ if (this->comment.length() != 0) {
+ fprintf(to, "%s\n", this->comment.c_str());
+ }
+
+ WriteModifiers(to, this->modifiers, ALL_MODIFIERS);
+
+ if (this->what == Class::CLASS) {
+ fprintf(to, "class ");
+ } else {
+ fprintf(to, "interface ");
+ }
+
+ string name = this->type->Name();
+ size_t pos = name.rfind('.');
+ if (pos != string::npos) {
+ name = name.c_str() + pos + 1;
+ }
+
+ fprintf(to, "%s", name.c_str());
+
+ if (this->extends != NULL) {
+ fprintf(to, " extends %s", this->extends->QualifiedName().c_str());
+ }
+
+ N = this->interfaces.size();
+ if (N != 0) {
+ if (this->what == Class::CLASS) {
+ fprintf(to, " implements");
+ } else {
+ fprintf(to, " extends");
+ }
+ for (i=0; i<N; i++) {
+ fprintf(to, " %s", this->interfaces[i]->QualifiedName().c_str());
+ }
+ }
+
+ fprintf(to, "\n");
+ fprintf(to, "{\n");
+
+ N = this->elements.size();
+ for (i=0; i<N; i++) {
+ this->elements[i]->Write(to);
+ }
+
+ fprintf(to, "}\n");
+
+}
+
+Document::Document()
+{
+}
+
+Document::~Document()
+{
+}
+
+static string
+escape_backslashes(const string& str)
+{
+ string result;
+ const size_t I=str.length();
+ for (size_t i=0; i<I; i++) {
+ char c = str[i];
+ if (c == '\\') {
+ result += "\\\\";
+ } else {
+ result += c;
+ }
+ }
+ return result;
+}
+
+void
+Document::Write(FILE* to)
+{
+ size_t N, i;
+
+ if (this->comment.length() != 0) {
+ fprintf(to, "%s\n", this->comment.c_str());
+ }
+ fprintf(to, "/*\n"
+ " * This file is auto-generated. DO NOT MODIFY.\n"
+ " * Original file: %s\n"
+ " */\n", escape_backslashes(this->originalSrc).c_str());
+ if (this->package.length() != 0) {
+ fprintf(to, "package %s;\n", this->package.c_str());
+ }
+
+ N = this->classes.size();
+ for (i=0; i<N; i++) {
+ Class* c = this->classes[i];
+ c->Write(to);
+ }
+}
+
diff --git a/tools/aidl/AST.h b/tools/aidl/AST.h
new file mode 100644
index 0000000..ead5e7a
--- /dev/null
+++ b/tools/aidl/AST.h
@@ -0,0 +1,371 @@
+#ifndef AIDL_AST_H
+#define AIDL_AST_H
+
+#include <string>
+#include <vector>
+#include <set>
+#include <stdarg.h>
+#include <stdio.h>
+
+using namespace std;
+
+class Type;
+
+enum {
+ PACKAGE_PRIVATE = 0x00000000,
+ PUBLIC = 0x00000001,
+ PRIVATE = 0x00000002,
+ PROTECTED = 0x00000003,
+ SCOPE_MASK = 0x00000003,
+
+ STATIC = 0x00000010,
+ FINAL = 0x00000020,
+ ABSTRACT = 0x00000040,
+
+ OVERRIDE = 0x00000100,
+
+ ALL_MODIFIERS = 0xffffffff
+};
+
+// Write the modifiers that are set in both mod and mask
+void WriteModifiers(FILE* to, int mod, int mask);
+
+struct ClassElement
+{
+ ClassElement();
+ virtual ~ClassElement();
+
+ virtual void GatherTypes(set<Type*>* types) const = 0;
+ virtual void Write(FILE* to) = 0;
+};
+
+struct Expression
+{
+ virtual ~Expression();
+ virtual void Write(FILE* to) = 0;
+};
+
+struct LiteralExpression : public Expression
+{
+ string value;
+
+ LiteralExpression(const string& value);
+ virtual ~LiteralExpression();
+ virtual void Write(FILE* to);
+};
+
+// TODO: also escape the contents. not needed for now
+struct StringLiteralExpression : public Expression
+{
+ string value;
+
+ StringLiteralExpression(const string& value);
+ virtual ~StringLiteralExpression();
+ virtual void Write(FILE* to);
+};
+
+struct Variable : public Expression
+{
+ Type* type;
+ string name;
+ int dimension;
+
+ Variable();
+ Variable(Type* type, const string& name);
+ Variable(Type* type, const string& name, int dimension);
+ virtual ~Variable();
+
+ virtual void GatherTypes(set<Type*>* types) const;
+ void WriteDeclaration(FILE* to);
+ void Write(FILE* to);
+};
+
+struct FieldVariable : public Expression
+{
+ Expression* object;
+ Type* clazz;
+ string name;
+
+ FieldVariable(Expression* object, const string& name);
+ FieldVariable(Type* clazz, const string& name);
+ virtual ~FieldVariable();
+
+ void Write(FILE* to);
+};
+
+struct Field : public ClassElement
+{
+ string comment;
+ int modifiers;
+ Variable *variable;
+ string value;
+
+ Field();
+ Field(int modifiers, Variable* variable);
+ virtual ~Field();
+
+ virtual void GatherTypes(set<Type*>* types) const;
+ virtual void Write(FILE* to);
+};
+
+struct Statement
+{
+ virtual ~Statement();
+ virtual void Write(FILE* to) = 0;
+};
+
+struct StatementBlock : public Statement
+{
+ vector<Statement*> statements;
+
+ StatementBlock();
+ virtual ~StatementBlock();
+ virtual void Write(FILE* to);
+
+ void Add(Statement* statement);
+ void Add(Expression* expression);
+};
+
+struct ExpressionStatement : public Statement
+{
+ Expression* expression;
+
+ ExpressionStatement(Expression* expression);
+ virtual ~ExpressionStatement();
+ virtual void Write(FILE* to);
+};
+
+struct Assignment : public Expression
+{
+ Variable* lvalue;
+ Expression* rvalue;
+ Type* cast;
+
+ Assignment(Variable* lvalue, Expression* rvalue);
+ Assignment(Variable* lvalue, Expression* rvalue, Type* cast);
+ virtual ~Assignment();
+ virtual void Write(FILE* to);
+};
+
+struct MethodCall : public Expression
+{
+ Expression* obj;
+ Type* clazz;
+ string name;
+ vector<Expression*> arguments;
+ vector<string> exceptions;
+
+ MethodCall(const string& name);
+ MethodCall(const string& name, int argc, ...);
+ MethodCall(Expression* obj, const string& name);
+ MethodCall(Type* clazz, const string& name);
+ MethodCall(Expression* obj, const string& name, int argc, ...);
+ MethodCall(Type* clazz, const string& name, int argc, ...);
+ virtual ~MethodCall();
+ virtual void Write(FILE* to);
+
+private:
+ void init(int n, va_list args);
+};
+
+struct Comparison : public Expression
+{
+ Expression* lvalue;
+ string op;
+ Expression* rvalue;
+
+ Comparison(Expression* lvalue, const string& op, Expression* rvalue);
+ virtual ~Comparison();
+ virtual void Write(FILE* to);
+};
+
+struct NewExpression : public Expression
+{
+ Type* type;
+ vector<Expression*> arguments;
+
+ NewExpression(Type* type);
+ NewExpression(Type* type, int argc, ...);
+ virtual ~NewExpression();
+ virtual void Write(FILE* to);
+
+private:
+ void init(int n, va_list args);
+};
+
+struct NewArrayExpression : public Expression
+{
+ Type* type;
+ Expression* size;
+
+ NewArrayExpression(Type* type, Expression* size);
+ virtual ~NewArrayExpression();
+ virtual void Write(FILE* to);
+};
+
+struct Ternary : public Expression
+{
+ Expression* condition;
+ Expression* ifpart;
+ Expression* elsepart;
+
+ Ternary();
+ Ternary(Expression* condition, Expression* ifpart, Expression* elsepart);
+ virtual ~Ternary();
+ virtual void Write(FILE* to);
+};
+
+struct Cast : public Expression
+{
+ Type* type;
+ Expression* expression;
+
+ Cast();
+ Cast(Type* type, Expression* expression);
+ virtual ~Cast();
+ virtual void Write(FILE* to);
+};
+
+struct VariableDeclaration : public Statement
+{
+ Variable* lvalue;
+ Type* cast;
+ Expression* rvalue;
+
+ VariableDeclaration(Variable* lvalue);
+ VariableDeclaration(Variable* lvalue, Expression* rvalue, Type* cast = NULL);
+ virtual ~VariableDeclaration();
+ virtual void Write(FILE* to);
+};
+
+struct IfStatement : public Statement
+{
+ Expression* expression;
+ StatementBlock* statements;
+ IfStatement* elseif;
+
+ IfStatement();
+ virtual ~IfStatement();
+ virtual void Write(FILE* to);
+};
+
+struct ReturnStatement : public Statement
+{
+ Expression* expression;
+
+ ReturnStatement(Expression* expression);
+ virtual ~ReturnStatement();
+ virtual void Write(FILE* to);
+};
+
+struct TryStatement : public Statement
+{
+ StatementBlock* statements;
+
+ TryStatement();
+ virtual ~TryStatement();
+ virtual void Write(FILE* to);
+};
+
+struct CatchStatement : public Statement
+{
+ StatementBlock* statements;
+ Variable* exception;
+
+ CatchStatement(Variable* exception);
+ virtual ~CatchStatement();
+ virtual void Write(FILE* to);
+};
+
+struct FinallyStatement : public Statement
+{
+ StatementBlock* statements;
+
+ FinallyStatement();
+ virtual ~FinallyStatement();
+ virtual void Write(FILE* to);
+};
+
+struct Case
+{
+ vector<string> cases;
+ StatementBlock* statements;
+
+ Case();
+ Case(const string& c);
+ virtual ~Case();
+ virtual void Write(FILE* to);
+};
+
+struct SwitchStatement : public Statement
+{
+ Expression* expression;
+ vector<Case*> cases;
+
+ SwitchStatement(Expression* expression);
+ virtual ~SwitchStatement();
+ virtual void Write(FILE* to);
+};
+
+struct Break : public Statement
+{
+ Break();
+ virtual ~Break();
+ virtual void Write(FILE* to);
+};
+
+struct Method : public ClassElement
+{
+ string comment;
+ int modifiers;
+ Type* returnType;
+ size_t returnTypeDimension;
+ string name;
+ vector<Variable*> parameters;
+ vector<Type*> exceptions;
+ StatementBlock* statements;
+
+ Method();
+ virtual ~Method();
+
+ virtual void GatherTypes(set<Type*>* types) const;
+ virtual void Write(FILE* to);
+};
+
+struct Class : public ClassElement
+{
+ enum {
+ CLASS,
+ INTERFACE
+ };
+
+ string comment;
+ int modifiers;
+ int what; // CLASS or INTERFACE
+ Type* type;
+ Type* extends;
+ vector<Type*> interfaces;
+ vector<ClassElement*> elements;
+
+ Class();
+ virtual ~Class();
+
+ virtual void GatherTypes(set<Type*>* types) const;
+ virtual void Write(FILE* to);
+};
+
+struct Document
+{
+ string comment;
+ string package;
+ string originalSrc;
+ set<Type*> imports;
+ vector<Class*> classes;
+
+ Document();
+ virtual ~Document();
+
+ virtual void Write(FILE* to);
+};
+
+#endif // AIDL_AST_H
diff --git a/tools/aidl/Android.mk b/tools/aidl/Android.mk
new file mode 100644
index 0000000..77d46ab
--- /dev/null
+++ b/tools/aidl/Android.mk
@@ -0,0 +1,29 @@
+# Copyright 2007 The Android Open Source Project
+#
+# Copies files into the directory structure described by a manifest
+
+# This tool is prebuilt if we're doing an app-only build.
+ifeq ($(TARGET_BUILD_APPS),)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ aidl_language_l.l \
+ aidl_language_y.y \
+ aidl.cpp \
+ aidl_language.cpp \
+ options.cpp \
+ search_path.cpp \
+ AST.cpp \
+ Type.cpp \
+ generate_java.cpp \
+ generate_java_binder.cpp \
+ generate_java_rpc.cpp
+
+LOCAL_CFLAGS := -g
+LOCAL_MODULE := aidl
+
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # TARGET_BUILD_APPS
diff --git a/tools/aidl/NOTICE b/tools/aidl/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/tools/aidl/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/tools/aidl/Type.cpp b/tools/aidl/Type.cpp
new file mode 100644
index 0000000..d572af6
--- /dev/null
+++ b/tools/aidl/Type.cpp
@@ -0,0 +1,1440 @@
+#include "Type.h"
+
+Namespace NAMES;
+
+Type* VOID_TYPE;
+Type* BOOLEAN_TYPE;
+Type* BYTE_TYPE;
+Type* CHAR_TYPE;
+Type* INT_TYPE;
+Type* LONG_TYPE;
+Type* FLOAT_TYPE;
+Type* DOUBLE_TYPE;
+Type* STRING_TYPE;
+Type* OBJECT_TYPE;
+Type* CHAR_SEQUENCE_TYPE;
+Type* TEXT_UTILS_TYPE;
+Type* REMOTE_EXCEPTION_TYPE;
+Type* RUNTIME_EXCEPTION_TYPE;
+Type* IBINDER_TYPE;
+Type* IINTERFACE_TYPE;
+Type* BINDER_NATIVE_TYPE;
+Type* BINDER_PROXY_TYPE;
+Type* PARCEL_TYPE;
+Type* PARCELABLE_INTERFACE_TYPE;
+Type* CONTEXT_TYPE;
+Type* MAP_TYPE;
+Type* LIST_TYPE;
+Type* CLASSLOADER_TYPE;
+Type* RPC_DATA_TYPE;
+Type* RPC_ERROR_TYPE;
+Type* EVENT_FAKE_TYPE;
+
+Expression* NULL_VALUE;
+Expression* THIS_VALUE;
+Expression* SUPER_VALUE;
+Expression* TRUE_VALUE;
+Expression* FALSE_VALUE;
+
+void
+register_base_types()
+{
+ VOID_TYPE = new BasicType("void",
+ "XXX", "XXX", "XXX", "XXX", "XXX",
+ "XXX", "XXX", "XXX", "XXX", "XXX");
+ NAMES.Add(VOID_TYPE);
+
+ BOOLEAN_TYPE = new BooleanType();
+ NAMES.Add(BOOLEAN_TYPE);
+
+ BYTE_TYPE = new BasicType("byte",
+ "writeByte", "readByte", "writeByteArray", "createByteArray", "readByteArray",
+ "putByte", "getByte", "putByteArray", "createByteArray", "getByteArray");
+ NAMES.Add(BYTE_TYPE);
+
+ CHAR_TYPE = new CharType();
+ NAMES.Add(CHAR_TYPE);
+
+ INT_TYPE = new BasicType("int",
+ "writeInt", "readInt", "writeIntArray", "createIntArray", "readIntArray",
+ "putInteger", "getInteger", "putIntegerArray", "createIntegerArray", "getIntegerArray");
+ NAMES.Add(INT_TYPE);
+
+ LONG_TYPE = new BasicType("long",
+ "writeLong", "readLong", "writeLongArray", "createLongArray", "readLongArray",
+ "putLong", "getLong", "putLongArray", "createLongArray", "getLongArray");
+ NAMES.Add(LONG_TYPE);
+
+ FLOAT_TYPE = new BasicType("float",
+ "writeFloat", "readFloat", "writeFloatArray", "createFloatArray", "readFloatArray",
+ "putFloat", "getFloat", "putFloatArray", "createFloatArray", "getFloatArray");
+ NAMES.Add(FLOAT_TYPE);
+
+ DOUBLE_TYPE = new BasicType("double",
+ "writeDouble", "readDouble", "writeDoubleArray", "createDoubleArray", "readDoubleArray",
+ "putDouble", "getDouble", "putDoubleArray", "createDoubleArray", "getDoubleArray");
+ NAMES.Add(DOUBLE_TYPE);
+
+ STRING_TYPE = new StringType();
+ NAMES.Add(STRING_TYPE);
+
+ OBJECT_TYPE = new Type("java.lang", "Object", Type::BUILT_IN, false, false, false);
+ NAMES.Add(OBJECT_TYPE);
+
+ CHAR_SEQUENCE_TYPE = new CharSequenceType();
+ NAMES.Add(CHAR_SEQUENCE_TYPE);
+
+ MAP_TYPE = new MapType();
+ NAMES.Add(MAP_TYPE);
+
+ LIST_TYPE = new ListType();
+ NAMES.Add(LIST_TYPE);
+
+ TEXT_UTILS_TYPE = new Type("android.text", "TextUtils", Type::BUILT_IN, false, false, false);
+ NAMES.Add(TEXT_UTILS_TYPE);
+
+ REMOTE_EXCEPTION_TYPE = new RemoteExceptionType();
+ NAMES.Add(REMOTE_EXCEPTION_TYPE);
+
+ RUNTIME_EXCEPTION_TYPE = new RuntimeExceptionType();
+ NAMES.Add(RUNTIME_EXCEPTION_TYPE);
+
+ IBINDER_TYPE = new IBinderType();
+ NAMES.Add(IBINDER_TYPE);
+
+ IINTERFACE_TYPE = new IInterfaceType();
+ NAMES.Add(IINTERFACE_TYPE);
+
+ BINDER_NATIVE_TYPE = new BinderType();
+ NAMES.Add(BINDER_NATIVE_TYPE);
+
+ BINDER_PROXY_TYPE = new BinderProxyType();
+ NAMES.Add(BINDER_PROXY_TYPE);
+
+ PARCEL_TYPE = new ParcelType();
+ NAMES.Add(PARCEL_TYPE);
+
+ PARCELABLE_INTERFACE_TYPE = new ParcelableInterfaceType();
+ NAMES.Add(PARCELABLE_INTERFACE_TYPE);
+
+ CONTEXT_TYPE = new Type("android.content", "Context", Type::BUILT_IN, false, false, false);
+ NAMES.Add(CONTEXT_TYPE);
+
+ RPC_DATA_TYPE = new RpcDataType();
+ NAMES.Add(RPC_DATA_TYPE);
+
+ RPC_ERROR_TYPE = new UserDataType("android.support.place.rpc", "RpcError",
+ true, __FILE__, __LINE__);
+ NAMES.Add(RPC_ERROR_TYPE);
+
+ EVENT_FAKE_TYPE = new Type("event", Type::BUILT_IN, false, false, false);
+ NAMES.Add(EVENT_FAKE_TYPE);
+
+ CLASSLOADER_TYPE = new ClassLoaderType();
+ NAMES.Add(CLASSLOADER_TYPE);
+
+ NULL_VALUE = new LiteralExpression("null");
+ THIS_VALUE = new LiteralExpression("this");
+ SUPER_VALUE = new LiteralExpression("super");
+ TRUE_VALUE = new LiteralExpression("true");
+ FALSE_VALUE = new LiteralExpression("false");
+
+ NAMES.AddGenericType("java.util", "List", 1);
+ NAMES.AddGenericType("java.util", "Map", 2);
+}
+
+static Type*
+make_generic_type(const string& package, const string& name,
+ const vector<Type*>& args)
+{
+ if (package == "java.util" && name == "List") {
+ return new GenericListType("java.util", "List", args);
+ }
+ return NULL;
+ //return new GenericType(package, name, args);
+}
+
+// ================================================================
+
+Type::Type(const string& name, int kind, bool canWriteToParcel, bool canWriteToRpcData,
+ bool canBeOut)
+ :m_package(),
+ m_name(name),
+ m_declFile(""),
+ m_declLine(-1),
+ m_kind(kind),
+ m_canWriteToParcel(canWriteToParcel),
+ m_canWriteToRpcData(canWriteToRpcData),
+ m_canBeOut(canBeOut)
+{
+ m_qualifiedName = name;
+}
+
+Type::Type(const string& package, const string& name,
+ int kind, bool canWriteToParcel, bool canWriteToRpcData,
+ bool canBeOut, const string& declFile, int declLine)
+ :m_package(package),
+ m_name(name),
+ m_declFile(declFile),
+ m_declLine(declLine),
+ m_kind(kind),
+ m_canWriteToParcel(canWriteToParcel),
+ m_canWriteToRpcData(canWriteToRpcData),
+ m_canBeOut(canBeOut)
+{
+ if (package.length() > 0) {
+ m_qualifiedName = package;
+ m_qualifiedName += '.';
+ }
+ m_qualifiedName += name;
+}
+
+Type::~Type()
+{
+}
+
+bool
+Type::CanBeArray() const
+{
+ return false;
+}
+
+string
+Type::ImportType() const
+{
+ return m_qualifiedName;
+}
+
+string
+Type::CreatorName() const
+{
+ return "";
+}
+
+string
+Type::RpcCreatorName() const
+{
+ return "";
+}
+
+string
+Type::InstantiableName() const
+{
+ return QualifiedName();
+}
+
+
+void
+Type::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%sn",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* WriteToParcel error "
+ + m_qualifiedName + " */"));
+}
+
+void
+Type::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* CreateFromParcel error "
+ + m_qualifiedName + " */"));
+}
+
+void
+Type::ReadFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* ReadFromParcel error "
+ + m_qualifiedName + " */"));
+}
+
+void
+Type::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* WriteArrayToParcel error "
+ + m_qualifiedName + " */"));
+}
+
+void
+Type::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* CreateArrayFromParcel error "
+ + m_qualifiedName + " */"));
+}
+
+void
+Type::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* ReadArrayFromParcel error "
+ + m_qualifiedName + " */"));
+}
+
+void
+Type::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* WriteToRpcData error "
+ + m_qualifiedName + " */"));
+}
+
+void
+Type::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* ReadFromRpcData error "
+ + m_qualifiedName + " */"));
+}
+
+void
+Type::SetQualifiedName(const string& qualified)
+{
+ m_qualifiedName = qualified;
+}
+
+Expression*
+Type::BuildWriteToParcelFlags(int flags)
+{
+ if (flags == 0) {
+ return new LiteralExpression("0");
+ }
+ if ((flags&PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+ return new FieldVariable(PARCELABLE_INTERFACE_TYPE,
+ "PARCELABLE_WRITE_RETURN_VALUE");
+ }
+ return new LiteralExpression("0");
+}
+
+// ================================================================
+
+BasicType::BasicType(const string& name, const string& marshallParcel,
+ const string& unmarshallParcel, const string& writeArrayParcel,
+ const string& createArrayParcel, const string& readArrayParcel,
+ const string& marshallRpc, const string& unmarshallRpc,
+ const string& writeArrayRpc, const string& createArrayRpc, const string& readArrayRpc)
+ :Type(name, BUILT_IN, true, true, false),
+ m_marshallParcel(marshallParcel),
+ m_unmarshallParcel(unmarshallParcel),
+ m_writeArrayParcel(writeArrayParcel),
+ m_createArrayParcel(createArrayParcel),
+ m_readArrayParcel(readArrayParcel),
+ m_marshallRpc(marshallRpc),
+ m_unmarshallRpc(unmarshallRpc),
+ m_writeArrayRpc(writeArrayRpc),
+ m_createArrayRpc(createArrayRpc),
+ m_readArrayRpc(readArrayRpc)
+{
+}
+
+void
+BasicType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, m_marshallParcel, 1, v));
+}
+
+void
+BasicType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(parcel, m_unmarshallParcel)));
+}
+
+bool
+BasicType::CanBeArray() const
+{
+ return true;
+}
+
+void
+BasicType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, m_writeArrayParcel, 1, v));
+}
+
+void
+BasicType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(parcel, m_createArrayParcel)));
+}
+
+void
+BasicType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ addTo->Add(new MethodCall(parcel, m_readArrayParcel, 1, v));
+}
+
+void
+BasicType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, m_marshallRpc, 2, k, v));
+}
+
+void
+BasicType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, m_unmarshallRpc, 1, k)));
+}
+
+// ================================================================
+
+BooleanType::BooleanType()
+ :Type("boolean", BUILT_IN, true, true, false)
+{
+}
+
+void
+BooleanType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, "writeInt", 1,
+ new Ternary(v, new LiteralExpression("1"),
+ new LiteralExpression("0"))));
+}
+
+void
+BooleanType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ addTo->Add(new Assignment(v, new Comparison(new LiteralExpression("0"),
+ "!=", new MethodCall(parcel, "readInt"))));
+}
+
+bool
+BooleanType::CanBeArray() const
+{
+ return true;
+}
+
+void
+BooleanType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, "writeBooleanArray", 1, v));
+}
+
+void
+BooleanType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(parcel, "createBooleanArray")));
+}
+
+void
+BooleanType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ addTo->Add(new MethodCall(parcel, "readBooleanArray", 1, v));
+}
+
+void
+BooleanType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putBoolean", 2, k, v));
+}
+
+void
+BooleanType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getBoolean", 1, k)));
+}
+
+// ================================================================
+
+CharType::CharType()
+ :Type("char", BUILT_IN, true, true, false)
+{
+}
+
+void
+CharType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, "writeInt", 1,
+ new Cast(INT_TYPE, v)));
+}
+
+void
+CharType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(parcel, "readInt"), this));
+}
+
+bool
+CharType::CanBeArray() const
+{
+ return true;
+}
+
+void
+CharType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, "writeCharArray", 1, v));
+}
+
+void
+CharType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(parcel, "createCharArray")));
+}
+
+void
+CharType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ addTo->Add(new MethodCall(parcel, "readCharArray", 1, v));
+}
+
+void
+CharType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putChar", 2, k, v));
+}
+
+void
+CharType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getChar", 1, k)));
+}
+
+// ================================================================
+
+StringType::StringType()
+ :Type("java.lang", "String", BUILT_IN, true, true, false)
+{
+}
+
+string
+StringType::CreatorName() const
+{
+ return "android.os.Parcel.STRING_CREATOR";
+}
+
+void
+StringType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, "writeString", 1, v));
+}
+
+void
+StringType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(parcel, "readString")));
+}
+
+bool
+StringType::CanBeArray() const
+{
+ return true;
+}
+
+void
+StringType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, "writeStringArray", 1, v));
+}
+
+void
+StringType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(parcel, "createStringArray")));
+}
+
+void
+StringType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ addTo->Add(new MethodCall(parcel, "readStringArray", 1, v));
+}
+
+void
+StringType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putString", 2, k, v));
+}
+
+void
+StringType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getString", 1, k)));
+}
+
+// ================================================================
+
+CharSequenceType::CharSequenceType()
+ :Type("java.lang", "CharSequence", BUILT_IN, true, true, false)
+{
+}
+
+string
+CharSequenceType::CreatorName() const
+{
+ return "android.os.Parcel.STRING_CREATOR";
+}
+
+void
+CharSequenceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ // if (v != null) {
+ // parcel.writeInt(1);
+ // v.writeToParcel(parcel);
+ // } else {
+ // parcel.writeInt(0);
+ // }
+ IfStatement* elsepart = new IfStatement();
+ elsepart->statements->Add(new MethodCall(parcel, "writeInt", 1,
+ new LiteralExpression("0")));
+ IfStatement* ifpart = new IfStatement;
+ ifpart->expression = new Comparison(v, "!=", NULL_VALUE);
+ ifpart->elseif = elsepart;
+ ifpart->statements->Add(new MethodCall(parcel, "writeInt", 1,
+ new LiteralExpression("1")));
+ ifpart->statements->Add(new MethodCall(TEXT_UTILS_TYPE, "writeToParcel",
+ 3, v, parcel, BuildWriteToParcelFlags(flags)));
+
+ addTo->Add(ifpart);
+}
+
+void
+CharSequenceType::CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ // if (0 != parcel.readInt()) {
+ // v = TextUtils.createFromParcel(parcel)
+ // } else {
+ // v = null;
+ // }
+ IfStatement* elsepart = new IfStatement();
+ elsepart->statements->Add(new Assignment(v, NULL_VALUE));
+
+ IfStatement* ifpart = new IfStatement();
+ ifpart->expression = new Comparison(new LiteralExpression("0"), "!=",
+ new MethodCall(parcel, "readInt"));
+ ifpart->elseif = elsepart;
+ ifpart->statements->Add(new Assignment(v,
+ new MethodCall(TEXT_UTILS_TYPE,
+ "CHAR_SEQUENCE_CREATOR.createFromParcel", 1, parcel)));
+
+ addTo->Add(ifpart);
+}
+
+
+// ================================================================
+
+RemoteExceptionType::RemoteExceptionType()
+ :Type("android.os", "RemoteException", BUILT_IN, false, false, false)
+{
+}
+
+void
+RemoteExceptionType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+void
+RemoteExceptionType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+// ================================================================
+
+RuntimeExceptionType::RuntimeExceptionType()
+ :Type("java.lang", "RuntimeException", BUILT_IN, false, false, false)
+{
+}
+
+void
+RuntimeExceptionType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+void
+RuntimeExceptionType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+
+// ================================================================
+
+IBinderType::IBinderType()
+ :Type("android.os", "IBinder", BUILT_IN, true, false, false)
+{
+}
+
+void
+IBinderType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, "writeStrongBinder", 1, v));
+}
+
+void
+IBinderType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(parcel, "readStrongBinder")));
+}
+
+void
+IBinderType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, "writeBinderArray", 1, v));
+}
+
+void
+IBinderType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(parcel, "createBinderArray")));
+}
+
+void
+IBinderType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ addTo->Add(new MethodCall(parcel, "readBinderArray", 1, v));
+}
+
+
+// ================================================================
+
+IInterfaceType::IInterfaceType()
+ :Type("android.os", "IInterface", BUILT_IN, false, false, false)
+{
+}
+
+void
+IInterfaceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+void
+IInterfaceType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+
+// ================================================================
+
+BinderType::BinderType()
+ :Type("android.os", "Binder", BUILT_IN, false, false, false)
+{
+}
+
+void
+BinderType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+void
+BinderType::CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+
+// ================================================================
+
+BinderProxyType::BinderProxyType()
+ :Type("android.os", "BinderProxy", BUILT_IN, false, false, false)
+{
+}
+
+void
+BinderProxyType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+void
+BinderProxyType::CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+
+// ================================================================
+
+ParcelType::ParcelType()
+ :Type("android.os", "Parcel", BUILT_IN, false, false, false)
+{
+}
+
+void
+ParcelType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+void
+ParcelType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+// ================================================================
+
+ParcelableInterfaceType::ParcelableInterfaceType()
+ :Type("android.os", "Parcelable", BUILT_IN, false, false, false)
+{
+}
+
+void
+ParcelableInterfaceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+void
+ParcelableInterfaceType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__, __LINE__);
+}
+
+// ================================================================
+
+MapType::MapType()
+ :Type("java.util", "Map", BUILT_IN, true, false, true)
+{
+}
+
+void
+MapType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, "writeMap", 1, v));
+}
+
+static void EnsureClassLoader(StatementBlock* addTo, Variable** cl)
+{
+ // We don't want to look up the class loader once for every
+ // collection argument, so ensure we do it at most once per method.
+ if (*cl == NULL) {
+ *cl = new Variable(CLASSLOADER_TYPE, "cl");
+ addTo->Add(new VariableDeclaration(*cl,
+ new LiteralExpression("this.getClass().getClassLoader()"),
+ CLASSLOADER_TYPE));
+ }
+}
+
+void
+MapType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl)
+{
+ EnsureClassLoader(addTo, cl);
+ addTo->Add(new Assignment(v, new MethodCall(parcel, "readHashMap", 1, *cl)));
+}
+
+void
+MapType::ReadFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl)
+{
+ EnsureClassLoader(addTo, cl);
+ addTo->Add(new MethodCall(parcel, "readMap", 2, v, *cl));
+}
+
+
+// ================================================================
+
+ListType::ListType()
+ :Type("java.util", "List", BUILT_IN, true, true, true)
+{
+}
+
+string
+ListType::InstantiableName() const
+{
+ return "java.util.ArrayList";
+}
+
+void
+ListType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, "writeList", 1, v));
+}
+
+void
+ListType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl)
+{
+ EnsureClassLoader(addTo, cl);
+ addTo->Add(new Assignment(v, new MethodCall(parcel, "readArrayList", 1, *cl)));
+}
+
+void
+ListType::ReadFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl)
+{
+ EnsureClassLoader(addTo, cl);
+ addTo->Add(new MethodCall(parcel, "readList", 2, v, *cl));
+}
+
+void
+ListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putList", 2, k, v));
+}
+
+void
+ListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getList", 1, k)));
+}
+
+// ================================================================
+
+UserDataType::UserDataType(const string& package, const string& name,
+ bool builtIn, bool canWriteToParcel, bool canWriteToRpcData,
+ const string& declFile, int declLine)
+ :Type(package, name, builtIn ? BUILT_IN : USERDATA, canWriteToParcel, canWriteToRpcData,
+ true, declFile, declLine)
+{
+}
+
+string
+UserDataType::CreatorName() const
+{
+ return QualifiedName() + ".CREATOR";
+}
+
+string
+UserDataType::RpcCreatorName() const
+{
+ return QualifiedName() + ".RPC_CREATOR";
+}
+
+void
+UserDataType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ // if (v != null) {
+ // parcel.writeInt(1);
+ // v.writeToParcel(parcel);
+ // } else {
+ // parcel.writeInt(0);
+ // }
+ IfStatement* elsepart = new IfStatement();
+ elsepart->statements->Add(new MethodCall(parcel, "writeInt", 1,
+ new LiteralExpression("0")));
+ IfStatement* ifpart = new IfStatement;
+ ifpart->expression = new Comparison(v, "!=", NULL_VALUE);
+ ifpart->elseif = elsepart;
+ ifpart->statements->Add(new MethodCall(parcel, "writeInt", 1,
+ new LiteralExpression("1")));
+ ifpart->statements->Add(new MethodCall(v, "writeToParcel", 2,
+ parcel, BuildWriteToParcelFlags(flags)));
+
+ addTo->Add(ifpart);
+}
+
+void
+UserDataType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ // if (0 != parcel.readInt()) {
+ // v = CLASS.CREATOR.createFromParcel(parcel)
+ // } else {
+ // v = null;
+ // }
+ IfStatement* elsepart = new IfStatement();
+ elsepart->statements->Add(new Assignment(v, NULL_VALUE));
+
+ IfStatement* ifpart = new IfStatement();
+ ifpart->expression = new Comparison(new LiteralExpression("0"), "!=",
+ new MethodCall(parcel, "readInt"));
+ ifpart->elseif = elsepart;
+ ifpart->statements->Add(new Assignment(v,
+ new MethodCall(v->type, "CREATOR.createFromParcel", 1, parcel)));
+
+ addTo->Add(ifpart);
+}
+
+void
+UserDataType::ReadFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ // TODO: really, we don't need to have this extra check, but we
+ // don't have two separate marshalling code paths
+ // if (0 != parcel.readInt()) {
+ // v.readFromParcel(parcel)
+ // }
+ IfStatement* ifpart = new IfStatement();
+ ifpart->expression = new Comparison(new LiteralExpression("0"), "!=",
+ new MethodCall(parcel, "readInt"));
+ ifpart->statements->Add(new MethodCall(v, "readFromParcel", 1, parcel));
+ addTo->Add(ifpart);
+}
+
+bool
+UserDataType::CanBeArray() const
+{
+ return true;
+}
+
+void
+UserDataType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ addTo->Add(new MethodCall(parcel, "writeTypedArray", 2, v,
+ BuildWriteToParcelFlags(flags)));
+}
+
+void
+UserDataType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ string creator = v->type->QualifiedName() + ".CREATOR";
+ addTo->Add(new Assignment(v, new MethodCall(parcel,
+ "createTypedArray", 1, new LiteralExpression(creator))));
+}
+
+void
+UserDataType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ string creator = v->type->QualifiedName() + ".CREATOR";
+ addTo->Add(new MethodCall(parcel, "readTypedArray", 2,
+ v, new LiteralExpression(creator)));
+}
+
+void
+UserDataType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ // data.putFlattenable(k, v);
+ addTo->Add(new MethodCall(data, "putFlattenable", 2, k, v));
+}
+
+void
+UserDataType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl)
+{
+ // data.getFlattenable(k, CLASS.RPC_CREATOR);
+ addTo->Add(new Assignment(v, new MethodCall(data, "getFlattenable", 2, k,
+ new FieldVariable(v->type, "RPC_CREATOR"))));
+}
+
+// ================================================================
+
+InterfaceType::InterfaceType(const string& package, const string& name,
+ bool builtIn, bool oneway,
+ const string& declFile, int declLine)
+ :Type(package, name, builtIn ? BUILT_IN : INTERFACE, true, false, false,
+ declFile, declLine)
+ ,m_oneway(oneway)
+{
+}
+
+bool
+InterfaceType::OneWay() const
+{
+ return m_oneway;
+}
+
+void
+InterfaceType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ // parcel.writeStrongBinder(v != null ? v.asBinder() : null);
+ addTo->Add(new MethodCall(parcel, "writeStrongBinder", 1,
+ new Ternary(
+ new Comparison(v, "!=", NULL_VALUE),
+ new MethodCall(v, "asBinder"),
+ NULL_VALUE)));
+}
+
+void
+InterfaceType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ // v = Interface.asInterface(parcel.readStrongBinder());
+ string type = v->type->QualifiedName();
+ type += ".Stub";
+ addTo->Add(new Assignment(v,
+ new MethodCall( NAMES.Find(type), "asInterface", 1,
+ new MethodCall(parcel, "readStrongBinder"))));
+}
+
+
+// ================================================================
+
+GenericType::GenericType(const string& package, const string& name,
+ const vector<Type*>& args)
+ :Type(package, name, BUILT_IN, true, true, true)
+{
+ m_args = args;
+
+ m_importName = package + '.' + name;
+
+ string gen = "<";
+ int N = args.size();
+ for (int i=0; i<N; i++) {
+ Type* t = args[i];
+ gen += t->QualifiedName();
+ if (i != N-1) {
+ gen += ',';
+ }
+ }
+ gen += '>';
+ m_genericArguments = gen;
+ SetQualifiedName(m_importName + gen);
+}
+
+const vector<Type*>&
+GenericType::GenericArgumentTypes() const
+{
+ return m_args;
+}
+
+string
+GenericType::GenericArguments() const
+{
+ return m_genericArguments;
+}
+
+string
+GenericType::ImportType() const
+{
+ return m_importName;
+}
+
+void
+GenericType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ fprintf(stderr, "implement GenericType::WriteToParcel\n");
+}
+
+void
+GenericType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ fprintf(stderr, "implement GenericType::CreateFromParcel\n");
+}
+
+void
+GenericType::ReadFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ fprintf(stderr, "implement GenericType::ReadFromParcel\n");
+}
+
+
+// ================================================================
+
+GenericListType::GenericListType(const string& package, const string& name,
+ const vector<Type*>& args)
+ :GenericType(package, name, args),
+ m_creator(args[0]->CreatorName())
+{
+}
+
+string
+GenericListType::CreatorName() const
+{
+ return "android.os.Parcel.arrayListCreator";
+}
+
+string
+GenericListType::InstantiableName() const
+{
+ return "java.util.ArrayList" + GenericArguments();
+}
+
+void
+GenericListType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+{
+ if (m_creator == STRING_TYPE->CreatorName()) {
+ addTo->Add(new MethodCall(parcel, "writeStringList", 1, v));
+ } else if (m_creator == IBINDER_TYPE->CreatorName()) {
+ addTo->Add(new MethodCall(parcel, "writeBinderList", 1, v));
+ } else {
+ // parcel.writeTypedListXX(arg);
+ addTo->Add(new MethodCall(parcel, "writeTypedList", 1, v));
+ }
+}
+
+void
+GenericListType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+{
+ if (m_creator == STRING_TYPE->CreatorName()) {
+ addTo->Add(new Assignment(v,
+ new MethodCall(parcel, "createStringArrayList", 0)));
+ } else if (m_creator == IBINDER_TYPE->CreatorName()) {
+ addTo->Add(new Assignment(v,
+ new MethodCall(parcel, "createBinderArrayList", 0)));
+ } else {
+ // v = _data.readTypedArrayList(XXX.creator);
+ addTo->Add(new Assignment(v,
+ new MethodCall(parcel, "createTypedArrayList", 1,
+ new LiteralExpression(m_creator))));
+ }
+}
+
+void
+GenericListType::ReadFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable**)
+{
+ if (m_creator == STRING_TYPE->CreatorName()) {
+ addTo->Add(new MethodCall(parcel, "readStringList", 1, v));
+ } else if (m_creator == IBINDER_TYPE->CreatorName()) {
+ addTo->Add(new MethodCall(parcel, "readBinderList", 1, v));
+ } else {
+ // v = _data.readTypedList(v, XXX.creator);
+ addTo->Add(new MethodCall(parcel, "readTypedList", 2,
+ v,
+ new LiteralExpression(m_creator)));
+ }
+}
+
+void
+GenericListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ Type* generic = GenericArgumentTypes()[0];
+ if (generic == RPC_DATA_TYPE) {
+ addTo->Add(new MethodCall(data, "putRpcDataList", 2, k, v));
+ } else if (generic->RpcCreatorName() != "") {
+ addTo->Add(new MethodCall(data, "putFlattenableList", 2, k, v));
+ } else {
+ addTo->Add(new MethodCall(data, "putList", 2, k, v));
+ }
+}
+
+void
+GenericListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl)
+{
+ Type* generic = GenericArgumentTypes()[0];
+ if (generic == RPC_DATA_TYPE) {
+ addTo->Add(new Assignment(v, new MethodCall(data, "getRpcDataList", 2, k)));
+ } else if (generic->RpcCreatorName() != "") {
+ addTo->Add(new Assignment(v, new MethodCall(data, "getFlattenableList", 2, k,
+ new LiteralExpression(generic->RpcCreatorName()))));
+ } else {
+ string classArg = GenericArgumentTypes()[0]->QualifiedName();
+ classArg += ".class";
+ addTo->Add(new Assignment(v, new MethodCall(data, "getList", 2, k,
+ new LiteralExpression(classArg))));
+ }
+}
+
+
+// ================================================================
+
+RpcDataType::RpcDataType()
+ :UserDataType("android.support.place.rpc", "RpcData", true, true, true)
+{
+}
+
+void
+RpcDataType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putRpcData", 2, k, v));
+}
+
+void
+RpcDataType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getRpcData", 1, k)));
+}
+
+
+// ================================================================
+
+ClassLoaderType::ClassLoaderType()
+ :Type("java.lang", "ClassLoader", BUILT_IN, false, false, false)
+{
+}
+
+
+// ================================================================
+
+Namespace::Namespace()
+{
+}
+
+Namespace::~Namespace()
+{
+ int N = m_types.size();
+ for (int i=0; i<N; i++) {
+ delete m_types[i];
+ }
+}
+
+void
+Namespace::Add(Type* type)
+{
+ Type* t = Find(type->QualifiedName());
+ if (t == NULL) {
+ m_types.push_back(type);
+ }
+}
+
+void
+Namespace::AddGenericType(const string& package, const string& name, int args)
+{
+ Generic g;
+ g.package = package;
+ g.name = name;
+ g.qualified = package + '.' + name;
+ g.args = args;
+ m_generics.push_back(g);
+}
+
+Type*
+Namespace::Find(const string& name) const
+{
+ int N = m_types.size();
+ for (int i=0; i<N; i++) {
+ if (m_types[i]->QualifiedName() == name) {
+ return m_types[i];
+ }
+ }
+ return NULL;
+}
+
+Type*
+Namespace::Find(const char* package, const char* name) const
+{
+ string s;
+ if (package != NULL) {
+ s += package;
+ s += '.';
+ }
+ s += name;
+ return Find(s);
+}
+
+static string
+normalize_generic(const string& s)
+{
+ string r;
+ int N = s.size();
+ for (int i=0; i<N; i++) {
+ char c = s[i];
+ if (!isspace(c)) {
+ r += c;
+ }
+ }
+ return r;
+}
+
+Type*
+Namespace::Search(const string& name)
+{
+ // an exact match wins
+ Type* result = Find(name);
+ if (result != NULL) {
+ return result;
+ }
+
+ // try the class names
+ // our language doesn't allow you to not specify outer classes
+ // when referencing an inner class. that could be changed, and this
+ // would be the place to do it, but I don't think the complexity in
+ // scoping rules is worth it.
+ int N = m_types.size();
+ for (int i=0; i<N; i++) {
+ if (m_types[i]->Name() == name) {
+ return m_types[i];
+ }
+ }
+
+ // we got to here and it's not a generic, give up
+ if (name.find('<') == name.npos) {
+ return NULL;
+ }
+
+ // remove any whitespace
+ string normalized = normalize_generic(name);
+
+ // find the part before the '<', find a generic for it
+ ssize_t baseIndex = normalized.find('<');
+ string base(normalized.c_str(), baseIndex);
+ const Generic* g = search_generic(base);
+ if (g == NULL) {
+ return NULL;
+ }
+
+ // For each of the args, do a recursive search on it. We don't allow
+ // generics within generics like Java does, because we're really limiting
+ // them to just built-in container classes, at least for now. Our syntax
+ // ensures this right now as well.
+ vector<Type*> args;
+ size_t start = baseIndex + 1;
+ size_t end = start;
+ while (normalized[start] != '\0') {
+ end = normalized.find(',', start);
+ if (end == normalized.npos) {
+ end = normalized.find('>', start);
+ }
+ string s(normalized.c_str()+start, end-start);
+ Type* t = this->Search(s);
+ if (t == NULL) {
+ // maybe we should print a warning here?
+ return NULL;
+ }
+ args.push_back(t);
+ start = end+1;
+ }
+
+ // construct a GenericType, add it to our name set so they always get
+ // the same object, and return it.
+ result = make_generic_type(g->package, g->name, args);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ this->Add(result);
+ return this->Find(result->QualifiedName());
+}
+
+const Namespace::Generic*
+Namespace::search_generic(const string& name) const
+{
+ int N = m_generics.size();
+
+ // first exact match
+ for (int i=0; i<N; i++) {
+ const Generic& g = m_generics[i];
+ if (g.qualified == name) {
+ return &g;
+ }
+ }
+
+ // then name match
+ for (int i=0; i<N; i++) {
+ const Generic& g = m_generics[i];
+ if (g.name == name) {
+ return &g;
+ }
+ }
+
+ return NULL;
+}
+
+void
+Namespace::Dump() const
+{
+ int n = m_types.size();
+ for (int i=0; i<n; i++) {
+ Type* t = m_types[i];
+ printf("type: package=%s name=%s qualifiedName=%s\n",
+ t->Package().c_str(), t->Name().c_str(),
+ t->QualifiedName().c_str());
+ }
+}
diff --git a/tools/aidl/Type.h b/tools/aidl/Type.h
new file mode 100644
index 0000000..ae12720
--- /dev/null
+++ b/tools/aidl/Type.h
@@ -0,0 +1,542 @@
+#ifndef AIDL_TYPE_H
+#define AIDL_TYPE_H
+
+#include "AST.h"
+#include <string>
+#include <vector>
+
+using namespace std;
+
+class Type
+{
+public:
+ // kinds
+ enum {
+ BUILT_IN,
+ USERDATA,
+ INTERFACE,
+ GENERATED
+ };
+
+ // WriteToParcel flags
+ enum {
+ PARCELABLE_WRITE_RETURN_VALUE = 0x0001
+ };
+
+ Type(const string& name, int kind, bool canWriteToParcel,
+ bool canWriteToRpcData, bool canBeOut);
+ Type(const string& package, const string& name,
+ int kind, bool canWriteToParcel, bool canWriteToRpcData, bool canBeOut,
+ const string& declFile = "", int declLine = -1);
+ virtual ~Type();
+
+ inline string Package() const { return m_package; }
+ inline string Name() const { return m_name; }
+ inline string QualifiedName() const { return m_qualifiedName; }
+ inline int Kind() const { return m_kind; }
+ inline string DeclFile() const { return m_declFile; }
+ inline int DeclLine() const { return m_declLine; }
+ inline bool CanWriteToParcel() const { return m_canWriteToParcel; }
+ inline bool CanWriteToRpcData() const { return m_canWriteToRpcData; }
+ inline bool CanBeOutParameter() const { return m_canBeOut; }
+
+ virtual string ImportType() const;
+ virtual string CreatorName() const;
+ virtual string RpcCreatorName() const;
+ virtual string InstantiableName() const;
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual bool CanBeArray() const;
+
+ virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+
+protected:
+ void SetQualifiedName(const string& qualified);
+ Expression* BuildWriteToParcelFlags(int flags);
+
+private:
+ Type();
+ Type(const Type&);
+
+ string m_package;
+ string m_name;
+ string m_qualifiedName;
+ string m_declFile;
+ int m_declLine;
+ int m_kind;
+ bool m_canWriteToParcel;
+ bool m_canWriteToRpcData;
+ bool m_canBeOut;
+};
+
+class BasicType : public Type
+{
+public:
+ BasicType(const string& name,
+ const string& marshallParcel,
+ const string& unmarshallParcel,
+ const string& writeArrayParcel,
+ const string& createArrayParcel,
+ const string& readArrayParcel,
+ const string& marshallRpc,
+ const string& unmarshallRpc,
+ const string& writeArrayRpc,
+ const string& createArrayRpc,
+ const string& readArrayRpc);
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual bool CanBeArray() const;
+
+ virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+
+private:
+ string m_marshallParcel;
+ string m_unmarshallParcel;
+ string m_writeArrayParcel;
+ string m_createArrayParcel;
+ string m_readArrayParcel;
+ string m_marshallRpc;
+ string m_unmarshallRpc;
+ string m_writeArrayRpc;
+ string m_createArrayRpc;
+ string m_readArrayRpc;
+};
+
+class BooleanType : public Type
+{
+public:
+ BooleanType();
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual bool CanBeArray() const;
+
+ virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+};
+
+class CharType : public Type
+{
+public:
+ CharType();
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual bool CanBeArray() const;
+
+ virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+};
+
+
+class StringType : public Type
+{
+public:
+ StringType();
+
+ virtual string CreatorName() const;
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual bool CanBeArray() const;
+
+ virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+};
+
+class CharSequenceType : public Type
+{
+public:
+ CharSequenceType();
+
+ virtual string CreatorName() const;
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+};
+
+class RemoteExceptionType : public Type
+{
+public:
+ RemoteExceptionType();
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+};
+
+class RuntimeExceptionType : public Type
+{
+public:
+ RuntimeExceptionType();
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+};
+
+class IBinderType : public Type
+{
+public:
+ IBinderType();
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+};
+
+class IInterfaceType : public Type
+{
+public:
+ IInterfaceType();
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+};
+
+class BinderType : public Type
+{
+public:
+ BinderType();
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+};
+
+class BinderProxyType : public Type
+{
+public:
+ BinderProxyType();
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+};
+
+class ParcelType : public Type
+{
+public:
+ ParcelType();
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+};
+
+class ParcelableInterfaceType : public Type
+{
+public:
+ ParcelableInterfaceType();
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+};
+
+class MapType : public Type
+{
+public:
+ MapType();
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+};
+
+class ListType : public Type
+{
+public:
+ ListType();
+
+ virtual string InstantiableName() const;
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+};
+
+class UserDataType : public Type
+{
+public:
+ UserDataType(const string& package, const string& name,
+ bool builtIn, bool canWriteToParcel, bool canWriteToRpcData,
+ const string& declFile = "", int declLine = -1);
+
+ virtual string CreatorName() const;
+ virtual string RpcCreatorName() const;
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual bool CanBeArray() const;
+
+ virtual void WriteArrayToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+};
+
+class InterfaceType : public Type
+{
+public:
+ InterfaceType(const string& package, const string& name,
+ bool builtIn, bool oneway,
+ const string& declFile, int declLine);
+
+ bool OneWay() const;
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+private:
+ bool m_oneway;
+};
+
+
+class GenericType : public Type
+{
+public:
+ GenericType(const string& package, const string& name,
+ const vector<Type*>& args);
+
+ const vector<Type*>& GenericArgumentTypes() const;
+ string GenericArguments() const;
+
+ virtual string ImportType() const;
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+private:
+ string m_genericArguments;
+ string m_importName;
+ vector<Type*> m_args;
+};
+
+class RpcDataType : public UserDataType
+{
+public:
+ RpcDataType();
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+};
+
+class ClassLoaderType : public Type
+{
+public:
+ ClassLoaderType();
+};
+
+class GenericListType : public GenericType
+{
+public:
+ GenericListType(const string& package, const string& name,
+ const vector<Type*>& args);
+
+ virtual string CreatorName() const;
+ virtual string InstantiableName() const;
+
+ virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags);
+ virtual void CreateFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+ virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+
+private:
+ string m_creator;
+};
+
+class Namespace
+{
+public:
+ Namespace();
+ ~Namespace();
+ void Add(Type* type);
+
+ // args is the number of template types (what is this called?)
+ void AddGenericType(const string& package, const string& name, int args);
+
+ // lookup a specific class name
+ Type* Find(const string& name) const;
+ Type* Find(const char* package, const char* name) const;
+
+ // try to search by either a full name or a partial name
+ Type* Search(const string& name);
+
+ void Dump() const;
+
+private:
+ struct Generic {
+ string package;
+ string name;
+ string qualified;
+ int args;
+ };
+
+ const Generic* search_generic(const string& name) const;
+
+ vector<Type*> m_types;
+ vector<Generic> m_generics;
+};
+
+extern Namespace NAMES;
+
+extern Type* VOID_TYPE;
+extern Type* BOOLEAN_TYPE;
+extern Type* BYTE_TYPE;
+extern Type* CHAR_TYPE;
+extern Type* INT_TYPE;
+extern Type* LONG_TYPE;
+extern Type* FLOAT_TYPE;
+extern Type* DOUBLE_TYPE;
+extern Type* OBJECT_TYPE;
+extern Type* STRING_TYPE;
+extern Type* CHAR_SEQUENCE_TYPE;
+extern Type* TEXT_UTILS_TYPE;
+extern Type* REMOTE_EXCEPTION_TYPE;
+extern Type* RUNTIME_EXCEPTION_TYPE;
+extern Type* IBINDER_TYPE;
+extern Type* IINTERFACE_TYPE;
+extern Type* BINDER_NATIVE_TYPE;
+extern Type* BINDER_PROXY_TYPE;
+extern Type* PARCEL_TYPE;
+extern Type* PARCELABLE_INTERFACE_TYPE;
+
+extern Type* CONTEXT_TYPE;
+
+extern Type* RPC_DATA_TYPE;
+extern Type* RPC_ERROR_TYPE;
+extern Type* RPC_CONTEXT_TYPE;
+extern Type* EVENT_FAKE_TYPE;
+
+extern Expression* NULL_VALUE;
+extern Expression* THIS_VALUE;
+extern Expression* SUPER_VALUE;
+extern Expression* TRUE_VALUE;
+extern Expression* FALSE_VALUE;
+
+void register_base_types();
+
+#endif // AIDL_TYPE_H
diff --git a/tools/aidl/aidl.cpp b/tools/aidl/aidl.cpp
new file mode 100644
index 0000000..9c1867e
--- /dev/null
+++ b/tools/aidl/aidl.cpp
@@ -0,0 +1,1156 @@
+
+#include "aidl_language.h"
+#include "options.h"
+#include "search_path.h"
+#include "Type.h"
+#include "generate_java.h"
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <map>
+
+#ifdef HAVE_MS_C_RUNTIME
+#include <io.h>
+#include <sys/stat.h>
+#endif
+
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+// The following are gotten as the offset from the allowable id's between
+// android.os.IBinder.FIRST_CALL_TRANSACTION=1 and
+// android.os.IBinder.LAST_CALL_TRANSACTION=16777215
+#define MIN_USER_SET_METHOD_ID 0
+#define MAX_USER_SET_METHOD_ID 16777214
+
+using namespace std;
+
+static void
+test_document(document_item_type* d)
+{
+ while (d) {
+ if (d->item_type == INTERFACE_TYPE_BINDER) {
+ interface_type* c = (interface_type*)d;
+ printf("interface %s %s {\n", c->package, c->name.data);
+ interface_item_type *q = (interface_item_type*)c->interface_items;
+ while (q) {
+ if (q->item_type == METHOD_TYPE) {
+ method_type *m = (method_type*)q;
+ printf(" %s %s(", m->type.type.data, m->name.data);
+ arg_type *p = m->args;
+ while (p) {
+ printf("%s %s",p->type.type.data,p->name.data);
+ if (p->next) printf(", ");
+ p=p->next;
+ }
+ printf(")");
+ printf(";\n");
+ }
+ q=q->next;
+ }
+ printf("}\n");
+ }
+ else if (d->item_type == USER_DATA_TYPE) {
+ user_data_type* b = (user_data_type*)d;
+ if ((b->flattening_methods & PARCELABLE_DATA) != 0) {
+ printf("parcelable %s %s;\n", b->package, b->name.data);
+ }
+ if ((b->flattening_methods & RPC_DATA) != 0) {
+ printf("flattenable %s %s;\n", b->package, b->name.data);
+ }
+ }
+ else {
+ printf("UNKNOWN d=0x%08lx d->item_type=%d\n", (long)d, d->item_type);
+ }
+ d = d->next;
+ }
+}
+
+// ==========================================================
+int
+convert_direction(const char* direction)
+{
+ if (direction == NULL) {
+ return IN_PARAMETER;
+ }
+ if (0 == strcmp(direction, "in")) {
+ return IN_PARAMETER;
+ }
+ if (0 == strcmp(direction, "out")) {
+ return OUT_PARAMETER;
+ }
+ return INOUT_PARAMETER;
+}
+
+// ==========================================================
+struct import_info {
+ const char* from;
+ const char* filename;
+ buffer_type statement;
+ const char* neededClass;
+ document_item_type* doc;
+ struct import_info* next;
+};
+
+document_item_type* g_document = NULL;
+import_info* g_imports = NULL;
+
+static void
+main_document_parsed(document_item_type* d)
+{
+ g_document = d;
+}
+
+static void
+main_import_parsed(buffer_type* statement)
+{
+ import_info* import = (import_info*)malloc(sizeof(import_info));
+ memset(import, 0, sizeof(import_info));
+ import->from = strdup(g_currentFilename);
+ import->statement.lineno = statement->lineno;
+ import->statement.data = strdup(statement->data);
+ import->statement.extra = NULL;
+ import->next = g_imports;
+ import->neededClass = parse_import_statement(statement->data);
+ g_imports = import;
+}
+
+static ParserCallbacks g_mainCallbacks = {
+ &main_document_parsed,
+ &main_import_parsed
+};
+
+char*
+parse_import_statement(const char* text)
+{
+ const char* end;
+ int len;
+
+ while (isspace(*text)) {
+ text++;
+ }
+ while (!isspace(*text)) {
+ text++;
+ }
+ while (isspace(*text)) {
+ text++;
+ }
+ end = text;
+ while (!isspace(*end) && *end != ';') {
+ end++;
+ }
+ len = end-text;
+
+ char* rv = (char*)malloc(len+1);
+ memcpy(rv, text, len);
+ rv[len] = '\0';
+
+ return rv;
+}
+
+// ==========================================================
+static void
+import_import_parsed(buffer_type* statement)
+{
+}
+
+static ParserCallbacks g_importCallbacks = {
+ &main_document_parsed,
+ &import_import_parsed
+};
+
+// ==========================================================
+static int
+check_filename(const char* filename, const char* package, buffer_type* name)
+{
+ const char* p;
+ string expected;
+ string fn;
+ size_t len;
+ char cwd[MAXPATHLEN];
+ bool valid = false;
+
+#ifdef HAVE_WINDOWS_PATHS
+ if (isalpha(filename[0]) && filename[1] == ':'
+ && filename[2] == OS_PATH_SEPARATOR) {
+#else
+ if (filename[0] == OS_PATH_SEPARATOR) {
+#endif
+ fn = filename;
+ } else {
+ fn = getcwd(cwd, sizeof(cwd));
+ len = fn.length();
+ if (fn[len-1] != OS_PATH_SEPARATOR) {
+ fn += OS_PATH_SEPARATOR;
+ }
+ fn += filename;
+ }
+
+ if (package) {
+ expected = package;
+ expected += '.';
+ }
+
+ len = expected.length();
+ for (size_t i=0; i<len; i++) {
+ if (expected[i] == '.') {
+ expected[i] = OS_PATH_SEPARATOR;
+ }
+ }
+
+ p = strchr(name->data, '.');
+ len = p ? p-name->data : strlen(name->data);
+ expected.append(name->data, len);
+
+ expected += ".aidl";
+
+ len = fn.length();
+ valid = (len >= expected.length());
+
+ if (valid) {
+ p = fn.c_str() + (len - expected.length());
+
+#ifdef HAVE_WINDOWS_PATHS
+ if (OS_PATH_SEPARATOR != '/') {
+ // Input filename under cygwin most likely has / separators
+ // whereas the expected string uses \\ separators. Adjust
+ // them accordingly.
+ for (char *c = const_cast<char *>(p); *c; ++c) {
+ if (*c == '/') *c = OS_PATH_SEPARATOR;
+ }
+ }
+#endif
+
+#ifdef OS_CASE_SENSITIVE
+ valid = (expected == p);
+#else
+ valid = !strcasecmp(expected.c_str(), p);
+#endif
+ }
+
+ if (!valid) {
+ fprintf(stderr, "%s:%d interface %s should be declared in a file"
+ " called %s.\n",
+ filename, name->lineno, name->data, expected.c_str());
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+check_filenames(const char* filename, document_item_type* items)
+{
+ int err = 0;
+ while (items) {
+ if (items->item_type == USER_DATA_TYPE) {
+ user_data_type* p = (user_data_type*)items;
+ err |= check_filename(filename, p->package, &p->name);
+ }
+ else if (items->item_type == INTERFACE_TYPE_BINDER
+ || items->item_type == INTERFACE_TYPE_RPC) {
+ interface_type* c = (interface_type*)items;
+ err |= check_filename(filename, c->package, &c->name);
+ }
+ else {
+ fprintf(stderr, "aidl: internal error unkown document type %d.\n",
+ items->item_type);
+ return 1;
+ }
+ items = items->next;
+ }
+ return err;
+}
+
+// ==========================================================
+static const char*
+kind_to_string(int kind)
+{
+ switch (kind)
+ {
+ case Type::INTERFACE:
+ return "an interface";
+ case Type::USERDATA:
+ return "a user data";
+ default:
+ return "ERROR";
+ }
+}
+
+static char*
+rfind(char* str, char c)
+{
+ char* p = str + strlen(str) - 1;
+ while (p >= str) {
+ if (*p == c) {
+ return p;
+ }
+ p--;
+ }
+ return NULL;
+}
+
+static int
+gather_types(const char* filename, document_item_type* items)
+{
+ int err = 0;
+ while (items) {
+ Type* type;
+ if (items->item_type == USER_DATA_TYPE) {
+ user_data_type* p = (user_data_type*)items;
+ type = new UserDataType(p->package ? p->package : "", p->name.data,
+ false, ((p->flattening_methods & PARCELABLE_DATA) != 0),
+ ((p->flattening_methods & RPC_DATA) != 0), filename, p->name.lineno);
+ }
+ else if (items->item_type == INTERFACE_TYPE_BINDER
+ || items->item_type == INTERFACE_TYPE_RPC) {
+ interface_type* c = (interface_type*)items;
+ type = new InterfaceType(c->package ? c->package : "",
+ c->name.data, false, c->oneway,
+ filename, c->name.lineno);
+ }
+ else {
+ fprintf(stderr, "aidl: internal error %s:%d\n", __FILE__, __LINE__);
+ return 1;
+ }
+
+ Type* old = NAMES.Find(type->QualifiedName());
+ if (old == NULL) {
+ NAMES.Add(type);
+
+ if (items->item_type == INTERFACE_TYPE_BINDER) {
+ // for interfaces, also add the stub and proxy types, we don't
+ // bother checking these for duplicates, because the parser
+ // won't let us do it.
+ interface_type* c = (interface_type*)items;
+
+ string name = c->name.data;
+ name += ".Stub";
+ Type* stub = new Type(c->package ? c->package : "",
+ name, Type::GENERATED, false, false, false,
+ filename, c->name.lineno);
+ NAMES.Add(stub);
+
+ name = c->name.data;
+ name += ".Stub.Proxy";
+ Type* proxy = new Type(c->package ? c->package : "",
+ name, Type::GENERATED, false, false, false,
+ filename, c->name.lineno);
+ NAMES.Add(proxy);
+ }
+ else if (items->item_type == INTERFACE_TYPE_RPC) {
+ // for interfaces, also add the service base type, we don't
+ // bother checking these for duplicates, because the parser
+ // won't let us do it.
+ interface_type* c = (interface_type*)items;
+
+ string name = c->name.data;
+ name += ".ServiceBase";
+ Type* base = new Type(c->package ? c->package : "",
+ name, Type::GENERATED, false, false, false,
+ filename, c->name.lineno);
+ NAMES.Add(base);
+ }
+ } else {
+ if (old->Kind() == Type::BUILT_IN) {
+ fprintf(stderr, "%s:%d attempt to redefine built in class %s\n",
+ filename, type->DeclLine(),
+ type->QualifiedName().c_str());
+ err = 1;
+ }
+ else if (type->Kind() != old->Kind()) {
+ const char* oldKind = kind_to_string(old->Kind());
+ const char* newKind = kind_to_string(type->Kind());
+
+ fprintf(stderr, "%s:%d attempt to redefine %s as %s,\n",
+ filename, type->DeclLine(),
+ type->QualifiedName().c_str(), newKind);
+ fprintf(stderr, "%s:%d previously defined here as %s.\n",
+ old->DeclFile().c_str(), old->DeclLine(), oldKind);
+ err = 1;
+ }
+ }
+
+ items = items->next;
+ }
+ return err;
+}
+
+// ==========================================================
+static bool
+matches_keyword(const char* str)
+{
+ static const char* KEYWORDS[] = { "abstract", "assert", "boolean", "break",
+ "byte", "case", "catch", "char", "class", "const", "continue",
+ "default", "do", "double", "else", "enum", "extends", "final",
+ "finally", "float", "for", "goto", "if", "implements", "import",
+ "instanceof", "int", "interface", "long", "native", "new", "package",
+ "private", "protected", "public", "return", "short", "static",
+ "strictfp", "super", "switch", "synchronized", "this", "throw",
+ "throws", "transient", "try", "void", "volatile", "while",
+ "true", "false", "null",
+ NULL
+ };
+ const char** k = KEYWORDS;
+ while (*k) {
+ if (0 == strcmp(str, *k)) {
+ return true;
+ }
+ k++;
+ }
+ return false;
+}
+
+static int
+check_method(const char* filename, int kind, method_type* m)
+{
+ int err = 0;
+
+ // return type
+ Type* returnType = NAMES.Search(m->type.type.data);
+ if (returnType == NULL) {
+ fprintf(stderr, "%s:%d unknown return type %s\n", filename,
+ m->type.type.lineno, m->type.type.data);
+ err = 1;
+ return err;
+ }
+
+ if (returnType == EVENT_FAKE_TYPE) {
+ if (kind != INTERFACE_TYPE_RPC) {
+ fprintf(stderr, "%s:%d event methods only supported for rpc interfaces\n",
+ filename, m->type.type.lineno);
+ err = 1;
+ }
+ } else {
+ if (!(kind == INTERFACE_TYPE_BINDER ? returnType->CanWriteToParcel()
+ : returnType->CanWriteToRpcData())) {
+ fprintf(stderr, "%s:%d return type %s can't be marshalled.\n", filename,
+ m->type.type.lineno, m->type.type.data);
+ err = 1;
+ }
+ }
+
+ if (m->type.dimension > 0 && !returnType->CanBeArray()) {
+ fprintf(stderr, "%s:%d return type %s%s can't be an array.\n", filename,
+ m->type.array_token.lineno, m->type.type.data,
+ m->type.array_token.data);
+ err = 1;
+ }
+
+ if (m->type.dimension > 1) {
+ fprintf(stderr, "%s:%d return type %s%s only one"
+ " dimensional arrays are supported\n", filename,
+ m->type.array_token.lineno, m->type.type.data,
+ m->type.array_token.data);
+ err = 1;
+ }
+
+ int index = 1;
+
+ arg_type* arg = m->args;
+ while (arg) {
+ Type* t = NAMES.Search(arg->type.type.data);
+
+ // check the arg type
+ if (t == NULL) {
+ fprintf(stderr, "%s:%d parameter %s (%d) unknown type %s\n",
+ filename, m->type.type.lineno, arg->name.data, index,
+ arg->type.type.data);
+ err = 1;
+ goto next;
+ }
+
+ if (t == EVENT_FAKE_TYPE) {
+ fprintf(stderr, "%s:%d parameter %s (%d) event can not be used as a parameter %s\n",
+ filename, m->type.type.lineno, arg->name.data, index,
+ arg->type.type.data);
+ err = 1;
+ goto next;
+ }
+
+ if (!(kind == INTERFACE_TYPE_BINDER ? t->CanWriteToParcel() : t->CanWriteToRpcData())) {
+ fprintf(stderr, "%s:%d parameter %d: '%s %s' can't be marshalled.\n",
+ filename, m->type.type.lineno, index,
+ arg->type.type.data, arg->name.data);
+ err = 1;
+ }
+
+ if (returnType == EVENT_FAKE_TYPE
+ && convert_direction(arg->direction.data) != IN_PARAMETER) {
+ fprintf(stderr, "%s:%d parameter %d: '%s %s' All paremeters on events must be 'in'.\n",
+ filename, m->type.type.lineno, index,
+ arg->type.type.data, arg->name.data);
+ err = 1;
+ goto next;
+ }
+
+ if (arg->direction.data == NULL
+ && (arg->type.dimension != 0 || t->CanBeOutParameter())) {
+ fprintf(stderr, "%s:%d parameter %d: '%s %s' can be an out"
+ " parameter, so you must declare it as in,"
+ " out or inout.\n",
+ filename, m->type.type.lineno, index,
+ arg->type.type.data, arg->name.data);
+ err = 1;
+ }
+
+ if (convert_direction(arg->direction.data) != IN_PARAMETER
+ && !t->CanBeOutParameter()
+ && arg->type.dimension == 0) {
+ fprintf(stderr, "%s:%d parameter %d: '%s %s %s' can only be an in"
+ " parameter.\n",
+ filename, m->type.type.lineno, index,
+ arg->direction.data, arg->type.type.data,
+ arg->name.data);
+ err = 1;
+ }
+
+ if (arg->type.dimension > 0 && !t->CanBeArray()) {
+ fprintf(stderr, "%s:%d parameter %d: '%s %s%s %s' can't be an"
+ " array.\n", filename,
+ m->type.array_token.lineno, index, arg->direction.data,
+ arg->type.type.data, arg->type.array_token.data,
+ arg->name.data);
+ err = 1;
+ }
+
+ if (arg->type.dimension > 1) {
+ fprintf(stderr, "%s:%d parameter %d: '%s %s%s %s' only one"
+ " dimensional arrays are supported\n", filename,
+ m->type.array_token.lineno, index, arg->direction.data,
+ arg->type.type.data, arg->type.array_token.data,
+ arg->name.data);
+ err = 1;
+ }
+
+ // check that the name doesn't match a keyword
+ if (matches_keyword(arg->name.data)) {
+ fprintf(stderr, "%s:%d parameter %d %s is named the same as a"
+ " Java or aidl keyword\n",
+ filename, m->name.lineno, index, arg->name.data);
+ err = 1;
+ }
+
+next:
+ index++;
+ arg = arg->next;
+ }
+
+ return err;
+}
+
+static int
+check_types(const char* filename, document_item_type* items)
+{
+ int err = 0;
+ while (items) {
+ // (nothing to check for USER_DATA_TYPE)
+ if (items->item_type == INTERFACE_TYPE_BINDER
+ || items->item_type == INTERFACE_TYPE_RPC) {
+ map<string,method_type*> methodNames;
+ interface_type* c = (interface_type*)items;
+
+ interface_item_type* member = c->interface_items;
+ while (member) {
+ if (member->item_type == METHOD_TYPE) {
+ method_type* m = (method_type*)member;
+
+ err |= check_method(filename, items->item_type, m);
+
+ // prevent duplicate methods
+ if (methodNames.find(m->name.data) == methodNames.end()) {
+ methodNames[m->name.data] = m;
+ } else {
+ fprintf(stderr,"%s:%d attempt to redefine method %s,\n",
+ filename, m->name.lineno, m->name.data);
+ method_type* old = methodNames[m->name.data];
+ fprintf(stderr, "%s:%d previously defined here.\n",
+ filename, old->name.lineno);
+ err = 1;
+ }
+ }
+ member = member->next;
+ }
+ }
+
+ items = items->next;
+ }
+ return err;
+}
+
+// ==========================================================
+static int
+exactly_one_interface(const char* filename, const document_item_type* items, const Options& options,
+ bool* onlyParcelable)
+{
+ if (items == NULL) {
+ fprintf(stderr, "%s: file does not contain any interfaces\n",
+ filename);
+ return 1;
+ }
+
+ const document_item_type* next = items->next;
+ // Allow parcelables to skip the "one-only" rule.
+ if (items->next != NULL && next->item_type != USER_DATA_TYPE) {
+ int lineno = -1;
+ if (next->item_type == INTERFACE_TYPE_BINDER) {
+ lineno = ((interface_type*)next)->interface_token.lineno;
+ }
+ else if (next->item_type == INTERFACE_TYPE_RPC) {
+ lineno = ((interface_type*)next)->interface_token.lineno;
+ }
+ fprintf(stderr, "%s:%d aidl can only handle one interface per file\n",
+ filename, lineno);
+ return 1;
+ }
+
+ if (items->item_type == USER_DATA_TYPE) {
+ *onlyParcelable = true;
+ if (options.failOnParcelable) {
+ fprintf(stderr, "%s:%d aidl can only generate code for interfaces, not"
+ " parcelables or flattenables,\n", filename,
+ ((user_data_type*)items)->keyword_token.lineno);
+ fprintf(stderr, "%s:%d .aidl files that only declare parcelables or flattenables"
+ "may not go in the Makefile.\n", filename,
+ ((user_data_type*)items)->keyword_token.lineno);
+ return 1;
+ }
+ } else {
+ *onlyParcelable = false;
+ }
+
+ return 0;
+}
+
+// ==========================================================
+void
+generate_dep_file(const Options& options, const document_item_type* items)
+{
+ /* we open the file in binary mode to ensure that the same output is
+ * generated on all platforms !!
+ */
+ FILE* to = NULL;
+ if (options.autoDepFile) {
+ string fileName = options.outputFileName + ".d";
+ to = fopen(fileName.c_str(), "wb");
+ } else {
+ to = fopen(options.depFileName.c_str(), "wb");
+ }
+
+ if (to == NULL) {
+ return;
+ }
+
+ const char* slash = "\\";
+ import_info* import = g_imports;
+ if (import == NULL) {
+ slash = "";
+ }
+
+ if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
+ fprintf(to, "%s: \\\n", options.outputFileName.c_str());
+ } else {
+ // parcelable: there's no output file.
+ fprintf(to, " : \\\n");
+ }
+ fprintf(to, " %s %s\n", options.inputFileName.c_str(), slash);
+
+ while (import) {
+ if (import->next == NULL) {
+ slash = "";
+ }
+ if (import->filename) {
+ fprintf(to, " %s %s\n", import->filename, slash);
+ }
+ import = import->next;
+ }
+
+ fprintf(to, "\n");
+
+ // Output "<imported_file>: " so make won't fail if the imported file has
+ // been deleted, moved or renamed in incremental build.
+ import = g_imports;
+ while (import) {
+ if (import->filename) {
+ fprintf(to, "%s :\n", import->filename);
+ }
+ import = import->next;
+ }
+
+ fclose(to);
+}
+
+// ==========================================================
+static string
+generate_outputFileName2(const Options& options, const buffer_type& name, const char* package)
+{
+ string result;
+
+ // create the path to the destination folder based on the
+ // interface package name
+ result = options.outputBaseFolder;
+ result += OS_PATH_SEPARATOR;
+
+ string packageStr = package;
+ size_t len = packageStr.length();
+ for (size_t i=0; i<len; i++) {
+ if (packageStr[i] == '.') {
+ packageStr[i] = OS_PATH_SEPARATOR;
+ }
+ }
+
+ result += packageStr;
+
+ // add the filename by replacing the .aidl extension to .java
+ const char* p = strchr(name.data, '.');
+ len = p ? p-name.data : strlen(name.data);
+
+ result += OS_PATH_SEPARATOR;
+ result.append(name.data, len);
+ result += ".java";
+
+ return result;
+}
+
+// ==========================================================
+static string
+generate_outputFileName(const Options& options, const document_item_type* items)
+{
+ // items has already been checked to have only one interface.
+ if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
+ interface_type* type = (interface_type*)items;
+
+ return generate_outputFileName2(options, type->name, type->package);
+ } else if (items->item_type == USER_DATA_TYPE) {
+ user_data_type* type = (user_data_type*)items;
+ return generate_outputFileName2(options, type->name, type->package);
+ }
+
+ // I don't think we can come here, but safer than returning NULL.
+ string result;
+ return result;
+}
+
+
+
+// ==========================================================
+static void
+check_outputFilePath(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)
+{
+ int err;
+
+ FILE* f = fopen(filename.c_str(), "rb");
+ if (f == NULL) {
+ fprintf(stderr, "aidl: can't open preprocessed file: %s\n",
+ filename.c_str());
+ return 1;
+ }
+
+ int lineno = 1;
+ char line[1024];
+ char type[1024];
+ char fullname[1024];
+ while (fgets(line, sizeof(line), f)) {
+ // skip comments and empty lines
+ if (!line[0] || strncmp(line, "//", 2) == 0) {
+ continue;
+ }
+
+ sscanf(line, "%s %[^; \r\n\t];", type, fullname);
+
+ char* packagename;
+ char* classname = rfind(fullname, '.');
+ if (classname != NULL) {
+ *classname = '\0';
+ classname++;
+ packagename = fullname;
+ } else {
+ classname = fullname;
+ packagename = NULL;
+ }
+
+ //printf("%s:%d:...%s...%s...%s...\n", filename.c_str(), lineno,
+ // type, packagename, classname);
+ document_item_type* doc;
+
+ if (0 == strcmp("parcelable", type)) {
+ user_data_type* parcl = (user_data_type*)malloc(
+ sizeof(user_data_type));
+ memset(parcl, 0, sizeof(user_data_type));
+ parcl->document_item.item_type = USER_DATA_TYPE;
+ parcl->keyword_token.lineno = lineno;
+ parcl->keyword_token.data = strdup(type);
+ parcl->package = packagename ? strdup(packagename) : NULL;
+ parcl->name.lineno = lineno;
+ parcl->name.data = strdup(classname);
+ parcl->semicolon_token.lineno = lineno;
+ parcl->semicolon_token.data = strdup(";");
+ parcl->flattening_methods = PARCELABLE_DATA;
+ doc = (document_item_type*)parcl;
+ }
+ else if (0 == strcmp("flattenable", type)) {
+ user_data_type* parcl = (user_data_type*)malloc(
+ sizeof(user_data_type));
+ memset(parcl, 0, sizeof(user_data_type));
+ parcl->document_item.item_type = USER_DATA_TYPE;
+ parcl->keyword_token.lineno = lineno;
+ parcl->keyword_token.data = strdup(type);
+ parcl->package = packagename ? strdup(packagename) : NULL;
+ parcl->name.lineno = lineno;
+ parcl->name.data = strdup(classname);
+ parcl->semicolon_token.lineno = lineno;
+ parcl->semicolon_token.data = strdup(";");
+ parcl->flattening_methods = RPC_DATA;
+ doc = (document_item_type*)parcl;
+ }
+ else if (0 == strcmp("interface", type)) {
+ interface_type* iface = (interface_type*)malloc(
+ sizeof(interface_type));
+ memset(iface, 0, sizeof(interface_type));
+ iface->document_item.item_type = INTERFACE_TYPE_BINDER;
+ iface->interface_token.lineno = lineno;
+ iface->interface_token.data = strdup(type);
+ iface->package = packagename ? strdup(packagename) : NULL;
+ iface->name.lineno = lineno;
+ iface->name.data = strdup(classname);
+ iface->open_brace_token.lineno = lineno;
+ iface->open_brace_token.data = strdup("{");
+ iface->close_brace_token.lineno = lineno;
+ iface->close_brace_token.data = strdup("}");
+ doc = (document_item_type*)iface;
+ }
+ else {
+ fprintf(stderr, "%s:%d: bad type in line: %s\n",
+ filename.c_str(), lineno, line);
+ fclose(f);
+ return 1;
+ }
+ err = gather_types(filename.c_str(), doc);
+ lineno++;
+ }
+
+ if (!feof(f)) {
+ fprintf(stderr, "%s:%d: error reading file, line to long.\n",
+ filename.c_str(), lineno);
+ return 1;
+ }
+
+ fclose(f);
+ return 0;
+}
+
+static int
+check_and_assign_method_ids(const char * filename, interface_item_type* first_item)
+{
+ // Check whether there are any methods with manually assigned id's and any that are not.
+ // Either all method id's must be manually assigned or all of them must not.
+ // Also, check for duplicates of user set id's and that the id's are within the proper bounds.
+ set<int> usedIds;
+ interface_item_type* item = first_item;
+ bool hasUnassignedIds = false;
+ bool hasAssignedIds = false;
+ while (item != NULL) {
+ if (item->item_type == METHOD_TYPE) {
+ method_type* method_item = (method_type*)item;
+ if (method_item->hasId) {
+ hasAssignedIds = true;
+ method_item->assigned_id = atoi(method_item->id.data);
+ // Ensure that the user set id is not duplicated.
+ if (usedIds.find(method_item->assigned_id) != usedIds.end()) {
+ // We found a duplicate id, so throw an error.
+ fprintf(stderr,
+ "%s:%d Found duplicate method id (%d) for method: %s\n",
+ filename, method_item->id.lineno,
+ method_item->assigned_id, method_item->name.data);
+ return 1;
+ }
+ // Ensure that the user set id is within the appropriate limits
+ if (method_item->assigned_id < MIN_USER_SET_METHOD_ID ||
+ method_item->assigned_id > MAX_USER_SET_METHOD_ID) {
+ fprintf(stderr, "%s:%d Found out of bounds id (%d) for method: %s\n",
+ filename, method_item->id.lineno,
+ method_item->assigned_id, method_item->name.data);
+ fprintf(stderr, " Value for id must be between %d and %d inclusive.\n",
+ MIN_USER_SET_METHOD_ID, MAX_USER_SET_METHOD_ID);
+ return 1;
+ }
+ usedIds.insert(method_item->assigned_id);
+ } else {
+ hasUnassignedIds = true;
+ }
+ if (hasAssignedIds && hasUnassignedIds) {
+ fprintf(stderr,
+ "%s: You must either assign id's to all methods or to none of them.\n",
+ filename);
+ return 1;
+ }
+ }
+ item = item->next;
+ }
+
+ // In the case that all methods have unassigned id's, set a unique id for them.
+ if (hasUnassignedIds) {
+ int newId = 0;
+ item = first_item;
+ while (item != NULL) {
+ if (item->item_type == METHOD_TYPE) {
+ method_type* method_item = (method_type*)item;
+ method_item->assigned_id = newId++;
+ }
+ item = item->next;
+ }
+ }
+
+ // success
+ return 0;
+}
+
+// ==========================================================
+static int
+compile_aidl(Options& options)
+{
+ int err = 0, N;
+
+ set_import_paths(options.importPaths);
+
+ register_base_types();
+
+ // import the preprocessed file
+ N = options.preprocessedFiles.size();
+ for (int i=0; i<N; i++) {
+ const string& s = options.preprocessedFiles[i];
+ err |= parse_preprocessed_file(s);
+ }
+ if (err != 0) {
+ return err;
+ }
+
+ // parse the main file
+ g_callbacks = &g_mainCallbacks;
+ err = parse_aidl(options.inputFileName.c_str());
+ document_item_type* mainDoc = g_document;
+ g_document = NULL;
+
+ // parse the imports
+ g_callbacks = &g_mainCallbacks;
+ import_info* import = g_imports;
+ while (import) {
+ if (NAMES.Find(import->neededClass) == NULL) {
+ import->filename = find_import_file(import->neededClass);
+ if (!import->filename) {
+ fprintf(stderr, "%s:%d: couldn't find import for class %s\n",
+ import->from, import->statement.lineno,
+ import->neededClass);
+ err |= 1;
+ } else {
+ err |= parse_aidl(import->filename);
+ import->doc = g_document;
+ if (import->doc == NULL) {
+ err |= 1;
+ }
+ }
+ }
+ import = import->next;
+ }
+ // bail out now if parsing wasn't successful
+ if (err != 0 || mainDoc == NULL) {
+ //fprintf(stderr, "aidl: parsing failed, stopping.\n");
+ return 1;
+ }
+
+ // complain about ones that aren't in the right files
+ err |= check_filenames(options.inputFileName.c_str(), mainDoc);
+ import = g_imports;
+ while (import) {
+ err |= check_filenames(import->filename, import->doc);
+ import = import->next;
+ }
+
+ // gather the types that have been declared
+ err |= gather_types(options.inputFileName.c_str(), mainDoc);
+ import = g_imports;
+ while (import) {
+ err |= gather_types(import->filename, import->doc);
+ import = import->next;
+ }
+
+#if 0
+ printf("---- main doc ----\n");
+ test_document(mainDoc);
+
+ import = g_imports;
+ while (import) {
+ printf("---- import doc ----\n");
+ test_document(import->doc);
+ import = import->next;
+ }
+ NAMES.Dump();
+#endif
+
+ // check the referenced types in mainDoc to make sure we've imported them
+ err |= check_types(options.inputFileName.c_str(), mainDoc);
+
+ // finally, there really only needs to be one thing in mainDoc, and it
+ // needs to be an interface.
+ bool onlyParcelable = false;
+ err |= exactly_one_interface(options.inputFileName.c_str(), mainDoc, options, &onlyParcelable);
+
+ // If this includes an interface definition, then assign method ids and validate.
+ if (!onlyParcelable) {
+ err |= check_and_assign_method_ids(options.inputFileName.c_str(),
+ ((interface_type*)mainDoc)->interface_items);
+ }
+
+ // after this, there shouldn't be any more errors because of the
+ // input.
+ if (err != 0 || mainDoc == NULL) {
+ return 1;
+ }
+
+ // if needed, generate the outputFileName from the outputBaseFolder
+ if (options.outputFileName.length() == 0 &&
+ options.outputBaseFolder.length() > 0) {
+ options.outputFileName = generate_outputFileName(options, mainDoc);
+ }
+
+ // if we were asked to, generate a make dependency file
+ // unless it's a parcelable *and* it's supposed to fail on parcelable
+ if ((options.autoDepFile || options.depFileName != "") &&
+ !(onlyParcelable && options.failOnParcelable)) {
+ // make sure the folders of the output file all exists
+ check_outputFilePath(options.outputFileName);
+ generate_dep_file(options, mainDoc);
+ }
+
+ // they didn't ask to fail on parcelables, so just exit quietly.
+ if (onlyParcelable && !options.failOnParcelable) {
+ return 0;
+ }
+
+ // make sure the folders of the output file all exists
+ check_outputFilePath(options.outputFileName);
+
+ err = generate_java(options.outputFileName, options.inputFileName.c_str(),
+ (interface_type*)mainDoc);
+
+ return err;
+}
+
+static int
+preprocess_aidl(const Options& options)
+{
+ vector<string> lines;
+ int err;
+
+ // read files
+ int N = options.filesToPreprocess.size();
+ for (int i=0; i<N; i++) {
+ g_callbacks = &g_mainCallbacks;
+ err = parse_aidl(options.filesToPreprocess[i].c_str());
+ if (err != 0) {
+ return err;
+ }
+ document_item_type* doc = g_document;
+ string line;
+ if (doc->item_type == USER_DATA_TYPE) {
+ user_data_type* parcelable = (user_data_type*)doc;
+ if ((parcelable->flattening_methods & PARCELABLE_DATA) != 0) {
+ line = "parcelable ";
+ }
+ if ((parcelable->flattening_methods & RPC_DATA) != 0) {
+ line = "flattenable ";
+ }
+ if (parcelable->package) {
+ line += parcelable->package;
+ line += '.';
+ }
+ line += parcelable->name.data;
+ } else {
+ line = "interface ";
+ interface_type* iface = (interface_type*)doc;
+ if (iface->package) {
+ line += iface->package;
+ line += '.';
+ }
+ line += iface->name.data;
+ }
+ line += ";\n";
+ lines.push_back(line);
+ }
+
+ // write preprocessed file
+ int fd = open( options.outputFileName.c_str(),
+ O_RDWR|O_CREAT|O_TRUNC|O_BINARY,
+#ifdef HAVE_MS_C_RUNTIME
+ _S_IREAD|_S_IWRITE);
+#else
+ S_IRUSR|S_IWUSR|S_IRGRP);
+#endif
+ if (fd == -1) {
+ fprintf(stderr, "aidl: could not open file for write: %s\n",
+ options.outputFileName.c_str());
+ return 1;
+ }
+
+ N = lines.size();
+ for (int i=0; i<N; i++) {
+ const string& s = lines[i];
+ int len = s.length();
+ if (len != write(fd, s.c_str(), len)) {
+ fprintf(stderr, "aidl: error writing to file %s\n",
+ options.outputFileName.c_str());
+ close(fd);
+ unlink(options.outputFileName.c_str());
+ return 1;
+ }
+ }
+
+ close(fd);
+ return 0;
+}
+
+// ==========================================================
+int
+main(int argc, const char **argv)
+{
+ Options options;
+ int result = parse_options(argc, argv, &options);
+ if (result) {
+ return result;
+ }
+
+ switch (options.task)
+ {
+ case COMPILE_AIDL:
+ return compile_aidl(options);
+ case PREPROCESS_AIDL:
+ return preprocess_aidl(options);
+ }
+ fprintf(stderr, "aidl: internal error\n");
+ return 1;
+}
diff --git a/tools/aidl/aidl_language.cpp b/tools/aidl/aidl_language.cpp
new file mode 100644
index 0000000..cd6a3bd
--- /dev/null
+++ b/tools/aidl/aidl_language.cpp
@@ -0,0 +1,20 @@
+#include "aidl_language.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_MS_C_RUNTIME
+int isatty(int fd)
+{
+ return (fd == 0);
+}
+#endif
+
+#if 0
+ParserCallbacks k_parserCallbacks = {
+ NULL
+};
+#endif
+
+ParserCallbacks* g_callbacks = NULL; // &k_parserCallbacks;
+
diff --git a/tools/aidl/aidl_language.h b/tools/aidl/aidl_language.h
new file mode 100644
index 0000000..de1370c
--- /dev/null
+++ b/tools/aidl/aidl_language.h
@@ -0,0 +1,172 @@
+#ifndef DEVICE_TOOLS_AIDL_AIDL_LANGUAGE_H
+#define DEVICE_TOOLS_AIDL_AIDL_LANGUAGE_H
+
+
+typedef enum {
+ NO_EXTRA_TEXT = 0,
+ SHORT_COMMENT,
+ LONG_COMMENT,
+ COPY_TEXT,
+ WHITESPACE
+} which_extra_text;
+
+typedef struct extra_text_type {
+ unsigned lineno;
+ which_extra_text which;
+ char* data;
+ unsigned len;
+ struct extra_text_type* next;
+} extra_text_type;
+
+typedef struct buffer_type {
+ unsigned lineno;
+ unsigned token;
+ char *data;
+ extra_text_type* extra;
+} buffer_type;
+
+typedef struct type_type {
+ buffer_type type;
+ buffer_type array_token;
+ int dimension;
+} type_type;
+
+typedef struct arg_type {
+ buffer_type comma_token; // empty in the first one in the list
+ buffer_type direction;
+ type_type type;
+ buffer_type name;
+ struct arg_type *next;
+} arg_type;
+
+enum {
+ METHOD_TYPE
+};
+
+typedef struct interface_item_type {
+ unsigned item_type;
+ struct interface_item_type* next;
+} interface_item_type;
+
+typedef struct method_type {
+ interface_item_type interface_item;
+ type_type type;
+ bool oneway;
+ buffer_type oneway_token;
+ buffer_type name;
+ buffer_type open_paren_token;
+ arg_type* args;
+ buffer_type close_paren_token;
+ bool hasId;
+ buffer_type equals_token;
+ buffer_type id;
+ // XXX missing comments/copy text here
+ buffer_type semicolon_token;
+ buffer_type* comments_token; // points into this structure, DO NOT DELETE
+ int assigned_id;
+} method_type;
+
+enum {
+ USER_DATA_TYPE = 12,
+ INTERFACE_TYPE_BINDER,
+ INTERFACE_TYPE_RPC
+};
+
+typedef struct document_item_type {
+ unsigned item_type;
+ struct document_item_type* next;
+} document_item_type;
+
+
+// for user_data_type.flattening_methods
+enum {
+ PARCELABLE_DATA = 0x1,
+ RPC_DATA = 0x2
+};
+
+typedef struct user_data_type {
+ document_item_type document_item;
+ buffer_type keyword_token; // only the first one
+ char* package;
+ buffer_type name;
+ buffer_type semicolon_token;
+ int flattening_methods;
+} user_data_type;
+
+typedef struct interface_type {
+ document_item_type document_item;
+ buffer_type interface_token;
+ bool oneway;
+ buffer_type oneway_token;
+ char* package;
+ buffer_type name;
+ buffer_type open_brace_token;
+ interface_item_type* interface_items;
+ buffer_type close_brace_token;
+ buffer_type* comments_token; // points into this structure, DO NOT DELETE
+} interface_type;
+
+typedef union lexer_type {
+ buffer_type buffer;
+ type_type type;
+ arg_type *arg;
+ method_type* method;
+ interface_item_type* interface_item;
+ interface_type* interface_obj;
+ user_data_type* user_data;
+ document_item_type* document_item;
+} lexer_type;
+
+
+#define YYSTYPE lexer_type
+
+#if __cplusplus
+extern "C" {
+#endif
+
+int parse_aidl(char const *);
+
+// strips off the leading whitespace, the "import" text
+// also returns whether it's a local or system import
+// we rely on the input matching the import regex from below
+char* parse_import_statement(const char* text);
+
+// in, out or inout
+enum {
+ IN_PARAMETER = 1,
+ OUT_PARAMETER = 2,
+ INOUT_PARAMETER = 3
+};
+int convert_direction(const char* direction);
+
+// callbacks from within the parser
+// these functions all take ownership of the strings
+typedef struct ParserCallbacks {
+ void (*document)(document_item_type* items);
+ void (*import)(buffer_type* statement);
+} ParserCallbacks;
+
+extern ParserCallbacks* g_callbacks;
+
+// true if there was an error parsing, false otherwise
+extern int g_error;
+
+// the name of the file we're currently parsing
+extern char const* g_currentFilename;
+
+// the package name for our current file
+extern char const* g_currentPackage;
+
+typedef enum {
+ STATEMENT_INSIDE_INTERFACE
+} error_type;
+
+void init_buffer_type(buffer_type* buf, int lineno);
+
+
+#if __cplusplus
+}
+#endif
+
+
+#endif // DEVICE_TOOLS_AIDL_AIDL_LANGUAGE_H
diff --git a/tools/aidl/aidl_language_l.l b/tools/aidl/aidl_language_l.l
new file mode 100644
index 0000000..3d33e7a
--- /dev/null
+++ b/tools/aidl/aidl_language_l.l
@@ -0,0 +1,214 @@
+%{
+#include "aidl_language.h"
+#include "aidl_language_y.h"
+#include "search_path.h"
+#include <string.h>
+#include <stdlib.h>
+
+extern YYSTYPE yylval;
+
+// comment and whitespace handling
+// these functions save a copy of the buffer
+static void begin_extra_text(unsigned lineno, which_extra_text which);
+static void append_extra_text(char* text);
+static extra_text_type* get_extra_text(void); // you now own the object
+ // this returns
+static void drop_extra_text(void);
+
+// package handling
+static void do_package_statement(const char* importText);
+
+#define SET_BUFFER(t) \
+ do { \
+ yylval.buffer.lineno = yylineno; \
+ yylval.buffer.token = (t); \
+ yylval.buffer.data = strdup(yytext); \
+ yylval.buffer.extra = get_extra_text(); \
+ } while(0)
+
+%}
+
+%option yylineno
+%option noyywrap
+
+%x COPYING LONG_COMMENT
+
+identifier [_a-zA-Z][_a-zA-Z0-9\.]*
+whitespace ([ \t\n\r]+)
+brackets \[{whitespace}?\]
+idvalue (0|[1-9][0-9]*)
+
+%%
+
+
+\%\%\{ { begin_extra_text(yylineno, COPY_TEXT); BEGIN(COPYING); }
+<COPYING>\}\%\% { BEGIN(INITIAL); }
+<COPYING>.*\n { append_extra_text(yytext); }
+<COPYING>.* { append_extra_text(yytext); }
+<COPYING>\n+ { append_extra_text(yytext); }
+
+
+\/\* { begin_extra_text(yylineno, (which_extra_text)LONG_COMMENT);
+ BEGIN(LONG_COMMENT); }
+<LONG_COMMENT>[^*]* { append_extra_text(yytext); }
+<LONG_COMMENT>\*+[^/] { append_extra_text(yytext); }
+<LONG_COMMENT>\n { append_extra_text(yytext); }
+<LONG_COMMENT>\**\/ { BEGIN(INITIAL); }
+
+^{whitespace}?import{whitespace}[^ \t\r\n]+{whitespace}?; {
+ SET_BUFFER(IMPORT);
+ return IMPORT;
+ }
+^{whitespace}?package{whitespace}[^ \t\r\n]+{whitespace}?; {
+ do_package_statement(yytext);
+ SET_BUFFER(PACKAGE);
+ return PACKAGE;
+ }
+<<EOF>> { yyterminate(); }
+
+\/\/.*\n { begin_extra_text(yylineno, SHORT_COMMENT);
+ append_extra_text(yytext); }
+
+{whitespace} { /* begin_extra_text(yylineno, WHITESPACE);
+ append_extra_text(yytext); */ }
+
+; { SET_BUFFER(';'); return ';'; }
+\{ { SET_BUFFER('{'); return '{'; }
+\} { SET_BUFFER('}'); return '}'; }
+\( { SET_BUFFER('('); return '('; }
+\) { SET_BUFFER(')'); return ')'; }
+, { SET_BUFFER(','); return ','; }
+= { SET_BUFFER('='); return '='; }
+
+ /* keywords */
+parcelable { SET_BUFFER(PARCELABLE); return PARCELABLE; }
+interface { SET_BUFFER(INTERFACE); return INTERFACE; }
+flattenable { SET_BUFFER(FLATTENABLE); return FLATTENABLE; }
+rpc { SET_BUFFER(INTERFACE); return RPC; }
+in { SET_BUFFER(IN); return IN; }
+out { SET_BUFFER(OUT); return OUT; }
+inout { SET_BUFFER(INOUT); return INOUT; }
+oneway { SET_BUFFER(ONEWAY); return ONEWAY; }
+
+{brackets}+ { SET_BUFFER(ARRAY); return ARRAY; }
+{idvalue} { SET_BUFFER(IDVALUE); return IDVALUE; }
+{identifier} { SET_BUFFER(IDENTIFIER); return IDENTIFIER; }
+{identifier}\<{whitespace}*{identifier}({whitespace}*,{whitespace}*{identifier})*{whitespace}*\> {
+ SET_BUFFER(GENERIC); return GENERIC; }
+
+ /* syntax error! */
+. { printf("UNKNOWN(%s)", yytext);
+ yylval.buffer.lineno = yylineno;
+ yylval.buffer.token = IDENTIFIER;
+ yylval.buffer.data = strdup(yytext);
+ return IDENTIFIER;
+ }
+
+%%
+
+// comment and whitespace handling
+// ================================================
+extra_text_type* g_extraText = NULL;
+extra_text_type* g_nextExtraText = NULL;
+
+void begin_extra_text(unsigned lineno, which_extra_text which)
+{
+ extra_text_type* text = (extra_text_type*)malloc(sizeof(extra_text_type));
+ text->lineno = lineno;
+ text->which = which;
+ text->data = NULL;
+ text->len = 0;
+ text->next = NULL;
+ if (g_nextExtraText == NULL) {
+ g_extraText = text;
+ } else {
+ g_nextExtraText->next = text;
+ }
+ g_nextExtraText = text;
+}
+
+void append_extra_text(char* text)
+{
+ if (g_nextExtraText->data == NULL) {
+ g_nextExtraText->data = strdup(text);
+ g_nextExtraText->len = strlen(text);
+ } else {
+ char* orig = g_nextExtraText->data;
+ unsigned oldLen = g_nextExtraText->len;
+ unsigned len = strlen(text);
+ g_nextExtraText->len += len;
+ g_nextExtraText->data = (char*)malloc(g_nextExtraText->len+1);
+ memcpy(g_nextExtraText->data, orig, oldLen);
+ memcpy(g_nextExtraText->data+oldLen, text, len);
+ g_nextExtraText->data[g_nextExtraText->len] = '\0';
+ free(orig);
+ }
+}
+
+extra_text_type*
+get_extra_text(void)
+{
+ extra_text_type* result = g_extraText;
+ g_extraText = NULL;
+ g_nextExtraText = NULL;
+ return result;
+}
+
+void drop_extra_text(void)
+{
+ extra_text_type* p = g_extraText;
+ while (p) {
+ extra_text_type* next = p->next;
+ free(p->data);
+ free(p);
+ free(next);
+ }
+ g_extraText = NULL;
+ g_nextExtraText = NULL;
+}
+
+
+// package handling
+// ================================================
+void do_package_statement(const char* importText)
+{
+ if (g_currentPackage) free((void*)g_currentPackage);
+ g_currentPackage = parse_import_statement(importText);
+}
+
+
+// main parse function
+// ================================================
+char const* g_currentFilename = NULL;
+char const* g_currentPackage = NULL;
+
+int yyparse(void);
+
+int parse_aidl(char const *filename)
+{
+ yyin = fopen(filename, "r");
+ if (yyin) {
+ char const* oldFilename = g_currentFilename;
+ char const* oldPackage = g_currentPackage;
+ g_currentFilename = strdup(filename);
+
+ g_error = 0;
+ yylineno = 1;
+ int rv = yyparse();
+ if (g_error != 0) {
+ rv = g_error;
+ }
+
+ free((void*)g_currentFilename);
+ g_currentFilename = oldFilename;
+
+ if (g_currentPackage) free((void*)g_currentPackage);
+ g_currentPackage = oldPackage;
+
+ return rv;
+ } else {
+ fprintf(stderr, "aidl: unable to open file for read: %s\n", filename);
+ return 1;
+ }
+}
+
diff --git a/tools/aidl/aidl_language_y.y b/tools/aidl/aidl_language_y.y
new file mode 100644
index 0000000..9b40d28
--- /dev/null
+++ b/tools/aidl/aidl_language_y.y
@@ -0,0 +1,373 @@
+%{
+#include "aidl_language.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int yyerror(char* errstr);
+int yylex(void);
+extern int yylineno;
+
+static int count_brackets(const char*);
+
+%}
+
+%token IMPORT
+%token PACKAGE
+%token IDENTIFIER
+%token IDVALUE
+%token GENERIC
+%token ARRAY
+%token PARCELABLE
+%token INTERFACE
+%token FLATTENABLE
+%token RPC
+%token IN
+%token OUT
+%token INOUT
+%token ONEWAY
+
+%%
+document:
+ document_items { g_callbacks->document($1.document_item); }
+ | headers document_items { g_callbacks->document($2.document_item); }
+ ;
+
+headers:
+ package { }
+ | imports { }
+ | package imports { }
+ ;
+
+package:
+ PACKAGE { }
+ ;
+
+imports:
+ IMPORT { g_callbacks->import(&($1.buffer)); }
+ | IMPORT imports { g_callbacks->import(&($1.buffer)); }
+ ;
+
+document_items:
+ { $$.document_item = NULL; }
+ | document_items declaration {
+ if ($2.document_item == NULL) {
+ // error cases only
+ $$ = $1;
+ } else {
+ document_item_type* p = $1.document_item;
+ while (p && p->next) {
+ p=p->next;
+ }
+ if (p) {
+ p->next = (document_item_type*)$2.document_item;
+ $$ = $1;
+ } else {
+ $$.document_item = (document_item_type*)$2.document_item;
+ }
+ }
+ }
+ | document_items error {
+ fprintf(stderr, "%s:%d: syntax error don't know what to do with \"%s\"\n", g_currentFilename,
+ $2.buffer.lineno, $2.buffer.data);
+ $$ = $1;
+ }
+ ;
+
+declaration:
+ parcelable_decl { $$.document_item = (document_item_type*)$1.user_data; }
+ | interface_decl { $$.document_item = (document_item_type*)$1.interface_item; }
+ ;
+
+parcelable_decl:
+ PARCELABLE IDENTIFIER ';' {
+ user_data_type* b = (user_data_type*)malloc(sizeof(user_data_type));
+ b->document_item.item_type = USER_DATA_TYPE;
+ b->document_item.next = NULL;
+ b->keyword_token = $1.buffer;
+ b->name = $2.buffer;
+ b->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
+ b->semicolon_token = $3.buffer;
+ b->flattening_methods = PARCELABLE_DATA;
+ $$.user_data = b;
+ }
+ | PARCELABLE ';' {
+ fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name.\n",
+ g_currentFilename, $1.buffer.lineno);
+ $$.user_data = NULL;
+ }
+ | PARCELABLE error ';' {
+ fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name, saw \"%s\".\n",
+ g_currentFilename, $2.buffer.lineno, $2.buffer.data);
+ $$.user_data = NULL;
+ }
+ | FLATTENABLE IDENTIFIER ';' {
+ user_data_type* b = (user_data_type*)malloc(sizeof(user_data_type));
+ b->document_item.item_type = USER_DATA_TYPE;
+ b->document_item.next = NULL;
+ b->keyword_token = $1.buffer;
+ b->name = $2.buffer;
+ b->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
+ b->semicolon_token = $3.buffer;
+ b->flattening_methods = PARCELABLE_DATA | RPC_DATA;
+ $$.user_data = b;
+ }
+ | FLATTENABLE ';' {
+ fprintf(stderr, "%s:%d syntax error in flattenable declaration. Expected type name.\n",
+ g_currentFilename, $1.buffer.lineno);
+ $$.user_data = NULL;
+ }
+ | FLATTENABLE error ';' {
+ fprintf(stderr, "%s:%d syntax error in flattenable declaration. Expected type name, saw \"%s\".\n",
+ g_currentFilename, $2.buffer.lineno, $2.buffer.data);
+ $$.user_data = NULL;
+ }
+
+ ;
+
+interface_header:
+ INTERFACE {
+ interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+ c->document_item.item_type = INTERFACE_TYPE_BINDER;
+ c->document_item.next = NULL;
+ c->interface_token = $1.buffer;
+ c->oneway = false;
+ memset(&c->oneway_token, 0, sizeof(buffer_type));
+ c->comments_token = &c->interface_token;
+ $$.interface_obj = c;
+ }
+ | ONEWAY INTERFACE {
+ interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+ c->document_item.item_type = INTERFACE_TYPE_BINDER;
+ c->document_item.next = NULL;
+ c->interface_token = $2.buffer;
+ c->oneway = true;
+ c->oneway_token = $1.buffer;
+ c->comments_token = &c->oneway_token;
+ $$.interface_obj = c;
+ }
+ | RPC {
+ interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+ c->document_item.item_type = INTERFACE_TYPE_RPC;
+ c->document_item.next = NULL;
+ c->interface_token = $1.buffer;
+ c->oneway = false;
+ memset(&c->oneway_token, 0, sizeof(buffer_type));
+ c->comments_token = &c->interface_token;
+ $$.interface_obj = c;
+ }
+ ;
+
+interface_keywords:
+ INTERFACE
+ | RPC
+ ;
+
+interface_decl:
+ interface_header IDENTIFIER '{' interface_items '}' {
+ interface_type* c = $1.interface_obj;
+ c->name = $2.buffer;
+ c->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
+ c->open_brace_token = $3.buffer;
+ c->interface_items = $4.interface_item;
+ c->close_brace_token = $5.buffer;
+ $$.interface_obj = c;
+ }
+ | interface_keywords error '{' interface_items '}' {
+ fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n",
+ g_currentFilename, $2.buffer.lineno, $2.buffer.data);
+ $$.document_item = NULL;
+ }
+ | interface_keywords error '}' {
+ fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n",
+ g_currentFilename, $2.buffer.lineno, $2.buffer.data);
+ $$.document_item = NULL;
+ }
+
+ ;
+
+interface_items:
+ { $$.interface_item = NULL; }
+ | interface_items method_decl {
+ interface_item_type* p=$1.interface_item;
+ while (p && p->next) {
+ p=p->next;
+ }
+ if (p) {
+ p->next = (interface_item_type*)$2.method;
+ $$ = $1;
+ } else {
+ $$.interface_item = (interface_item_type*)$2.method;
+ }
+ }
+ | interface_items error ';' {
+ fprintf(stderr, "%s:%d: syntax error before ';' (expected method declaration)\n",
+ g_currentFilename, $3.buffer.lineno);
+ $$ = $1;
+ }
+ ;
+
+method_decl:
+ type IDENTIFIER '(' arg_list ')' ';' {
+ method_type *method = (method_type*)malloc(sizeof(method_type));
+ method->interface_item.item_type = METHOD_TYPE;
+ method->interface_item.next = NULL;
+ method->oneway = false;
+ method->type = $1.type;
+ memset(&method->oneway_token, 0, sizeof(buffer_type));
+ method->name = $2.buffer;
+ method->open_paren_token = $3.buffer;
+ method->args = $4.arg;
+ method->close_paren_token = $5.buffer;
+ method->hasId = false;
+ memset(&method->equals_token, 0, sizeof(buffer_type));
+ memset(&method->id, 0, sizeof(buffer_type));
+ method->semicolon_token = $6.buffer;
+ method->comments_token = &method->type.type;
+ $$.method = method;
+ }
+ | ONEWAY type IDENTIFIER '(' arg_list ')' ';' {
+ method_type *method = (method_type*)malloc(sizeof(method_type));
+ method->interface_item.item_type = METHOD_TYPE;
+ method->interface_item.next = NULL;
+ method->oneway = true;
+ method->oneway_token = $1.buffer;
+ method->type = $2.type;
+ method->name = $3.buffer;
+ method->open_paren_token = $4.buffer;
+ method->args = $5.arg;
+ method->close_paren_token = $6.buffer;
+ method->hasId = false;
+ memset(&method->equals_token, 0, sizeof(buffer_type));
+ memset(&method->id, 0, sizeof(buffer_type));
+ method->semicolon_token = $7.buffer;
+ method->comments_token = &method->oneway_token;
+ $$.method = method;
+ }
+ | type IDENTIFIER '(' arg_list ')' '=' IDVALUE ';' {
+ method_type *method = (method_type*)malloc(sizeof(method_type));
+ method->interface_item.item_type = METHOD_TYPE;
+ method->interface_item.next = NULL;
+ method->oneway = false;
+ memset(&method->oneway_token, 0, sizeof(buffer_type));
+ method->type = $1.type;
+ method->name = $2.buffer;
+ method->open_paren_token = $3.buffer;
+ method->args = $4.arg;
+ method->close_paren_token = $5.buffer;
+ method->hasId = true;
+ method->equals_token = $6.buffer;
+ method->id = $7.buffer;
+ method->semicolon_token = $8.buffer;
+ method->comments_token = &method->type.type;
+ $$.method = method;
+ }
+ | ONEWAY type IDENTIFIER '(' arg_list ')' '=' IDVALUE ';' {
+ method_type *method = (method_type*)malloc(sizeof(method_type));
+ method->interface_item.item_type = METHOD_TYPE;
+ method->interface_item.next = NULL;
+ method->oneway = true;
+ method->oneway_token = $1.buffer;
+ method->type = $2.type;
+ method->name = $3.buffer;
+ method->open_paren_token = $4.buffer;
+ method->args = $5.arg;
+ method->close_paren_token = $6.buffer;
+ method->hasId = true;
+ method->equals_token = $7.buffer;
+ method->id = $8.buffer;
+ method->semicolon_token = $9.buffer;
+ method->comments_token = &method->oneway_token;
+ $$.method = method;
+ }
+ ;
+
+arg_list:
+ { $$.arg = NULL; }
+ | arg { $$ = $1; }
+ | arg_list ',' arg {
+ if ($$.arg != NULL) {
+ // only NULL on error
+ $$ = $1;
+ arg_type *p = $1.arg;
+ while (p && p->next) {
+ p=p->next;
+ }
+ $3.arg->comma_token = $2.buffer;
+ p->next = $3.arg;
+ }
+ }
+ | error {
+ fprintf(stderr, "%s:%d: syntax error in parameter list\n", g_currentFilename, $1.buffer.lineno);
+ $$.arg = NULL;
+ }
+ ;
+
+arg:
+ direction type IDENTIFIER {
+ arg_type* arg = (arg_type*)malloc(sizeof(arg_type));
+ memset(&arg->comma_token, 0, sizeof(buffer_type));
+ arg->direction = $1.buffer;
+ arg->type = $2.type;
+ arg->name = $3.buffer;
+ arg->next = NULL;
+ $$.arg = arg;
+ }
+ ;
+
+type:
+ IDENTIFIER {
+ $$.type.type = $1.buffer;
+ init_buffer_type(&$$.type.array_token, yylineno);
+ $$.type.dimension = 0;
+ }
+ | IDENTIFIER ARRAY {
+ $$.type.type = $1.buffer;
+ $$.type.array_token = $2.buffer;
+ $$.type.dimension = count_brackets($2.buffer.data);
+ }
+ | GENERIC {
+ $$.type.type = $1.buffer;
+ init_buffer_type(&$$.type.array_token, yylineno);
+ $$.type.dimension = 0;
+ }
+ ;
+
+direction:
+ { init_buffer_type(&$$.buffer, yylineno); }
+ | IN { $$.buffer = $1.buffer; }
+ | OUT { $$.buffer = $1.buffer; }
+ | INOUT { $$.buffer = $1.buffer; }
+ ;
+
+%%
+
+#include <ctype.h>
+#include <stdio.h>
+
+int g_error = 0;
+
+int yyerror(char* errstr)
+{
+ fprintf(stderr, "%s:%d: %s\n", g_currentFilename, yylineno, errstr);
+ g_error = 1;
+ return 1;
+}
+
+void init_buffer_type(buffer_type* buf, int lineno)
+{
+ buf->lineno = lineno;
+ buf->token = 0;
+ buf->data = NULL;
+ buf->extra = NULL;
+}
+
+static int count_brackets(const char* s)
+{
+ int n=0;
+ while (*s) {
+ if (*s == '[') n++;
+ s++;
+ }
+ return n;
+}
diff --git a/tools/aidl/generate_java.cpp b/tools/aidl/generate_java.cpp
new file mode 100644
index 0000000..9e57407
--- /dev/null
+++ b/tools/aidl/generate_java.cpp
@@ -0,0 +1,99 @@
+#include "generate_java.h"
+#include "Type.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// =================================================
+VariableFactory::VariableFactory(const string& base)
+ :m_base(base),
+ m_index(0)
+{
+}
+
+Variable*
+VariableFactory::Get(Type* type)
+{
+ char name[100];
+ sprintf(name, "%s%d", m_base.c_str(), m_index);
+ m_index++;
+ Variable* v = new Variable(type, name);
+ m_vars.push_back(v);
+ return v;
+}
+
+Variable*
+VariableFactory::Get(int index)
+{
+ return m_vars[index];
+}
+
+// =================================================
+string
+gather_comments(extra_text_type* extra)
+{
+ string s;
+ while (extra) {
+ if (extra->which == SHORT_COMMENT) {
+ s += extra->data;
+ }
+ else if (extra->which == LONG_COMMENT) {
+ s += "/*";
+ s += extra->data;
+ s += "*/";
+ }
+ extra = extra->next;
+ }
+ return s;
+}
+
+string
+append(const char* a, const char* b)
+{
+ string s = a;
+ s += b;
+ return s;
+}
+
+// =================================================
+int
+generate_java(const string& filename, const string& originalSrc,
+ interface_type* iface)
+{
+ Class* cl;
+
+ if (iface->document_item.item_type == INTERFACE_TYPE_BINDER) {
+ cl = generate_binder_interface_class(iface);
+ }
+ else if (iface->document_item.item_type == INTERFACE_TYPE_RPC) {
+ cl = generate_rpc_interface_class(iface);
+ }
+
+ Document* document = new Document;
+ document->comment = "";
+ if (iface->package) document->package = iface->package;
+ document->originalSrc = originalSrc;
+ document->classes.push_back(cl);
+
+// printf("outputting... filename=%s\n", filename.c_str());
+ FILE* to;
+ if (filename == "-") {
+ to = stdout;
+ } else {
+ /* open file in binary mode to ensure that the tool produces the
+ * same output on all platforms !!
+ */
+ to = fopen(filename.c_str(), "wb");
+ if (to == NULL) {
+ fprintf(stderr, "unable to open %s for write\n", filename.c_str());
+ return 1;
+ }
+ }
+
+ document->Write(to);
+
+ fclose(to);
+ return 0;
+}
+
diff --git a/tools/aidl/generate_java.h b/tools/aidl/generate_java.h
new file mode 100644
index 0000000..4bfcfeb
--- /dev/null
+++ b/tools/aidl/generate_java.h
@@ -0,0 +1,33 @@
+#ifndef GENERATE_JAVA_H
+#define GENERATE_JAVA_H
+
+#include "aidl_language.h"
+#include "AST.h"
+
+#include <string>
+
+using namespace std;
+
+int generate_java(const string& filename, const string& originalSrc,
+ interface_type* iface);
+
+Class* generate_binder_interface_class(const interface_type* iface);
+Class* generate_rpc_interface_class(const interface_type* iface);
+
+string gather_comments(extra_text_type* extra);
+string append(const char* a, const char* b);
+
+class VariableFactory
+{
+public:
+ VariableFactory(const string& base); // base must be short
+ Variable* Get(Type* type);
+ Variable* Get(int index);
+private:
+ vector<Variable*> m_vars;
+ string m_base;
+ int m_index;
+};
+
+#endif // GENERATE_JAVA_H
+
diff --git a/tools/aidl/generate_java_binder.cpp b/tools/aidl/generate_java_binder.cpp
new file mode 100644
index 0000000..f291ceb
--- /dev/null
+++ b/tools/aidl/generate_java_binder.cpp
@@ -0,0 +1,560 @@
+#include "generate_java.h"
+#include "Type.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// =================================================
+class StubClass : public Class
+{
+public:
+ StubClass(Type* type, Type* interfaceType);
+ virtual ~StubClass();
+
+ Variable* transact_code;
+ Variable* transact_data;
+ Variable* transact_reply;
+ Variable* transact_flags;
+ SwitchStatement* transact_switch;
+private:
+ void make_as_interface(Type* interfaceType);
+};
+
+StubClass::StubClass(Type* type, Type* interfaceType)
+ :Class()
+{
+ this->comment = "/** Local-side IPC implementation stub class. */";
+ this->modifiers = PUBLIC | ABSTRACT | STATIC;
+ this->what = Class::CLASS;
+ this->type = type;
+ this->extends = BINDER_NATIVE_TYPE;
+ this->interfaces.push_back(interfaceType);
+
+ // descriptor
+ Field* descriptor = new Field(STATIC | FINAL | PRIVATE,
+ new Variable(STRING_TYPE, "DESCRIPTOR"));
+ descriptor->value = "\"" + interfaceType->QualifiedName() + "\"";
+ this->elements.push_back(descriptor);
+
+ // ctor
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->comment = "/** Construct the stub at attach it to the "
+ "interface. */";
+ ctor->name = "Stub";
+ ctor->statements = new StatementBlock;
+ MethodCall* attach = new MethodCall(THIS_VALUE, "attachInterface",
+ 2, THIS_VALUE, new LiteralExpression("DESCRIPTOR"));
+ ctor->statements->Add(attach);
+ this->elements.push_back(ctor);
+
+ // asInterface
+ make_as_interface(interfaceType);
+
+ // asBinder
+ Method* asBinder = new Method;
+ asBinder->modifiers = PUBLIC | OVERRIDE;
+ asBinder->returnType = IBINDER_TYPE;
+ asBinder->name = "asBinder";
+ asBinder->statements = new StatementBlock;
+ asBinder->statements->Add(new ReturnStatement(THIS_VALUE));
+ this->elements.push_back(asBinder);
+
+ // onTransact
+ this->transact_code = new Variable(INT_TYPE, "code");
+ this->transact_data = new Variable(PARCEL_TYPE, "data");
+ this->transact_reply = new Variable(PARCEL_TYPE, "reply");
+ this->transact_flags = new Variable(INT_TYPE, "flags");
+ Method* onTransact = new Method;
+ onTransact->modifiers = PUBLIC | OVERRIDE;
+ onTransact->returnType = BOOLEAN_TYPE;
+ onTransact->name = "onTransact";
+ onTransact->parameters.push_back(this->transact_code);
+ onTransact->parameters.push_back(this->transact_data);
+ onTransact->parameters.push_back(this->transact_reply);
+ onTransact->parameters.push_back(this->transact_flags);
+ onTransact->statements = new StatementBlock;
+ onTransact->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+ this->elements.push_back(onTransact);
+ this->transact_switch = new SwitchStatement(this->transact_code);
+
+ onTransact->statements->Add(this->transact_switch);
+ MethodCall* superCall = new MethodCall(SUPER_VALUE, "onTransact", 4,
+ this->transact_code, this->transact_data,
+ this->transact_reply, this->transact_flags);
+ onTransact->statements->Add(new ReturnStatement(superCall));
+}
+
+StubClass::~StubClass()
+{
+}
+
+void
+StubClass::make_as_interface(Type *interfaceType)
+{
+ Variable* obj = new Variable(IBINDER_TYPE, "obj");
+
+ Method* m = new Method;
+ m->comment = "/**\n * Cast an IBinder object into an ";
+ m->comment += interfaceType->QualifiedName();
+ m->comment += " interface,\n";
+ m->comment += " * generating a proxy if needed.\n */";
+ m->modifiers = PUBLIC | STATIC;
+ m->returnType = interfaceType;
+ m->name = "asInterface";
+ m->parameters.push_back(obj);
+ m->statements = new StatementBlock;
+
+ IfStatement* ifstatement = new IfStatement();
+ ifstatement->expression = new Comparison(obj, "==", NULL_VALUE);
+ ifstatement->statements = new StatementBlock;
+ ifstatement->statements->Add(new ReturnStatement(NULL_VALUE));
+ m->statements->Add(ifstatement);
+
+ // IInterface iin = obj.queryLocalInterface(DESCRIPTOR)
+ MethodCall* queryLocalInterface = new MethodCall(obj, "queryLocalInterface");
+ queryLocalInterface->arguments.push_back(new LiteralExpression("DESCRIPTOR"));
+ IInterfaceType* iinType = new IInterfaceType();
+ Variable *iin = new Variable(iinType, "iin");
+ VariableDeclaration* iinVd = new VariableDeclaration(iin, queryLocalInterface, NULL);
+ m->statements->Add(iinVd);
+
+ // Ensure the instance type of the local object is as expected.
+ // One scenario where this is needed is if another package (with a
+ // different class loader) runs in the same process as the service.
+
+ // if (iin != null && iin instanceof <interfaceType>) return (<interfaceType>) iin;
+ Comparison* iinNotNull = new Comparison(iin, "!=", NULL_VALUE);
+ Comparison* instOfCheck = new Comparison(iin, " instanceof ",
+ new LiteralExpression(interfaceType->QualifiedName()));
+ IfStatement* instOfStatement = new IfStatement();
+ instOfStatement->expression = new Comparison(iinNotNull, "&&", instOfCheck);
+ instOfStatement->statements = new StatementBlock;
+ instOfStatement->statements->Add(new ReturnStatement(new Cast(interfaceType, iin)));
+ m->statements->Add(instOfStatement);
+
+ string proxyType = interfaceType->QualifiedName();
+ proxyType += ".Stub.Proxy";
+ NewExpression* ne = new NewExpression(NAMES.Find(proxyType));
+ ne->arguments.push_back(obj);
+ m->statements->Add(new ReturnStatement(ne));
+
+ this->elements.push_back(m);
+}
+
+
+
+// =================================================
+class ProxyClass : public Class
+{
+public:
+ ProxyClass(Type* type, InterfaceType* interfaceType);
+ virtual ~ProxyClass();
+
+ Variable* mRemote;
+ bool mOneWay;
+};
+
+ProxyClass::ProxyClass(Type* type, InterfaceType* interfaceType)
+ :Class()
+{
+ this->modifiers = PRIVATE | STATIC;
+ this->what = Class::CLASS;
+ this->type = type;
+ this->interfaces.push_back(interfaceType);
+
+ mOneWay = interfaceType->OneWay();
+
+ // IBinder mRemote
+ mRemote = new Variable(IBINDER_TYPE, "mRemote");
+ this->elements.push_back(new Field(PRIVATE, mRemote));
+
+ // Proxy()
+ Variable* remote = new Variable(IBINDER_TYPE, "remote");
+ Method* ctor = new Method;
+ ctor->name = "Proxy";
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(remote);
+ ctor->statements->Add(new Assignment(mRemote, remote));
+ this->elements.push_back(ctor);
+
+ // IBinder asBinder()
+ Method* asBinder = new Method;
+ asBinder->modifiers = PUBLIC | OVERRIDE;
+ asBinder->returnType = IBINDER_TYPE;
+ asBinder->name = "asBinder";
+ asBinder->statements = new StatementBlock;
+ asBinder->statements->Add(new ReturnStatement(mRemote));
+ this->elements.push_back(asBinder);
+}
+
+ProxyClass::~ProxyClass()
+{
+}
+
+// =================================================
+static void
+generate_new_array(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel)
+{
+ Variable* len = new Variable(INT_TYPE, v->name + "_length");
+ addTo->Add(new VariableDeclaration(len, new MethodCall(parcel, "readInt")));
+ IfStatement* lencheck = new IfStatement();
+ lencheck->expression = new Comparison(len, "<", new LiteralExpression("0"));
+ lencheck->statements->Add(new Assignment(v, NULL_VALUE));
+ lencheck->elseif = new IfStatement();
+ lencheck->elseif->statements->Add(new Assignment(v,
+ new NewArrayExpression(t, len)));
+ addTo->Add(lencheck);
+}
+
+static void
+generate_write_to_parcel(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags)
+{
+ if (v->dimension == 0) {
+ t->WriteToParcel(addTo, v, parcel, flags);
+ }
+ if (v->dimension == 1) {
+ t->WriteArrayToParcel(addTo, v, parcel, flags);
+ }
+}
+
+static void
+generate_create_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl)
+{
+ if (v->dimension == 0) {
+ t->CreateFromParcel(addTo, v, parcel, cl);
+ }
+ if (v->dimension == 1) {
+ t->CreateArrayFromParcel(addTo, v, parcel, cl);
+ }
+}
+
+static void
+generate_read_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl)
+{
+ if (v->dimension == 0) {
+ t->ReadFromParcel(addTo, v, parcel, cl);
+ }
+ if (v->dimension == 1) {
+ t->ReadArrayFromParcel(addTo, v, parcel, cl);
+ }
+}
+
+
+static void
+generate_method(const method_type* method, Class* interface,
+ StubClass* stubClass, ProxyClass* proxyClass, int index)
+{
+ arg_type* arg;
+ int i;
+ bool hasOutParams = false;
+
+ const bool oneway = proxyClass->mOneWay || method->oneway;
+
+ // == the TRANSACT_ constant =============================================
+ string transactCodeName = "TRANSACTION_";
+ transactCodeName += method->name.data;
+
+ char transactCodeValue[60];
+ sprintf(transactCodeValue, "(android.os.IBinder.FIRST_CALL_TRANSACTION + %d)", index);
+
+ Field* transactCode = new Field(STATIC | FINAL,
+ new Variable(INT_TYPE, transactCodeName));
+ transactCode->value = transactCodeValue;
+ stubClass->elements.push_back(transactCode);
+
+ // == the declaration in the interface ===================================
+ Method* decl = new Method;
+ decl->comment = gather_comments(method->comments_token->extra);
+ decl->modifiers = PUBLIC;
+ decl->returnType = NAMES.Search(method->type.type.data);
+ decl->returnTypeDimension = method->type.dimension;
+ decl->name = method->name.data;
+
+ arg = method->args;
+ while (arg != NULL) {
+ decl->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ arg = arg->next;
+ }
+
+ decl->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+
+ interface->elements.push_back(decl);
+
+ // == the stub method ====================================================
+
+ Case* c = new Case(transactCodeName);
+
+ MethodCall* realCall = new MethodCall(THIS_VALUE, method->name.data);
+
+ // interface token validation is the very first thing we do
+ c->statements->Add(new MethodCall(stubClass->transact_data,
+ "enforceInterface", 1, new LiteralExpression("DESCRIPTOR")));
+
+ // args
+ Variable* cl = NULL;
+ VariableFactory stubArgs("_arg");
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(t);
+ v->dimension = arg->type.dimension;
+
+ c->statements->Add(new VariableDeclaration(v));
+
+ if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+ generate_create_from_parcel(t, c->statements, v,
+ stubClass->transact_data, &cl);
+ } else {
+ if (arg->type.dimension == 0) {
+ c->statements->Add(new Assignment(v, new NewExpression(v->type)));
+ }
+ else if (arg->type.dimension == 1) {
+ generate_new_array(v->type, c->statements, v,
+ stubClass->transact_data);
+ }
+ else {
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
+ __LINE__);
+ }
+ }
+
+ realCall->arguments.push_back(v);
+
+ arg = arg->next;
+ }
+
+ // the real call
+ Variable* _result = NULL;
+ if (0 == strcmp(method->type.type.data, "void")) {
+ c->statements->Add(realCall);
+
+ if (!oneway) {
+ // report that there were no exceptions
+ MethodCall* ex = new MethodCall(stubClass->transact_reply,
+ "writeNoException", 0);
+ c->statements->Add(ex);
+ }
+ } else {
+ _result = new Variable(decl->returnType, "_result",
+ decl->returnTypeDimension);
+ c->statements->Add(new VariableDeclaration(_result, realCall));
+
+ if (!oneway) {
+ // report that there were no exceptions
+ MethodCall* ex = new MethodCall(stubClass->transact_reply,
+ "writeNoException", 0);
+ c->statements->Add(ex);
+ }
+
+ // marshall the return value
+ generate_write_to_parcel(decl->returnType, c->statements, _result,
+ stubClass->transact_reply,
+ Type::PARCELABLE_WRITE_RETURN_VALUE);
+ }
+
+ // out parameters
+ i = 0;
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(i++);
+
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ generate_write_to_parcel(t, c->statements, v,
+ stubClass->transact_reply,
+ Type::PARCELABLE_WRITE_RETURN_VALUE);
+ hasOutParams = true;
+ }
+
+ arg = arg->next;
+ }
+
+ // return true
+ c->statements->Add(new ReturnStatement(TRUE_VALUE));
+ stubClass->transact_switch->cases.push_back(c);
+
+ // == the proxy method ===================================================
+ Method* proxy = new Method;
+ proxy->comment = gather_comments(method->comments_token->extra);
+ proxy->modifiers = PUBLIC | OVERRIDE;
+ proxy->returnType = NAMES.Search(method->type.type.data);
+ proxy->returnTypeDimension = method->type.dimension;
+ proxy->name = method->name.data;
+ proxy->statements = new StatementBlock;
+ arg = method->args;
+ while (arg != NULL) {
+ proxy->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ arg = arg->next;
+ }
+ proxy->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+ proxyClass->elements.push_back(proxy);
+
+ // the parcels
+ Variable* _data = new Variable(PARCEL_TYPE, "_data");
+ proxy->statements->Add(new VariableDeclaration(_data,
+ new MethodCall(PARCEL_TYPE, "obtain")));
+ Variable* _reply = NULL;
+ if (!oneway) {
+ _reply = new Variable(PARCEL_TYPE, "_reply");
+ proxy->statements->Add(new VariableDeclaration(_reply,
+ new MethodCall(PARCEL_TYPE, "obtain")));
+ }
+
+ // the return value
+ _result = NULL;
+ if (0 != strcmp(method->type.type.data, "void")) {
+ _result = new Variable(proxy->returnType, "_result",
+ method->type.dimension);
+ proxy->statements->Add(new VariableDeclaration(_result));
+ }
+
+ // try and finally
+ TryStatement* tryStatement = new TryStatement();
+ proxy->statements->Add(tryStatement);
+ FinallyStatement* finallyStatement = new FinallyStatement();
+ proxy->statements->Add(finallyStatement);
+
+ // the interface identifier token: the DESCRIPTOR constant, marshalled as a string
+ tryStatement->statements->Add(new MethodCall(_data, "writeInterfaceToken",
+ 1, new LiteralExpression("DESCRIPTOR")));
+
+ // the parameters
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+ int dir = convert_direction(arg->direction.data);
+ if (dir == OUT_PARAMETER && arg->type.dimension != 0) {
+ IfStatement* checklen = new IfStatement();
+ checklen->expression = new Comparison(v, "==", NULL_VALUE);
+ checklen->statements->Add(new MethodCall(_data, "writeInt", 1,
+ new LiteralExpression("-1")));
+ checklen->elseif = new IfStatement();
+ checklen->elseif->statements->Add(new MethodCall(_data, "writeInt",
+ 1, new FieldVariable(v, "length")));
+ tryStatement->statements->Add(checklen);
+ }
+ else if (dir & IN_PARAMETER) {
+ generate_write_to_parcel(t, tryStatement->statements, v, _data, 0);
+ }
+ arg = arg->next;
+ }
+
+ // the transact call
+ MethodCall* call = new MethodCall(proxyClass->mRemote, "transact", 4,
+ new LiteralExpression("Stub." + transactCodeName),
+ _data, _reply ? _reply : NULL_VALUE,
+ new LiteralExpression(
+ oneway ? "android.os.IBinder.FLAG_ONEWAY" : "0"));
+ tryStatement->statements->Add(call);
+
+ // throw back exceptions.
+ if (_reply) {
+ MethodCall* ex = new MethodCall(_reply, "readException", 0);
+ tryStatement->statements->Add(ex);
+ }
+
+ // returning and cleanup
+ if (_reply != NULL) {
+ if (_result != NULL) {
+ generate_create_from_parcel(proxy->returnType,
+ tryStatement->statements, _result, _reply, &cl);
+ }
+
+ // the out/inout parameters
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ generate_read_from_parcel(t, tryStatement->statements,
+ v, _reply, &cl);
+ }
+ arg = arg->next;
+ }
+
+ finallyStatement->statements->Add(new MethodCall(_reply, "recycle"));
+ }
+ finallyStatement->statements->Add(new MethodCall(_data, "recycle"));
+
+ if (_result != NULL) {
+ proxy->statements->Add(new ReturnStatement(_result));
+ }
+}
+
+static void
+generate_interface_descriptors(StubClass* stub, ProxyClass* proxy)
+{
+ // the interface descriptor transaction handler
+ Case* c = new Case("INTERFACE_TRANSACTION");
+ c->statements->Add(new MethodCall(stub->transact_reply, "writeString",
+ 1, new LiteralExpression("DESCRIPTOR")));
+ c->statements->Add(new ReturnStatement(TRUE_VALUE));
+ stub->transact_switch->cases.push_back(c);
+
+ // and the proxy-side method returning the descriptor directly
+ Method* getDesc = new Method;
+ getDesc->modifiers = PUBLIC;
+ getDesc->returnType = STRING_TYPE;
+ getDesc->returnTypeDimension = 0;
+ getDesc->name = "getInterfaceDescriptor";
+ getDesc->statements = new StatementBlock;
+ getDesc->statements->Add(new ReturnStatement(new LiteralExpression("DESCRIPTOR")));
+ proxy->elements.push_back(getDesc);
+}
+
+Class*
+generate_binder_interface_class(const interface_type* iface)
+{
+ InterfaceType* interfaceType = static_cast<InterfaceType*>(
+ NAMES.Find(iface->package, iface->name.data));
+
+ // the interface class
+ Class* interface = new Class;
+ interface->comment = gather_comments(iface->comments_token->extra);
+ interface->modifiers = PUBLIC;
+ interface->what = Class::INTERFACE;
+ interface->type = interfaceType;
+ interface->interfaces.push_back(IINTERFACE_TYPE);
+
+ // the stub inner class
+ StubClass* stub = new StubClass(
+ NAMES.Find(iface->package, append(iface->name.data, ".Stub").c_str()),
+ interfaceType);
+ interface->elements.push_back(stub);
+
+ // the proxy inner class
+ ProxyClass* proxy = new ProxyClass(
+ NAMES.Find(iface->package,
+ append(iface->name.data, ".Stub.Proxy").c_str()),
+ interfaceType);
+ stub->elements.push_back(proxy);
+
+ // stub and proxy support for getInterfaceDescriptor()
+ generate_interface_descriptors(stub, proxy);
+
+ // all the declared methods of the interface
+ int index = 0;
+ interface_item_type* item = iface->interface_items;
+ while (item != NULL) {
+ if (item->item_type == METHOD_TYPE) {
+ method_type * method_item = (method_type*) item;
+ generate_method(method_item, interface, stub, proxy, method_item->assigned_id);
+ }
+ item = item->next;
+ index++;
+ }
+
+ return interface;
+}
+
diff --git a/tools/aidl/generate_java_rpc.cpp b/tools/aidl/generate_java_rpc.cpp
new file mode 100644
index 0000000..5e4dacc
--- /dev/null
+++ b/tools/aidl/generate_java_rpc.cpp
@@ -0,0 +1,1001 @@
+#include "generate_java.h"
+#include "Type.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+Type* SERVICE_CONTEXT_TYPE = new Type("android.content",
+ "Context", Type::BUILT_IN, false, false, false);
+Type* PRESENTER_BASE_TYPE = new Type("android.support.place.connector",
+ "EventListener", Type::BUILT_IN, false, false, false);
+Type* PRESENTER_LISTENER_BASE_TYPE = new Type("android.support.place.connector",
+ "EventListener.Listener", Type::BUILT_IN, false, false, false);
+Type* RPC_BROKER_TYPE = new Type("android.support.place.connector", "Broker",
+ Type::BUILT_IN, false, false, false);
+Type* RPC_CONTAINER_TYPE = new Type("com.android.athome.connector", "ConnectorContainer",
+ Type::BUILT_IN, false, false, false);
+Type* PLACE_INFO_TYPE = new Type("android.support.place.connector", "PlaceInfo",
+ Type::BUILT_IN, false, false, false);
+// TODO: Just use Endpoint, so this works for all endpoints.
+Type* RPC_CONNECTOR_TYPE = new Type("android.support.place.connector", "Connector",
+ Type::BUILT_IN, false, false, false);
+Type* RPC_ENDPOINT_INFO_TYPE = new UserDataType("android.support.place.rpc",
+ "EndpointInfo", true, __FILE__, __LINE__);
+Type* RPC_RESULT_HANDLER_TYPE = new UserDataType("android.support.place.rpc", "RpcResultHandler",
+ true, __FILE__, __LINE__);
+Type* RPC_ERROR_LISTENER_TYPE = new Type("android.support.place.rpc", "RpcErrorHandler",
+ Type::BUILT_IN, false, false, false);
+Type* RPC_CONTEXT_TYPE = new UserDataType("android.support.place.rpc", "RpcContext", true,
+ __FILE__, __LINE__);
+
+static void generate_create_from_data(Type* t, StatementBlock* addTo, const string& key,
+ Variable* v, Variable* data, Variable** cl);
+static void generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from);
+static void generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data);
+
+static string
+format_int(int n)
+{
+ char str[20];
+ sprintf(str, "%d", n);
+ return string(str);
+}
+
+static string
+class_name_leaf(const string& str)
+{
+ string::size_type pos = str.rfind('.');
+ if (pos == string::npos) {
+ return str;
+ } else {
+ return string(str, pos+1);
+ }
+}
+
+static string
+results_class_name(const string& n)
+{
+ string str = n;
+ str[0] = toupper(str[0]);
+ str.insert(0, "On");
+ return str;
+}
+
+static string
+results_method_name(const string& n)
+{
+ string str = n;
+ str[0] = toupper(str[0]);
+ str.insert(0, "on");
+ return str;
+}
+
+static string
+push_method_name(const string& n)
+{
+ string str = n;
+ str[0] = toupper(str[0]);
+ str.insert(0, "push");
+ return str;
+}
+
+// =================================================
+class DispatcherClass : public Class
+{
+public:
+ DispatcherClass(const interface_type* iface, Expression* target);
+ virtual ~DispatcherClass();
+
+ void AddMethod(const method_type* method);
+ void DoneWithMethods();
+
+ Method* processMethod;
+ Variable* actionParam;
+ Variable* requestParam;
+ Variable* rpcContextParam;
+ Variable* errorParam;
+ Variable* requestData;
+ Variable* resultData;
+ IfStatement* dispatchIfStatement;
+ Expression* targetExpression;
+
+private:
+ void generate_process();
+};
+
+DispatcherClass::DispatcherClass(const interface_type* iface, Expression* target)
+ :Class(),
+ dispatchIfStatement(NULL),
+ targetExpression(target)
+{
+ generate_process();
+}
+
+DispatcherClass::~DispatcherClass()
+{
+}
+
+void
+DispatcherClass::generate_process()
+{
+ // byte[] process(String action, byte[] params, RpcContext context, RpcError status)
+ this->processMethod = new Method;
+ this->processMethod->modifiers = PUBLIC;
+ this->processMethod->returnType = BYTE_TYPE;
+ this->processMethod->returnTypeDimension = 1;
+ this->processMethod->name = "process";
+ this->processMethod->statements = new StatementBlock;
+
+ this->actionParam = new Variable(STRING_TYPE, "action");
+ this->processMethod->parameters.push_back(this->actionParam);
+
+ this->requestParam = new Variable(BYTE_TYPE, "requestParam", 1);
+ this->processMethod->parameters.push_back(this->requestParam);
+
+ this->rpcContextParam = new Variable(RPC_CONTEXT_TYPE, "context", 0);
+ this->processMethod->parameters.push_back(this->rpcContextParam);
+
+ this->errorParam = new Variable(RPC_ERROR_TYPE, "errorParam", 0);
+ this->processMethod->parameters.push_back(this->errorParam);
+
+ this->requestData = new Variable(RPC_DATA_TYPE, "request");
+ this->processMethod->statements->Add(new VariableDeclaration(requestData,
+ new NewExpression(RPC_DATA_TYPE, 1, this->requestParam)));
+
+ this->resultData = new Variable(RPC_DATA_TYPE, "resultData");
+ this->processMethod->statements->Add(new VariableDeclaration(this->resultData,
+ NULL_VALUE));
+}
+
+void
+DispatcherClass::AddMethod(const method_type* method)
+{
+ arg_type* arg;
+
+ // The if/switch statement
+ IfStatement* ifs = new IfStatement();
+ ifs->expression = new MethodCall(new StringLiteralExpression(method->name.data), "equals",
+ 1, this->actionParam);
+ StatementBlock* block = ifs->statements = new StatementBlock;
+ if (this->dispatchIfStatement == NULL) {
+ this->dispatchIfStatement = ifs;
+ this->processMethod->statements->Add(dispatchIfStatement);
+ } else {
+ this->dispatchIfStatement->elseif = ifs;
+ this->dispatchIfStatement = ifs;
+ }
+
+ // The call to decl (from above)
+ MethodCall* realCall = new MethodCall(this->targetExpression, method->name.data);
+
+ // args
+ Variable* classLoader = NULL;
+ VariableFactory stubArgs("_arg");
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(t);
+ v->dimension = arg->type.dimension;
+
+ // Unmarshall the parameter
+ block->Add(new VariableDeclaration(v));
+ if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+ generate_create_from_data(t, block, arg->name.data, v,
+ this->requestData, &classLoader);
+ } else {
+ if (arg->type.dimension == 0) {
+ block->Add(new Assignment(v, new NewExpression(v->type)));
+ }
+ else if (arg->type.dimension == 1) {
+ generate_new_array(v->type, block, v, this->requestData);
+ }
+ else {
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
+ __LINE__);
+ }
+ }
+
+ // Add that parameter to the method call
+ realCall->arguments.push_back(v);
+
+ arg = arg->next;
+ }
+
+ // Add a final parameter: RpcContext. Contains data about
+ // incoming request (e.g., certificate)
+ realCall->arguments.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
+
+ Type* returnType = NAMES.Search(method->type.type.data);
+ if (returnType == EVENT_FAKE_TYPE) {
+ returnType = VOID_TYPE;
+ }
+
+ // the real call
+ bool first = true;
+ Variable* _result = NULL;
+ if (returnType == VOID_TYPE) {
+ block->Add(realCall);
+ } else {
+ _result = new Variable(returnType, "_result",
+ method->type.dimension);
+ block->Add(new VariableDeclaration(_result, realCall));
+
+ // need the result RpcData
+ if (first) {
+ block->Add(new Assignment(this->resultData,
+ new NewExpression(RPC_DATA_TYPE)));
+ first = false;
+ }
+
+ // marshall the return value
+ generate_write_to_data(returnType, block,
+ new StringLiteralExpression("_result"), _result, this->resultData);
+ }
+
+ // out parameters
+ int i = 0;
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(i++);
+
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ // need the result RpcData
+ if (first) {
+ block->Add(new Assignment(this->resultData, new NewExpression(RPC_DATA_TYPE)));
+ first = false;
+ }
+
+ generate_write_to_data(t, block, new StringLiteralExpression(arg->name.data),
+ v, this->resultData);
+ }
+
+ arg = arg->next;
+ }
+}
+
+void
+DispatcherClass::DoneWithMethods()
+{
+ if (this->dispatchIfStatement == NULL) {
+ return;
+ }
+
+ this->elements.push_back(this->processMethod);
+
+ IfStatement* fallthrough = new IfStatement();
+ fallthrough->statements = new StatementBlock;
+ fallthrough->statements->Add(new ReturnStatement(
+ new MethodCall(SUPER_VALUE, "process", 4,
+ this->actionParam, this->requestParam,
+ this->rpcContextParam,
+ this->errorParam)));
+ this->dispatchIfStatement->elseif = fallthrough;
+ IfStatement* s = new IfStatement;
+ s->statements = new StatementBlock;
+ this->processMethod->statements->Add(s);
+ s->expression = new Comparison(this->resultData, "!=", NULL_VALUE);
+ s->statements->Add(new ReturnStatement(new MethodCall(this->resultData, "serialize")));
+ s->elseif = new IfStatement;
+ s = s->elseif;
+ s->statements->Add(new ReturnStatement(NULL_VALUE));
+}
+
+// =================================================
+class RpcProxyClass : public Class
+{
+public:
+ RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType);
+ virtual ~RpcProxyClass();
+
+ Variable* endpoint;
+ Variable* broker;
+
+private:
+ void generate_ctor();
+ void generate_get_endpoint_info();
+};
+
+RpcProxyClass::RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType)
+ :Class()
+{
+ this->comment = gather_comments(iface->comments_token->extra);
+ this->modifiers = PUBLIC;
+ this->what = Class::CLASS;
+ this->type = interfaceType;
+
+ // broker
+ this->broker = new Variable(RPC_BROKER_TYPE, "_broker");
+ this->elements.push_back(new Field(PRIVATE, this->broker));
+ // endpoint
+ this->endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "_endpoint");
+ this->elements.push_back(new Field(PRIVATE, this->endpoint));
+
+ // methods
+ generate_ctor();
+ generate_get_endpoint_info();
+}
+
+RpcProxyClass::~RpcProxyClass()
+{
+}
+
+void
+RpcProxyClass::generate_ctor()
+{
+ Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
+ Variable* endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "endpoint");
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->name = class_name_leaf(this->type->Name());
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(broker);
+ ctor->parameters.push_back(endpoint);
+ this->elements.push_back(ctor);
+
+ ctor->statements->Add(new Assignment(this->broker, broker));
+ ctor->statements->Add(new Assignment(this->endpoint, endpoint));
+}
+
+void
+RpcProxyClass::generate_get_endpoint_info()
+{
+ Method* get = new Method;
+ get->modifiers = PUBLIC;
+ get->returnType = RPC_ENDPOINT_INFO_TYPE;
+ get->name = "getEndpointInfo";
+ get->statements = new StatementBlock;
+ this->elements.push_back(get);
+
+ get->statements->Add(new ReturnStatement(this->endpoint));
+}
+
+// =================================================
+class EventListenerClass : public DispatcherClass
+{
+public:
+ EventListenerClass(const interface_type* iface, Type* listenerType);
+ virtual ~EventListenerClass();
+
+ Variable* _listener;
+
+private:
+ void generate_ctor();
+};
+
+Expression*
+generate_get_listener_expression(Type* cast)
+{
+ return new Cast(cast, new MethodCall(THIS_VALUE, "getView"));
+}
+
+EventListenerClass::EventListenerClass(const interface_type* iface, Type* listenerType)
+ :DispatcherClass(iface, new FieldVariable(THIS_VALUE, "_listener"))
+{
+ this->modifiers = PRIVATE;
+ this->what = Class::CLASS;
+ this->type = new Type(iface->package ? iface->package : "",
+ append(iface->name.data, ".Presenter"),
+ Type::GENERATED, false, false, false);
+ this->extends = PRESENTER_BASE_TYPE;
+
+ this->_listener = new Variable(listenerType, "_listener");
+ this->elements.push_back(new Field(PRIVATE, this->_listener));
+
+ // methods
+ generate_ctor();
+}
+
+EventListenerClass::~EventListenerClass()
+{
+}
+
+void
+EventListenerClass::generate_ctor()
+{
+ Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
+ Variable* listener = new Variable(this->_listener->type, "listener");
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->name = class_name_leaf(this->type->Name());
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(broker);
+ ctor->parameters.push_back(listener);
+ this->elements.push_back(ctor);
+
+ ctor->statements->Add(new MethodCall("super", 2, broker, listener));
+ ctor->statements->Add(new Assignment(this->_listener, listener));
+}
+
+// =================================================
+class ListenerClass : public Class
+{
+public:
+ ListenerClass(const interface_type* iface);
+ virtual ~ListenerClass();
+
+ bool needed;
+
+private:
+ void generate_ctor();
+};
+
+ListenerClass::ListenerClass(const interface_type* iface)
+ :Class(),
+ needed(false)
+{
+ this->comment = "/** Extend this to listen to the events from this class. */";
+ this->modifiers = STATIC | PUBLIC ;
+ this->what = Class::CLASS;
+ this->type = new Type(iface->package ? iface->package : "",
+ append(iface->name.data, ".Listener"),
+ Type::GENERATED, false, false, false);
+ this->extends = PRESENTER_LISTENER_BASE_TYPE;
+}
+
+ListenerClass::~ListenerClass()
+{
+}
+
+// =================================================
+class EndpointBaseClass : public DispatcherClass
+{
+public:
+ EndpointBaseClass(const interface_type* iface);
+ virtual ~EndpointBaseClass();
+
+ bool needed;
+
+private:
+ void generate_ctor();
+};
+
+EndpointBaseClass::EndpointBaseClass(const interface_type* iface)
+ :DispatcherClass(iface, THIS_VALUE),
+ needed(false)
+{
+ this->comment = "/** Extend this to implement a link service. */";
+ this->modifiers = STATIC | PUBLIC | ABSTRACT;
+ this->what = Class::CLASS;
+ this->type = new Type(iface->package ? iface->package : "",
+ append(iface->name.data, ".EndpointBase"),
+ Type::GENERATED, false, false, false);
+ this->extends = RPC_CONNECTOR_TYPE;
+
+ // methods
+ generate_ctor();
+}
+
+EndpointBaseClass::~EndpointBaseClass()
+{
+}
+
+void
+EndpointBaseClass::generate_ctor()
+{
+ Variable* container = new Variable(RPC_CONTAINER_TYPE, "container");
+ Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
+ Variable* place = new Variable(PLACE_INFO_TYPE, "placeInfo");
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->name = class_name_leaf(this->type->Name());
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(container);
+ ctor->parameters.push_back(broker);
+ ctor->parameters.push_back(place);
+ this->elements.push_back(ctor);
+
+ ctor->statements->Add(new MethodCall("super", 3, container, broker, place));
+}
+
+// =================================================
+class ResultDispatcherClass : public Class
+{
+public:
+ ResultDispatcherClass();
+ virtual ~ResultDispatcherClass();
+
+ void AddMethod(int index, const string& name, Method** method, Variable** param);
+
+ bool needed;
+ Variable* methodId;
+ Variable* callback;
+ Method* onResultMethod;
+ Variable* resultParam;
+ SwitchStatement* methodSwitch;
+
+private:
+ void generate_ctor();
+ void generate_onResult();
+};
+
+ResultDispatcherClass::ResultDispatcherClass()
+ :Class(),
+ needed(false)
+{
+ this->modifiers = PRIVATE | FINAL;
+ this->what = Class::CLASS;
+ this->type = new Type("_ResultDispatcher", Type::GENERATED, false, false, false);
+ this->interfaces.push_back(RPC_RESULT_HANDLER_TYPE);
+
+ // methodId
+ this->methodId = new Variable(INT_TYPE, "methodId");
+ this->elements.push_back(new Field(PRIVATE, this->methodId));
+ this->callback = new Variable(OBJECT_TYPE, "callback");
+ this->elements.push_back(new Field(PRIVATE, this->callback));
+
+ // methods
+ generate_ctor();
+ generate_onResult();
+}
+
+ResultDispatcherClass::~ResultDispatcherClass()
+{
+}
+
+void
+ResultDispatcherClass::generate_ctor()
+{
+ Variable* methodIdParam = new Variable(INT_TYPE, "methId");
+ Variable* callbackParam = new Variable(OBJECT_TYPE, "cbObj");
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->name = class_name_leaf(this->type->Name());
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(methodIdParam);
+ ctor->parameters.push_back(callbackParam);
+ this->elements.push_back(ctor);
+
+ ctor->statements->Add(new Assignment(this->methodId, methodIdParam));
+ ctor->statements->Add(new Assignment(this->callback, callbackParam));
+}
+
+void
+ResultDispatcherClass::generate_onResult()
+{
+ this->onResultMethod = new Method;
+ this->onResultMethod->modifiers = PUBLIC;
+ this->onResultMethod->returnType = VOID_TYPE;
+ this->onResultMethod->returnTypeDimension = 0;
+ this->onResultMethod->name = "onResult";
+ this->onResultMethod->statements = new StatementBlock;
+ this->elements.push_back(this->onResultMethod);
+
+ this->resultParam = new Variable(BYTE_TYPE, "result", 1);
+ this->onResultMethod->parameters.push_back(this->resultParam);
+
+ this->methodSwitch = new SwitchStatement(this->methodId);
+ this->onResultMethod->statements->Add(this->methodSwitch);
+}
+
+void
+ResultDispatcherClass::AddMethod(int index, const string& name, Method** method, Variable** param)
+{
+ Method* m = new Method;
+ m->modifiers = PUBLIC;
+ m->returnType = VOID_TYPE;
+ m->returnTypeDimension = 0;
+ m->name = name;
+ m->statements = new StatementBlock;
+ *param = new Variable(BYTE_TYPE, "result", 1);
+ m->parameters.push_back(*param);
+ this->elements.push_back(m);
+ *method = m;
+
+ Case* c = new Case(format_int(index));
+ c->statements->Add(new MethodCall(new LiteralExpression("this"), name, 1, this->resultParam));
+ c->statements->Add(new Break());
+
+ this->methodSwitch->cases.push_back(c);
+}
+
+// =================================================
+static void
+generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from)
+{
+ fprintf(stderr, "aidl: implement generate_new_array %s:%d\n", __FILE__, __LINE__);
+ exit(1);
+}
+
+static void
+generate_create_from_data(Type* t, StatementBlock* addTo, const string& key, Variable* v,
+ Variable* data, Variable** cl)
+{
+ Expression* k = new StringLiteralExpression(key);
+ if (v->dimension == 0) {
+ t->CreateFromRpcData(addTo, k, v, data, cl);
+ }
+ if (v->dimension == 1) {
+ //t->ReadArrayFromRpcData(addTo, v, data, cl);
+ fprintf(stderr, "aidl: implement generate_create_from_data for arrays%s:%d\n",
+ __FILE__, __LINE__);
+ }
+}
+
+static void
+generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v, Variable* data)
+{
+ if (v->dimension == 0) {
+ t->WriteToRpcData(addTo, k, v, data, 0);
+ }
+ if (v->dimension == 1) {
+ //t->WriteArrayToParcel(addTo, v, data);
+ fprintf(stderr, "aidl: implement generate_write_to_data for arrays%s:%d\n",
+ __FILE__, __LINE__);
+ }
+}
+
+// =================================================
+static Type*
+generate_results_method(const method_type* method, RpcProxyClass* proxyClass)
+{
+ arg_type* arg;
+
+ string resultsMethodName = results_method_name(method->name.data);
+ Type* resultsInterfaceType = new Type(results_class_name(method->name.data),
+ Type::GENERATED, false, false, false);
+
+ if (!method->oneway) {
+ Class* resultsClass = new Class;
+ resultsClass->modifiers = STATIC | PUBLIC;
+ resultsClass->what = Class::INTERFACE;
+ resultsClass->type = resultsInterfaceType;
+
+ Method* resultMethod = new Method;
+ resultMethod->comment = gather_comments(method->comments_token->extra);
+ resultMethod->modifiers = PUBLIC;
+ resultMethod->returnType = VOID_TYPE;
+ resultMethod->returnTypeDimension = 0;
+ resultMethod->name = resultsMethodName;
+ if (0 != strcmp("void", method->type.type.data)) {
+ resultMethod->parameters.push_back(new Variable(NAMES.Search(method->type.type.data),
+ "_result", method->type.dimension));
+ }
+ arg = method->args;
+ while (arg != NULL) {
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ resultMethod->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ }
+ arg = arg->next;
+ }
+ resultsClass->elements.push_back(resultMethod);
+
+ if (resultMethod->parameters.size() > 0) {
+ proxyClass->elements.push_back(resultsClass);
+ return resultsInterfaceType;
+ }
+ }
+ //delete resultsInterfaceType;
+ return NULL;
+}
+
+static void
+generate_proxy_method(const method_type* method, RpcProxyClass* proxyClass,
+ ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
+{
+ arg_type* arg;
+ Method* proxyMethod = new Method;
+ proxyMethod->comment = gather_comments(method->comments_token->extra);
+ proxyMethod->modifiers = PUBLIC;
+ proxyMethod->returnType = VOID_TYPE;
+ proxyMethod->returnTypeDimension = 0;
+ proxyMethod->name = method->name.data;
+ proxyMethod->statements = new StatementBlock;
+ proxyClass->elements.push_back(proxyMethod);
+
+ // The local variables
+ Variable* _data = new Variable(RPC_DATA_TYPE, "_data");
+ proxyMethod->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE)));
+
+ // Add the arguments
+ arg = method->args;
+ while (arg != NULL) {
+ if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+ // Function signature
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+ proxyMethod->parameters.push_back(v);
+
+ // Input parameter marshalling
+ generate_write_to_data(t, proxyMethod->statements,
+ new StringLiteralExpression(arg->name.data), v, _data);
+ }
+ arg = arg->next;
+ }
+
+ // If there is a results interface for this class
+ Expression* resultParameter;
+ if (resultsInterfaceType != NULL) {
+ // Result interface parameter
+ Variable* resultListener = new Variable(resultsInterfaceType, "_result");
+ proxyMethod->parameters.push_back(resultListener);
+
+ // Add the results dispatcher callback
+ resultsDispatcherClass->needed = true;
+ resultParameter = new NewExpression(resultsDispatcherClass->type, 2,
+ new LiteralExpression(format_int(index)), resultListener);
+ } else {
+ resultParameter = NULL_VALUE;
+ }
+
+ // All proxy methods take an error parameter
+ Variable* errorListener = new Variable(RPC_ERROR_LISTENER_TYPE, "_errors");
+ proxyMethod->parameters.push_back(errorListener);
+
+ // Call the broker
+ proxyMethod->statements->Add(new MethodCall(new FieldVariable(THIS_VALUE, "_broker"),
+ "sendRpc", 5,
+ proxyClass->endpoint,
+ new StringLiteralExpression(method->name.data),
+ new MethodCall(_data, "serialize"),
+ resultParameter,
+ errorListener));
+}
+
+static void
+generate_result_dispatcher_method(const method_type* method,
+ ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
+{
+ arg_type* arg;
+ Method* dispatchMethod;
+ Variable* dispatchParam;
+ resultsDispatcherClass->AddMethod(index, method->name.data, &dispatchMethod, &dispatchParam);
+
+ Variable* classLoader = NULL;
+ Variable* resultData = new Variable(RPC_DATA_TYPE, "resultData");
+ dispatchMethod->statements->Add(new VariableDeclaration(resultData,
+ new NewExpression(RPC_DATA_TYPE, 1, dispatchParam)));
+
+ // The callback method itself
+ MethodCall* realCall = new MethodCall(
+ new Cast(resultsInterfaceType, new FieldVariable(THIS_VALUE, "callback")),
+ results_method_name(method->name.data));
+
+ // The return value
+ {
+ Type* t = NAMES.Search(method->type.type.data);
+ if (t != VOID_TYPE) {
+ Variable* rv = new Variable(t, "rv");
+ dispatchMethod->statements->Add(new VariableDeclaration(rv));
+ generate_create_from_data(t, dispatchMethod->statements, "_result", rv,
+ resultData, &classLoader);
+ realCall->arguments.push_back(rv);
+ }
+ }
+
+ VariableFactory stubArgs("arg");
+ arg = method->args;
+ while (arg != NULL) {
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ // Unmarshall the results
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(t);
+ dispatchMethod->statements->Add(new VariableDeclaration(v));
+
+ generate_create_from_data(t, dispatchMethod->statements, arg->name.data, v,
+ resultData, &classLoader);
+
+ // Add the argument to the callback
+ realCall->arguments.push_back(v);
+ }
+ arg = arg->next;
+ }
+
+ // Call the callback method
+ IfStatement* ifst = new IfStatement;
+ ifst->expression = new Comparison(new FieldVariable(THIS_VALUE, "callback"), "!=", NULL_VALUE);
+ dispatchMethod->statements->Add(ifst);
+ ifst->statements->Add(realCall);
+}
+
+static void
+generate_regular_method(const method_type* method, RpcProxyClass* proxyClass,
+ EndpointBaseClass* serviceBaseClass, ResultDispatcherClass* resultsDispatcherClass,
+ int index)
+{
+ arg_type* arg;
+
+ // == the callback interface for results ================================
+ // the service base class
+ Type* resultsInterfaceType = generate_results_method(method, proxyClass);
+
+ // == the method in the proxy class =====================================
+ generate_proxy_method(method, proxyClass, resultsDispatcherClass, resultsInterfaceType, index);
+
+ // == the method in the result dispatcher class =========================
+ if (resultsInterfaceType != NULL) {
+ generate_result_dispatcher_method(method, resultsDispatcherClass, resultsInterfaceType,
+ index);
+ }
+
+ // == The abstract method that the service developers implement ==========
+ Method* decl = new Method;
+ decl->comment = gather_comments(method->comments_token->extra);
+ decl->modifiers = PUBLIC | ABSTRACT;
+ decl->returnType = NAMES.Search(method->type.type.data);
+ decl->returnTypeDimension = method->type.dimension;
+ decl->name = method->name.data;
+ arg = method->args;
+ while (arg != NULL) {
+ decl->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ arg = arg->next;
+ }
+
+ // Add the default RpcContext param to all methods
+ decl->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
+
+ serviceBaseClass->elements.push_back(decl);
+
+
+ // == the dispatch method in the service base class ======================
+ serviceBaseClass->AddMethod(method);
+}
+
+static void
+generate_event_method(const method_type* method, RpcProxyClass* proxyClass,
+ EndpointBaseClass* serviceBaseClass, ListenerClass* listenerClass,
+ EventListenerClass* presenterClass, int index)
+{
+ arg_type* arg;
+ listenerClass->needed = true;
+
+ // == the push method in the service base class =========================
+ Method* push = new Method;
+ push->modifiers = PUBLIC;
+ push->name = push_method_name(method->name.data);
+ push->statements = new StatementBlock;
+ push->returnType = VOID_TYPE;
+ serviceBaseClass->elements.push_back(push);
+
+ // The local variables
+ Variable* _data = new Variable(RPC_DATA_TYPE, "_data");
+ push->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE)));
+
+ // Add the arguments
+ arg = method->args;
+ while (arg != NULL) {
+ // Function signature
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+ push->parameters.push_back(v);
+
+ // Input parameter marshalling
+ generate_write_to_data(t, push->statements,
+ new StringLiteralExpression(arg->name.data), v, _data);
+
+ arg = arg->next;
+ }
+
+ // Send the notifications
+ push->statements->Add(new MethodCall("pushEvent", 2,
+ new StringLiteralExpression(method->name.data),
+ new MethodCall(_data, "serialize")));
+
+ // == the event callback dispatcher method ====================================
+ presenterClass->AddMethod(method);
+
+ // == the event method in the listener base class =====================
+ Method* event = new Method;
+ event->modifiers = PUBLIC;
+ event->name = method->name.data;
+ event->statements = new StatementBlock;
+ event->returnType = VOID_TYPE;
+ listenerClass->elements.push_back(event);
+ arg = method->args;
+ while (arg != NULL) {
+ event->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ arg = arg->next;
+ }
+
+ // Add a final parameter: RpcContext. Contains data about
+ // incoming request (e.g., certificate)
+ event->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
+}
+
+static void
+generate_listener_methods(RpcProxyClass* proxyClass, Type* presenterType, Type* listenerType)
+{
+ // AndroidAtHomePresenter _presenter;
+ // void startListening(Listener listener) {
+ // stopListening();
+ // _presenter = new Presenter(_broker, listener);
+ // _presenter.startListening(_endpoint);
+ // }
+ // void stopListening() {
+ // if (_presenter != null) {
+ // _presenter.stopListening();
+ // }
+ // }
+
+ Variable* _presenter = new Variable(presenterType, "_presenter");
+ proxyClass->elements.push_back(new Field(PRIVATE, _presenter));
+
+ Variable* listener = new Variable(listenerType, "listener");
+
+ Method* startListeningMethod = new Method;
+ startListeningMethod->modifiers = PUBLIC;
+ startListeningMethod->returnType = VOID_TYPE;
+ startListeningMethod->name = "startListening";
+ startListeningMethod->statements = new StatementBlock;
+ startListeningMethod->parameters.push_back(listener);
+ proxyClass->elements.push_back(startListeningMethod);
+
+ startListeningMethod->statements->Add(new MethodCall(THIS_VALUE, "stopListening"));
+ startListeningMethod->statements->Add(new Assignment(_presenter,
+ new NewExpression(presenterType, 2, proxyClass->broker, listener)));
+ startListeningMethod->statements->Add(new MethodCall(_presenter,
+ "startListening", 1, proxyClass->endpoint));
+
+ Method* stopListeningMethod = new Method;
+ stopListeningMethod->modifiers = PUBLIC;
+ stopListeningMethod->returnType = VOID_TYPE;
+ stopListeningMethod->name = "stopListening";
+ stopListeningMethod->statements = new StatementBlock;
+ proxyClass->elements.push_back(stopListeningMethod);
+
+ IfStatement* ifst = new IfStatement;
+ ifst->expression = new Comparison(_presenter, "!=", NULL_VALUE);
+ stopListeningMethod->statements->Add(ifst);
+
+ ifst->statements->Add(new MethodCall(_presenter, "stopListening"));
+ ifst->statements->Add(new Assignment(_presenter, NULL_VALUE));
+}
+
+Class*
+generate_rpc_interface_class(const interface_type* iface)
+{
+ // the proxy class
+ InterfaceType* interfaceType = static_cast<InterfaceType*>(
+ NAMES.Find(iface->package, iface->name.data));
+ RpcProxyClass* proxy = new RpcProxyClass(iface, interfaceType);
+
+ // the listener class
+ ListenerClass* listener = new ListenerClass(iface);
+
+ // the presenter class
+ EventListenerClass* presenter = new EventListenerClass(iface, listener->type);
+
+ // the service base class
+ EndpointBaseClass* base = new EndpointBaseClass(iface);
+ proxy->elements.push_back(base);
+
+ // the result dispatcher
+ ResultDispatcherClass* results = new ResultDispatcherClass();
+
+ // all the declared methods of the proxy
+ int index = 0;
+ interface_item_type* item = iface->interface_items;
+ while (item != NULL) {
+ if (item->item_type == METHOD_TYPE) {
+ if (NAMES.Search(((method_type*)item)->type.type.data) == EVENT_FAKE_TYPE) {
+ generate_event_method((method_type*)item, proxy, base, listener, presenter, index);
+ } else {
+ generate_regular_method((method_type*)item, proxy, base, results, index);
+ }
+ }
+ item = item->next;
+ index++;
+ }
+ presenter->DoneWithMethods();
+ base->DoneWithMethods();
+
+ // only add this if there are methods with results / out parameters
+ if (results->needed) {
+ proxy->elements.push_back(results);
+ }
+ if (listener->needed) {
+ proxy->elements.push_back(listener);
+ proxy->elements.push_back(presenter);
+ generate_listener_methods(proxy, presenter->type, listener->type);
+ }
+
+ return proxy;
+}
diff --git a/tools/aidl/options.cpp b/tools/aidl/options.cpp
new file mode 100644
index 0000000..7b2daeb
--- /dev/null
+++ b/tools/aidl/options.cpp
@@ -0,0 +1,154 @@
+
+#include "options.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int
+usage()
+{
+ fprintf(stderr,
+ "usage: aidl OPTIONS INPUT [OUTPUT]\n"
+ " aidl --preprocess OUTPUT INPUT...\n"
+ "\n"
+ "OPTIONS:\n"
+ " -I<DIR> search path for import statements.\n"
+ " -d<FILE> generate dependency file.\n"
+ " -a generate dependency file next to the output file with the name based on the input 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.\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;
+}
+
+int
+parse_options(int argc, const char* const* argv, Options *options)
+{
+ int i = 1;
+
+ if (argc >= 2 && 0 == strcmp(argv[1], "--preprocess")) {
+ if (argc < 4) {
+ return usage();
+ }
+ options->outputFileName = argv[2];
+ for (int i=3; i<argc; i++) {
+ options->filesToPreprocess.push_back(argv[i]);
+ }
+ options->task = PREPROCESS_AIDL;
+ return 0;
+ }
+
+ options->task = COMPILE_AIDL;
+ options->failOnParcelable = false;
+ options->autoDepFile = false;
+
+ // OPTIONS
+ while (i < argc) {
+ const char* s = argv[i];
+ int len = strlen(s);
+ if (s[0] == '-') {
+ if (len > 1) {
+ // -I<system-import-path>
+ if (s[1] == 'I') {
+ if (len > 2) {
+ options->importPaths.push_back(s+2);
+ } else {
+ fprintf(stderr, "-I option (%d) requires a path.\n", i);
+ return usage();
+ }
+ }
+ else if (s[1] == 'd') {
+ if (len > 2) {
+ options->depFileName = s+2;
+ } else {
+ fprintf(stderr, "-d option (%d) requires a file.\n", i);
+ return usage();
+ }
+ }
+ else if (s[1] == 'a') {
+ options->autoDepFile = true;
+ }
+ else if (s[1] == 'p') {
+ if (len > 2) {
+ options->preprocessedFiles.push_back(s+2);
+ } else {
+ fprintf(stderr, "-p option (%d) requires a file.\n", i);
+ 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;
+ }
+ else {
+ // s[1] is not known
+ fprintf(stderr, "unknown option (%d): %s\n", i, s);
+ return usage();
+ }
+ } else {
+ // len <= 1
+ fprintf(stderr, "unknown option (%d): %s\n", i, s);
+ return usage();
+ }
+ } else {
+ // s[0] != '-'
+ break;
+ }
+ i++;
+ }
+
+ // INPUT
+ if (i < argc) {
+ options->inputFileName = argv[i];
+ i++;
+ } else {
+ fprintf(stderr, "INPUT required\n");
+ return usage();
+ }
+
+ // OUTPUT
+ if (i < argc) {
+ options->outputFileName = argv[i];
+ i++;
+ } 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;
+ if (options->outputFileName.compare(pos, 5, ".aidl") == 0) { // 5 = strlen(".aidl")
+ options->outputFileName.replace(pos, 5, ".java"); // 5 = strlen(".aidl")
+ } else {
+ fprintf(stderr, "INPUT is not an .aidl file.\n");
+ return usage();
+ }
+ }
+
+ // anything remaining?
+ if (i != argc) {
+ fprintf(stderr, "unknown option%s:", (i==argc-1?(const char*)"":(const char*)"s"));
+ for (; i<argc-1; i++) {
+ fprintf(stderr, " %s", argv[i]);
+ }
+ fprintf(stderr, "\n");
+ return usage();
+ }
+
+ return 0;
+}
+
diff --git a/tools/aidl/options.h b/tools/aidl/options.h
new file mode 100644
index 0000000..387e37d
--- /dev/null
+++ b/tools/aidl/options.h
@@ -0,0 +1,36 @@
+#ifndef DEVICE_TOOLS_AIDL_H
+#define DEVICE_TOOLS_AIDL_H
+
+#include <string.h>
+#include <string>
+#include <vector>
+
+using namespace std;
+
+enum {
+ COMPILE_AIDL,
+ PREPROCESS_AIDL
+};
+
+// This struct is the parsed version of the command line options
+struct Options
+{
+ int task;
+ bool failOnParcelable;
+ vector<string> importPaths;
+ vector<string> preprocessedFiles;
+ string inputFileName;
+ string outputFileName;
+ string outputBaseFolder;
+ string depFileName;
+ bool autoDepFile;
+
+ vector<string> filesToPreprocess;
+};
+
+// takes the inputs from the command line and fills in the Options struct
+// Returns 0 on success, and nonzero on failure.
+// It also prints the usage statement on failure.
+int parse_options(int argc, const char* const* argv, Options *options);
+
+#endif // DEVICE_TOOLS_AIDL_H
diff --git a/tools/aidl/options_test.cpp b/tools/aidl/options_test.cpp
new file mode 100644
index 0000000..bd106ce
--- /dev/null
+++ b/tools/aidl/options_test.cpp
@@ -0,0 +1,291 @@
+#include <iostream>
+#include "options.h"
+
+const bool VERBOSE = false;
+
+using namespace std;
+
+struct Answer {
+ const char* argv[8];
+ int result;
+ const char* systemSearchPath[8];
+ const char* localSearchPath[8];
+ const char* inputFileName;
+ language_t nativeLanguage;
+ const char* outputH;
+ const char* outputCPP;
+ const char* outputJava;
+};
+
+bool
+match_arrays(const char* const*expected, const vector<string> &got)
+{
+ int count = 0;
+ while (expected[count] != NULL) {
+ count++;
+ }
+ if (got.size() != count) {
+ return false;
+ }
+ for (int i=0; i<count; i++) {
+ if (got[i] != expected[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void
+print_array(const char* prefix, const char* const*expected)
+{
+ while (*expected) {
+ cout << prefix << *expected << endl;
+ expected++;
+ }
+}
+
+void
+print_array(const char* prefix, const vector<string> &got)
+{
+ size_t count = got.size();
+ for (size_t i=0; i<count; i++) {
+ cout << prefix << got[i] << endl;
+ }
+}
+
+static int
+test(const Answer& answer)
+{
+ int argc = 0;
+ while (answer.argv[argc]) {
+ argc++;
+ }
+
+ int err = 0;
+
+ Options options;
+ int result = parse_options(argc, answer.argv, &options);
+
+ // result
+ if (((bool)result) != ((bool)answer.result)) {
+ cout << "mismatch: result: got " << result << " expected " <<
+ answer.result << endl;
+ err = 1;
+ }
+
+ if (result != 0) {
+ // if it failed, everything is invalid
+ return err;
+ }
+
+ // systemSearchPath
+ if (!match_arrays(answer.systemSearchPath, options.systemSearchPath)) {
+ cout << "mismatch: systemSearchPath: got" << endl;
+ print_array(" ", options.systemSearchPath);
+ cout << " expected" << endl;
+ print_array(" ", answer.systemSearchPath);
+ err = 1;
+ }
+
+ // localSearchPath
+ if (!match_arrays(answer.localSearchPath, options.localSearchPath)) {
+ cout << "mismatch: localSearchPath: got" << endl;
+ print_array(" ", options.localSearchPath);
+ cout << " expected" << endl;
+ print_array(" ", answer.localSearchPath);
+ err = 1;
+ }
+
+ // inputFileName
+ if (answer.inputFileName != options.inputFileName) {
+ cout << "mismatch: inputFileName: got " << options.inputFileName
+ << " expected " << answer.inputFileName << endl;
+ err = 1;
+ }
+
+ // nativeLanguage
+ if (answer.nativeLanguage != options.nativeLanguage) {
+ cout << "mismatch: nativeLanguage: got " << options.nativeLanguage
+ << " expected " << answer.nativeLanguage << endl;
+ err = 1;
+ }
+
+ // outputH
+ if (answer.outputH != options.outputH) {
+ cout << "mismatch: outputH: got " << options.outputH
+ << " expected " << answer.outputH << endl;
+ err = 1;
+ }
+
+ // outputCPP
+ if (answer.outputCPP != options.outputCPP) {
+ cout << "mismatch: outputCPP: got " << options.outputCPP
+ << " expected " << answer.outputCPP << endl;
+ err = 1;
+ }
+
+ // outputJava
+ if (answer.outputJava != options.outputJava) {
+ cout << "mismatch: outputJava: got " << options.outputJava
+ << " expected " << answer.outputJava << endl;
+ err = 1;
+ }
+
+ return err;
+}
+
+const Answer g_tests[] = {
+
+ {
+ /* argv */ { "test", "-i/moof", "-I/blah", "-Ibleh", "-imoo", "inputFileName.aidl_cpp", NULL, NULL },
+ /* result */ 0,
+ /* systemSearchPath */ { "/blah", "bleh", NULL, NULL, NULL, NULL, NULL, NULL },
+ /* localSearchPath */ { "/moof", "moo", NULL, NULL, NULL, NULL, NULL, NULL },
+ /* inputFileName */ "inputFileName.aidl_cpp",
+ /* nativeLanguage */ CPP,
+ /* outputH */ "",
+ /* outputCPP */ "",
+ /* outputJava */ ""
+ },
+
+ {
+ /* argv */ { "test", "inputFileName.aidl_cpp", "-oh", "outputH", NULL, NULL, NULL, NULL },
+ /* result */ 0,
+ /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* inputFileName */ "inputFileName.aidl_cpp",
+ /* nativeLanguage */ CPP,
+ /* outputH */ "outputH",
+ /* outputCPP */ "",
+ /* outputJava */ ""
+ },
+
+ {
+ /* argv */ { "test", "inputFileName.aidl_cpp", "-ocpp", "outputCPP", NULL, NULL, NULL, NULL },
+ /* result */ 0,
+ /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* inputFileName */ "inputFileName.aidl_cpp",
+ /* nativeLanguage */ CPP,
+ /* outputH */ "",
+ /* outputCPP */ "outputCPP",
+ /* outputJava */ ""
+ },
+
+ {
+ /* argv */ { "test", "inputFileName.aidl_cpp", "-ojava", "outputJava", NULL, NULL, NULL, NULL },
+ /* result */ 0,
+ /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* inputFileName */ "inputFileName.aidl_cpp",
+ /* nativeLanguage */ CPP,
+ /* outputH */ "",
+ /* outputCPP */ "",
+ /* outputJava */ "outputJava"
+ },
+
+ {
+ /* argv */ { "test", "inputFileName.aidl_cpp", "-oh", "outputH", "-ocpp", "outputCPP", "-ojava", "outputJava" },
+ /* result */ 0,
+ /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* inputFileName */ "inputFileName.aidl_cpp",
+ /* nativeLanguage */ CPP,
+ /* outputH */ "outputH",
+ /* outputCPP */ "outputCPP",
+ /* outputJava */ "outputJava"
+ },
+
+ {
+ /* argv */ { "test", "inputFileName.aidl_cpp", "-oh", "outputH", "-oh", "outputH1", NULL, NULL },
+ /* result */ 1,
+ /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* inputFileName */ "",
+ /* nativeLanguage */ CPP,
+ /* outputH */ "",
+ /* outputCPP */ "",
+ /* outputJava */ ""
+ },
+
+ {
+ /* argv */ { "test", "inputFileName.aidl_cpp", "-ocpp", "outputCPP", "-ocpp", "outputCPP1", NULL, NULL },
+ /* result */ 1,
+ /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* inputFileName */ "",
+ /* nativeLanguage */ CPP,
+ /* outputH */ "",
+ /* outputCPP */ "",
+ /* outputJava */ ""
+ },
+
+ {
+ /* argv */ { "test", "inputFileName.aidl_cpp", "-ojava", "outputJava", "-ojava", "outputJava1", NULL, NULL },
+ /* result */ 1,
+ /* systemSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* localSearchPath */ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
+ /* inputFileName */ "",
+ /* nativeLanguage */ CPP,
+ /* outputH */ "",
+ /* outputCPP */ "",
+ /* outputJava */ ""
+ },
+
+};
+
+int
+main(int argc, const char** argv)
+{
+ const int count = sizeof(g_tests)/sizeof(g_tests[0]);
+ int matches[count];
+
+ int result = 0;
+ for (int i=0; i<count; i++) {
+ if (VERBOSE) {
+ cout << endl;
+ cout << "---------------------------------------------" << endl;
+ const char* const* p = g_tests[i].argv;
+ while (*p) {
+ cout << " " << *p;
+ p++;
+ }
+ cout << endl;
+ cout << "---------------------------------------------" << endl;
+ }
+ matches[i] = test(g_tests[i]);
+ if (VERBOSE) {
+ if (0 == matches[i]) {
+ cout << "passed" << endl;
+ } else {
+ cout << "failed" << endl;
+ }
+ result |= matches[i];
+ }
+ }
+
+ cout << endl;
+ cout << "=============================================" << endl;
+ cout << "options_test summary" << endl;
+ cout << "=============================================" << endl;
+
+ if (!result) {
+ cout << "passed" << endl;
+ } else {
+ cout << "failed the following tests:" << endl;
+ for (int i=0; i<count; i++) {
+ if (matches[i]) {
+ cout << " ";
+ const char* const* p = g_tests[i].argv;
+ while (*p) {
+ cout << " " << *p;
+ p++;
+ }
+ cout << endl;
+ }
+ }
+ }
+
+ return result;
+}
+
diff --git a/tools/aidl/search_path.cpp b/tools/aidl/search_path.cpp
new file mode 100644
index 0000000..ffb6cb2
--- /dev/null
+++ b/tools/aidl/search_path.cpp
@@ -0,0 +1,57 @@
+#include <unistd.h>
+#include "search_path.h"
+#include "options.h"
+#include <string.h>
+
+#ifdef HAVE_MS_C_RUNTIME
+#include <io.h>
+#endif
+
+static vector<string> g_importPaths;
+
+void
+set_import_paths(const vector<string>& importPaths)
+{
+ g_importPaths = importPaths;
+}
+
+char*
+find_import_file(const char* given)
+{
+ string expected = given;
+
+ int N = expected.length();
+ for (int i=0; i<N; i++) {
+ char c = expected[i];
+ if (c == '.') {
+ expected[i] = OS_PATH_SEPARATOR;
+ }
+ }
+ expected += ".aidl";
+
+ vector<string>& paths = g_importPaths;
+ for (vector<string>::iterator it=paths.begin(); it!=paths.end(); it++) {
+ string f = *it;
+ if (f.size() == 0) {
+ f = ".";
+ f += OS_PATH_SEPARATOR;
+ }
+ else if (f[f.size()-1] != OS_PATH_SEPARATOR) {
+ f += OS_PATH_SEPARATOR;
+ }
+ f.append(expected);
+
+#ifdef HAVE_MS_C_RUNTIME
+ /* check that the file exists and is not write-only */
+ if (0 == _access(f.c_str(), 0) && /* mode 0=exist */
+ 0 == _access(f.c_str(), 4) ) { /* mode 4=readable */
+#else
+ if (0 == access(f.c_str(), R_OK)) {
+#endif
+ return strdup(f.c_str());
+ }
+ }
+
+ return NULL;
+}
+
diff --git a/tools/aidl/search_path.h b/tools/aidl/search_path.h
new file mode 100644
index 0000000..2bf94b1
--- /dev/null
+++ b/tools/aidl/search_path.h
@@ -0,0 +1,23 @@
+#ifndef DEVICE_TOOLS_AIDL_SEARCH_PATH_H
+#define DEVICE_TOOLS_AIDL_SEARCH_PATH_H
+
+#include <stdio.h>
+
+#if __cplusplus
+#include <vector>
+#include <string>
+using namespace std;
+extern "C" {
+#endif
+
+// returns a FILE* and the char* for the file that it found
+// given is the class name we're looking for
+char* find_import_file(const char* given);
+
+#if __cplusplus
+}; // extern "C"
+void set_import_paths(const vector<string>& importPaths);
+#endif
+
+#endif // DEVICE_TOOLS_AIDL_SEARCH_PATH_H
+
diff --git a/tools/layoutlib/.gitignore b/tools/layoutlib/.gitignore
new file mode 100644
index 0000000..c5e82d7
--- /dev/null
+++ b/tools/layoutlib/.gitignore
@@ -0,0 +1 @@
+bin
\ No newline at end of file
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
new file mode 100644
index 0000000..4e73568
--- /dev/null
+++ b/tools/layoutlib/Android.mk
@@ -0,0 +1,65 @@
+#
+# 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.
+#
+LOCAL_PATH := $(my-dir)
+include $(CLEAR_VARS)
+
+#
+# Define rules to build temp_layoutlib.jar, which contains a subset of
+# the classes in framework.jar. The layoutlib_create tool is used to
+# transform the framework jar into the temp_layoutlib jar.
+#
+
+# We need to process the framework classes.jar file, but we can't
+# depend directly on it (private vars won't be inherited correctly).
+# So, we depend on framework's BUILT file.
+built_framework_dep := $(call java-lib-deps,framework-base)
+built_framework_classes := $(call java-lib-files,framework-base)
+
+built_core_dep := $(call java-lib-deps,core)
+built_core_classes := $(call java-lib-files,core)
+
+built_layoutlib_create_jar := $(call intermediates-dir-for, \
+ JAVA_LIBRARIES,layoutlib_create,HOST)/javalib.jar
+
+# This is mostly a copy of config/host_java_library.mk
+LOCAL_MODULE := temp_layoutlib
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_MODULE_SUFFIX := $(COMMON_JAVA_PACKAGE_SUFFIX)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_BUILT_MODULE_STEM := javalib.jar
+
+#######################################
+include $(BUILD_SYSTEM)/base_rules.mk
+#######################################
+
+$(LOCAL_BUILT_MODULE): $(built_core_dep) \
+ $(built_framework_dep) \
+ $(built_layoutlib_create_jar)
+ $(hide) echo "host layoutlib_create: $@"
+ $(hide) mkdir -p $(dir $@)
+ $(hide) rm -f $@
+ $(hide) ls -l $(built_framework_classes)
+ $(hide) java -jar $(built_layoutlib_create_jar) \
+ $@ \
+ $(built_core_classes) \
+ $(built_framework_classes)
+ $(hide) ls -l $(built_framework_classes)
+
+
+#
+# Include the subdir makefiles.
+#
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/layoutlib/README b/tools/layoutlib/README
new file mode 100644
index 0000000..0fea9bd
--- /dev/null
+++ b/tools/layoutlib/README
@@ -0,0 +1,4 @@
+Layoutlib is a custom version of the android View framework designed to run inside Eclipse.
+The goal of the library is to provide layout rendering in Eclipse that are very very close to their rendering on devices.
+
+None of the com.android.* or android.* classes in layoutlib run on devices.
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/.classpath b/tools/layoutlib/bridge/.classpath
new file mode 100644
index 0000000..2e4274da
--- /dev/null
+++ b/tools/layoutlib/bridge/.classpath
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry excluding="org/kxml2/io/" kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/layoutlib_api/layoutlib_api-prebuilt.jar"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_PLAT_SRC/dalvik/libcore/xml/src/main/java"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/tools-common/tools-common-prebuilt.jar"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/icu4j/icu4j.jar"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/layoutlib/bridge/.project b/tools/layoutlib/bridge/.project
new file mode 100644
index 0000000..e36e71b
--- /dev/null
+++ b/tools/layoutlib/bridge/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>layoutlib_bridge</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/tools/layoutlib/bridge/.settings/README.txt b/tools/layoutlib/bridge/.settings/README.txt
new file mode 100644
index 0000000..9120b20
--- /dev/null
+++ b/tools/layoutlib/bridge/.settings/README.txt
@@ -0,0 +1,2 @@
+Copy this in eclipse project as a .settings folder at the root.
+This ensure proper compilation compliance and warning/error levels.
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs b/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..5381a0e
--- /dev/null
+++ b/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,93 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.nonnull=com.android.annotations.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.android.annotations.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled
+org.eclipse.jdt.core.compiler.annotation.nullable=com.android.annotations.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk
new file mode 100644
index 0000000..e3d48fc
--- /dev/null
+++ b/tools/layoutlib/bridge/Android.mk
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_JAVA_RESOURCE_DIRS := resources
+
+
+LOCAL_JAVA_LIBRARIES := \
+ kxml2-2.3.0 \
+ icu4j \
+ layoutlib_api-prebuilt \
+ tools-common-prebuilt
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ temp_layoutlib \
+ ninepatch-prebuilt
+
+LOCAL_MODULE := layoutlib
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/tools/layoutlib/bridge/resources/bars/action_bar.xml b/tools/layoutlib/bridge/resources/bars/action_bar.xml
new file mode 100644
index 0000000..7adc5af
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/action_bar.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <include layout="@android:layout/action_bar_home" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..84e6bc8
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png
new file mode 100644
index 0000000..38e4f45
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..bf9f300
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png
new file mode 100644
index 0000000..829378e
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png
new file mode 100644
index 0000000..931daed
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png
new file mode 100644
index 0000000..a4be298
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..782ebfe
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..677b471
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..a1b8062
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..fcdbefe
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..633d864
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..4665e2a
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..a00bc5b
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png
new file mode 100644
index 0000000..dc3183b
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..b07f611
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png
new file mode 100644
index 0000000..2773a70
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png
new file mode 100644
index 0000000..6e1ac91
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png
new file mode 100644
index 0000000..eb7c1a4
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/navigation_bar.xml b/tools/layoutlib/bridge/resources/bars/navigation_bar.xml
new file mode 100644
index 0000000..599ca08
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/navigation_bar.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"/>
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"/>
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/status_bar.xml b/tools/layoutlib/bridge/resources/bars/status_bar.xml
new file mode 100644
index 0000000..51b474d
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/status_bar.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginTop="1dp"/>
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginLeft="3dp"
+ android:layout_marginRight="5dp"
+ android:layout_marginTop="1dp"/>
+</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/title_bar.xml b/tools/layoutlib/bridge/resources/bars/title_bar.xml
new file mode 100644
index 0000000..76d78d9
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/title_bar.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..bd60cd6
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png
new file mode 100644
index 0000000..c5bc5c9
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..f621d9c
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png
new file mode 100644
index 0000000..c7fd719
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png
new file mode 100644
index 0000000..625c61d
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png
Binary files differ
diff --git a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
new file mode 100644
index 0000000..b10ec9f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2010 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 android.animation;
+
+import com.android.ide.common.rendering.api.IAnimationListener;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.Result.Status;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+
+import android.os.Handler;
+import android.os.Handler_Delegate;
+import android.os.Handler_Delegate.IHandlerCallback;
+import android.os.Message;
+
+import java.util.PriorityQueue;
+import java.util.Queue;
+
+/**
+ * Abstract animation thread.
+ * <p/>
+ * This does not actually start an animation, instead it fakes a looper that will play whatever
+ * animation is sending messages to its own {@link Handler}.
+ * <p/>
+ * Classes should implement {@link #preAnimation()} and {@link #postAnimation()}.
+ * <p/>
+ * If {@link #preAnimation()} does not start an animation somehow then the thread doesn't do
+ * anything.
+ *
+ */
+public abstract class AnimationThread extends Thread {
+
+ private static class MessageBundle implements Comparable<MessageBundle> {
+ final Handler mTarget;
+ final Message mMessage;
+ final long mUptimeMillis;
+
+ MessageBundle(Handler target, Message message, long uptimeMillis) {
+ mTarget = target;
+ mMessage = message;
+ mUptimeMillis = uptimeMillis;
+ }
+
+ @Override
+ public int compareTo(MessageBundle bundle) {
+ if (mUptimeMillis < bundle.mUptimeMillis) {
+ return -1;
+ }
+ return 1;
+ }
+ }
+
+ private final RenderSessionImpl mSession;
+
+ private Queue<MessageBundle> mQueue = new PriorityQueue<MessageBundle>();
+ private final IAnimationListener mListener;
+
+ public AnimationThread(RenderSessionImpl scene, String threadName,
+ IAnimationListener listener) {
+ super(threadName);
+ mSession = scene;
+ mListener = listener;
+ }
+
+ public abstract Result preAnimation();
+ public abstract void postAnimation();
+
+ @Override
+ public void run() {
+ Bridge.prepareThread();
+ try {
+ /* FIXME: The ANIMATION_FRAME message no longer exists. Instead, the
+ * animation timing loop is completely based on a Choreographer objects
+ * that schedules animation and drawing frames. The animation handler is
+ * no longer even a handler; it is just a Runnable enqueued on the Choreographer.
+ Handler_Delegate.setCallback(new IHandlerCallback() {
+ @Override
+ public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
+ if (msg.what == ValueAnimator.ANIMATION_START ||
+ msg.what == ValueAnimator.ANIMATION_FRAME) {
+ mQueue.add(new MessageBundle(handler, msg, uptimeMillis));
+ } else {
+ // just ignore.
+ }
+ }
+ });
+ */
+
+ // call out to the pre-animation work, which should start an animation or more.
+ Result result = preAnimation();
+ if (result.isSuccess() == false) {
+ mListener.done(result);
+ }
+
+ // loop the animation
+ RenderSession session = mSession.getSession();
+ do {
+ // check early.
+ if (mListener.isCanceled()) {
+ break;
+ }
+
+ // get the next message.
+ MessageBundle bundle = mQueue.poll();
+ if (bundle == null) {
+ break;
+ }
+
+ // sleep enough for this bundle to be on time
+ long currentTime = System.currentTimeMillis();
+ if (currentTime < bundle.mUptimeMillis) {
+ try {
+ sleep(bundle.mUptimeMillis - currentTime);
+ } catch (InterruptedException e) {
+ // FIXME log/do something/sleep again?
+ e.printStackTrace();
+ }
+ }
+
+ // check after sleeping.
+ if (mListener.isCanceled()) {
+ break;
+ }
+
+ // ready to do the work, acquire the scene.
+ result = mSession.acquire(250);
+ if (result.isSuccess() == false) {
+ mListener.done(result);
+ return;
+ }
+
+ // process the bundle. If the animation is not finished, this will enqueue
+ // the next message, so mQueue will have another one.
+ try {
+ // check after acquiring in case it took a while.
+ if (mListener.isCanceled()) {
+ break;
+ }
+
+ bundle.mTarget.handleMessage(bundle.mMessage);
+ if (mSession.render(false /*freshRender*/).isSuccess()) {
+ mListener.onNewFrame(session);
+ }
+ } finally {
+ mSession.release();
+ }
+ } while (mListener.isCanceled() == false && mQueue.size() > 0);
+
+ mListener.done(Status.SUCCESS.createResult());
+
+ } catch (Throwable throwable) {
+ // can't use Bridge.getLog() as the exception might be thrown outside
+ // of an acquire/release block.
+ mListener.done(Status.ERROR_UNKNOWN.createResult("Error playing animation", throwable));
+
+ } finally {
+ postAnimation();
+ Handler_Delegate.setCallback(null);
+ Bridge.cleanupThread();
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
new file mode 100644
index 0000000..7b444aa
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 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 android.animation;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.animation.PropertyValuesHolder
+ *
+ * Through the layoutlib_create tool, the original native methods of PropertyValuesHolder have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ * The main goal of this class' methods are to provide a native way to access setters and getters
+ * on some object. In this case we want to default to using Java reflection instead so the native
+ * methods do nothing.
+ *
+ */
+/*package*/ class PropertyValuesHolder_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static int nGetIntMethod(Class<?> targetClass, String methodName) {
+ // return 0 to force PropertyValuesHolder to use Java reflection.
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nGetFloatMethod(Class<?> targetClass, String methodName) {
+ // return 0 to force PropertyValuesHolder to use Java reflection.
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nCallIntMethod(Object target, int methodID, int arg) {
+ // do nothing
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nCallFloatMethod(Object target, int methodID, float arg) {
+ // do nothing
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java b/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java
new file mode 100644
index 0000000..aabd3f1
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2010 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 android.app;
+
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link Fragment}
+ *
+ * Through the layoutlib_create tool, the original methods of Fragment have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * The methods being re-implemented are the ones responsible for instantiating Fragment objects.
+ * Because the classes of these objects are found in the project, these methods need access to
+ * {@link IProjectCallback} object. They are however static methods, so the callback is set
+ * before the inflation through {@link #setProjectCallback(IProjectCallback)}.
+ */
+public class Fragment_Delegate {
+
+ private static IProjectCallback sProjectCallback;
+
+ /**
+ * Sets the current {@link IProjectCallback} to be used to instantiate classes coming
+ * from the project being rendered.
+ */
+ public static void setProjectCallback(IProjectCallback projectCallback) {
+ sProjectCallback = projectCallback;
+ }
+
+ /**
+ * Like {@link #instantiate(Context, String, Bundle)} but with a null
+ * argument Bundle.
+ */
+ @LayoutlibDelegate
+ /*package*/ static Fragment instantiate(Context context, String fname) {
+ return instantiate(context, fname, null);
+ }
+
+ /**
+ * Create a new instance of a Fragment with the given class name. This is
+ * the same as calling its empty constructor.
+ *
+ * @param context The calling context being used to instantiate the fragment.
+ * This is currently just used to get its ClassLoader.
+ * @param fname The class name of the fragment to instantiate.
+ * @param args Bundle of arguments to supply to the fragment, which it
+ * can retrieve with {@link #getArguments()}. May be null.
+ * @return Returns a new fragment instance.
+ * @throws InstantiationException If there is a failure in instantiating
+ * the given fragment class. This is a runtime exception; it is not
+ * normally expected to happen.
+ */
+ @LayoutlibDelegate
+ /*package*/ static Fragment instantiate(Context context, String fname, Bundle args) {
+ try {
+ if (sProjectCallback != null) {
+ Fragment f = (Fragment) sProjectCallback.loadView(fname,
+ new Class[0], new Object[0]);
+
+ if (args != null) {
+ args.setClassLoader(f.getClass().getClassLoader());
+ f.mArguments = args;
+ }
+ return f;
+ }
+
+ return null;
+ } catch (ClassNotFoundException e) {
+ throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname
+ + ": make sure class name exists, is public, and has an"
+ + " empty constructor that is public", e);
+ } catch (java.lang.InstantiationException e) {
+ throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname
+ + ": make sure class name exists, is public, and has an"
+ + " empty constructor that is public", e);
+ } catch (IllegalAccessException e) {
+ throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname
+ + ": make sure class name exists, is public, and has an"
+ + " empty constructor that is public", e);
+ } catch (Exception e) {
+ throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname
+ + ": make sure class name exists, is public, and has an"
+ + " empty constructor that is public", e);
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
new file mode 100644
index 0000000..a953918
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package android.content.res;
+
+import com.android.layoutlib.bridge.Bridge;
+
+import android.content.res.AssetManager;
+
+public class BridgeAssetManager extends AssetManager {
+
+ /**
+ * 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.
+ */
+ public static AssetManager initSystem() {
+ if (!(AssetManager.sSystem instanceof BridgeAssetManager)) {
+ // Note that AssetManager() creates a system AssetManager and we override it
+ // with our BridgeAssetManager.
+ AssetManager.sSystem = new BridgeAssetManager();
+ AssetManager.sSystem.makeStringBlocks(false);
+ }
+ return AssetManager.sSystem;
+ }
+
+ /**
+ * Clears the static {@link AssetManager#sSystem} to make sure we don't leave objects
+ * around that would prevent us from unloading the library.
+ */
+ public static void clearSystem() {
+ AssetManager.sSystem = null;
+ }
+
+ private BridgeAssetManager() {
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
new file mode 100644
index 0000000..8794452
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
@@ -0,0 +1,695 @@
+/*
+ * 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.
+ */
+
+package android.content.res;
+
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.ninepatch.NinePatch;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.ViewGroup.LayoutParams;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+/**
+ *
+ */
+public final class BridgeResources extends Resources {
+
+ private BridgeContext mContext;
+ private IProjectCallback mProjectCallback;
+ private boolean[] mPlatformResourceFlag = new boolean[1];
+
+ /**
+ * Simpler wrapper around FileInputStream. This is used when the input stream represent
+ * not a normal bitmap but a nine patch.
+ * This is useful when the InputStream is created in a method but used in another that needs
+ * to know whether this is 9-patch or not, such as BitmapFactory.
+ */
+ public class NinePatchInputStream extends FileInputStream {
+ private boolean mFakeMarkSupport = true;
+ public NinePatchInputStream(File file) throws FileNotFoundException {
+ super(file);
+ }
+
+ @Override
+ public boolean markSupported() {
+ if (mFakeMarkSupport) {
+ // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
+ return true;
+ }
+
+ return super.markSupported();
+ }
+
+ public void disableFakeMarkSupport() {
+ // disable fake mark support so that in case codec actually try to use them
+ // we don't lie to them.
+ mFakeMarkSupport = false;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public static Resources initSystem(BridgeContext context,
+ AssetManager assets,
+ DisplayMetrics metrics,
+ Configuration config,
+ IProjectCallback projectCallback) {
+ return Resources.mSystem = new BridgeResources(context,
+ assets,
+ metrics,
+ config,
+ projectCallback);
+ }
+
+ /**
+ * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects
+ * around that would prevent us from unloading the library.
+ */
+ public static void disposeSystem() {
+ 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 = projectCallback;
+ }
+
+ public BridgeTypedArray newTypeArray(int numEntries, boolean platformFile) {
+ return new BridgeTypedArray(this, mContext, numEntries, platformFile);
+ }
+
+ private Pair<String, ResourceValue> getResourceValue(int id, boolean[] platformResFlag_out) {
+ // first get the String related to this id in the framework
+ Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
+
+ if (resourceInfo != null) {
+ platformResFlag_out[0] = true;
+ String attributeName = resourceInfo.getSecond();
+
+ return Pair.of(attributeName, mContext.getRenderResources().getFrameworkResource(
+ resourceInfo.getFirst(), attributeName));
+ }
+
+ // didn't find a match in the framework? look in the project.
+ if (mProjectCallback != null) {
+ resourceInfo = mProjectCallback.resolveResourceId(id);
+
+ if (resourceInfo != null) {
+ platformResFlag_out[0] = false;
+ String attributeName = resourceInfo.getSecond();
+
+ return Pair.of(attributeName, mContext.getRenderResources().getProjectResource(
+ resourceInfo.getFirst(), attributeName));
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public Drawable getDrawable(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ return ResourceHelper.getDrawable(value.getSecond(), mContext);
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @Override
+ public int getColor(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ try {
+ return ResourceHelper.getColor(value.getSecond().getValue());
+ } catch (NumberFormatException e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e,
+ null /*data*/);
+ return 0;
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @Override
+ public ColorStateList getColorStateList(int id) throws NotFoundException {
+ Pair<String, ResourceValue> resValue = getResourceValue(id, mPlatformResourceFlag);
+
+ if (resValue != null) {
+ ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
+ mContext);
+ if (stateList != null) {
+ return stateList;
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @Override
+ public CharSequence getText(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ return v;
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @Override
+ public XmlResourceParser getLayout(int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
+
+ if (v != null) {
+ ResourceValue value = v.getSecond();
+ XmlPullParser parser = null;
+
+ try {
+ // check if the current parser can provide us with a custom parser.
+ if (mPlatformResourceFlag[0] == false) {
+ parser = mProjectCallback.getParser(value);
+ }
+
+ // create a new one manually if needed.
+ if (parser == null) {
+ File xml = new File(value.getValue());
+ if (xml.isFile()) {
+ // we need to create a pull parser around the layout XML file, and then
+ // give that to our XmlBlockParser
+ parser = ParserFactory.create(xml);
+ }
+ }
+
+ if (parser != null) {
+ return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ }
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to configure parser for " + value.getValue(), e, null /*data*/);
+ // we'll return null below.
+ } catch (FileNotFoundException e) {
+ // this shouldn't happen since we check above.
+ }
+
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @Override
+ public XmlResourceParser getAnimation(int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
+
+ if (v != null) {
+ ResourceValue value = v.getSecond();
+ XmlPullParser parser = null;
+
+ try {
+ File xml = new File(value.getValue());
+ if (xml.isFile()) {
+ // we need to create a pull parser around the layout XML file, and then
+ // give that to our XmlBlockParser
+ parser = ParserFactory.create(xml);
+
+ return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ }
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to configure parser for " + value.getValue(), e, null /*data*/);
+ // we'll return null below.
+ } catch (FileNotFoundException e) {
+ // this shouldn't happen since we check above.
+ }
+
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @Override
+ public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
+ return mContext.obtainStyledAttributes(set, attrs);
+ }
+
+ @Override
+ public TypedArray obtainTypedArray(int id) throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public float getDimension(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ if (v.equals(BridgeConstants.MATCH_PARENT) ||
+ v.equals(BridgeConstants.FILL_PARENT)) {
+ return LayoutParams.MATCH_PARENT;
+ } else if (v.equals(BridgeConstants.WRAP_CONTENT)) {
+ return LayoutParams.WRAP_CONTENT;
+ }
+
+ if (ResourceHelper.parseFloatAttribute(
+ value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
+ mTmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return mTmpValue.getDimension(getDisplayMetrics());
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @Override
+ public int getDimensionPixelOffset(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ if (ResourceHelper.parseFloatAttribute(
+ value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
+ mTmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelOffset(mTmpValue.data,
+ getDisplayMetrics());
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @Override
+ public int getDimensionPixelSize(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ if (ResourceHelper.parseFloatAttribute(
+ value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
+ mTmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(mTmpValue.data,
+ getDisplayMetrics());
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @Override
+ public int getInteger(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ int radix = 10;
+ if (v.startsWith("0x")) {
+ v = v.substring(2);
+ radix = 16;
+ }
+ try {
+ return Integer.parseInt(v, radix);
+ } catch (NumberFormatException e) {
+ // return exception below
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return 0;
+ }
+
+ @Override
+ public boolean getBoolean(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ return Boolean.parseBoolean(v);
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return false;
+ }
+
+ @Override
+ public String getResourceEntryName(int resid) throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getResourceName(int resid) throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getResourceTypeName(int resid) throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getString(int id, Object... formatArgs) throws NotFoundException {
+ String s = getString(id);
+ if (s != null) {
+ return String.format(s, formatArgs);
+
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @Override
+ public String getString(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null && value.getSecond().getValue() != null) {
+ return value.getSecond().getValue();
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @Override
+ public void getValue(int id, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ String v = value.getSecond().getValue();
+
+ if (v != null) {
+ if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue,
+ false /*requireUnit*/)) {
+ return;
+ }
+
+ // else it's a string
+ outValue.type = TypedValue.TYPE_STRING;
+ outValue.string = v;
+ return;
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+ }
+
+ @Override
+ public void getValue(String name, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public XmlResourceParser getXml(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ String v = value.getSecond().getValue();
+
+ if (v != null) {
+ // check this is a file
+ File f = new File(v);
+ if (f.isFile()) {
+ try {
+ XmlPullParser parser = ParserFactory.create(f);
+
+ return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ } catch (XmlPullParserException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ } catch (FileNotFoundException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @Override
+ public XmlResourceParser loadXmlResourceParser(String file, int id,
+ int assetCookie, String type) throws NotFoundException {
+ // even though we know the XML file to load directly, we still need to resolve the
+ // id so that we can know if it's a platform or project resource.
+ // (mPlatformResouceFlag will get the result and will be used later).
+ getResourceValue(id, mPlatformResourceFlag);
+
+ File f = new File(file);
+ try {
+ XmlPullParser parser = ParserFactory.create(f);
+
+ return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ } catch (XmlPullParserException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ } catch (FileNotFoundException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ }
+ }
+
+
+ @Override
+ public InputStream openRawResource(int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ String path = value.getSecond().getValue();
+
+ if (path != null) {
+ // check this is a file
+ File f = new File(path);
+ if (f.isFile()) {
+ try {
+ // if it's a nine-patch return a custom input stream so that
+ // other methods (mainly bitmap factory) can detect it's a 9-patch
+ // and actually load it as a 9-patch instead of a normal bitmap
+ if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
+ return new NinePatchInputStream(f);
+ }
+ return new FileInputStream(f);
+ } catch (FileNotFoundException e) {
+ NotFoundException newE = new NotFoundException();
+ newE.initCause(e);
+ throw newE;
+ }
+ }
+ }
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+ @Override
+ public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
+ getValue(id, value, true);
+
+ String path = value.string.toString();
+
+ File f = new File(path);
+ if (f.isFile()) {
+ try {
+ // if it's a nine-patch return a custom input stream so that
+ // other methods (mainly bitmap factory) can detect it's a 9-patch
+ // and actually load it as a 9-patch instead of a normal bitmap
+ if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
+ return new NinePatchInputStream(f);
+ }
+ return new FileInputStream(f);
+ } catch (FileNotFoundException e) {
+ NotFoundException exception = new NotFoundException();
+ exception.initCause(e);
+ throw exception;
+ }
+ }
+
+ throw new NotFoundException();
+ }
+
+ @Override
+ public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource type.
+ * @param id the id of the resource
+ * @throws NotFoundException
+ */
+ private void throwException(int id) throws NotFoundException {
+ // first get the String related to this id in the framework
+ Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
+
+ // if the name is unknown in the framework, get it from the custom view loader.
+ if (resourceInfo == null && mProjectCallback != null) {
+ resourceInfo = mProjectCallback.resolveResourceId(id);
+ }
+
+ String message = null;
+ if (resourceInfo != null) {
+ message = String.format(
+ "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
+ resourceInfo.getFirst(), id, resourceInfo.getSecond());
+ } else {
+ message = String.format(
+ "Could not resolve resource value: 0x%1$X.", id);
+ }
+
+ throw new NotFoundException(message);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
new file mode 100644
index 0000000..446d139
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -0,0 +1,908 @@
+/*
+ * 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.
+ */
+
+package android.content.res;
+
+import com.android.ide.common.rendering.api.AttrResourceValue;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.internal.util.XmlUtils;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.resources.ResourceType;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.LayoutInflater_Delegate;
+import android.view.ViewGroup.LayoutParams;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Custom implementation of TypedArray to handle non compiled resources.
+ */
+public final class BridgeTypedArray extends TypedArray {
+
+ private final BridgeResources mBridgeResources;
+ private final BridgeContext mContext;
+ private final boolean mPlatformFile;
+
+ private ResourceValue[] mResourceData;
+ private String[] mNames;
+ private boolean[] mIsFramework;
+
+ public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len,
+ boolean platformFile) {
+ super(null, null, null, 0);
+ mBridgeResources = resources;
+ mContext = context;
+ mPlatformFile = platformFile;
+ mResourceData = new ResourceValue[len];
+ mNames = new String[len];
+ mIsFramework = new boolean[len];
+ }
+
+ /**
+ * A bridge-specific method that sets a value in the type array
+ * @param index the index of the value in the TypedArray
+ * @param name the name of the attribute
+ * @param isFramework whether the attribute is in the android namespace.
+ * @param value the value of the attribute
+ */
+ public void bridgeSetValue(int index, String name, boolean isFramework, ResourceValue value) {
+ mResourceData[index] = value;
+ mNames[index] = name;
+ mIsFramework[index] = isFramework;
+ }
+
+ /**
+ * Seals the array after all calls to {@link #bridgeSetValue(int, String, ResourceValue)} have
+ * been done.
+ * <p/>This allows to compute the list of non default values, permitting
+ * {@link #getIndexCount()} to return the proper value.
+ */
+ public void sealArray() {
+ // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt
+ // first count the array size
+ int count = 0;
+ for (ResourceValue data : mResourceData) {
+ if (data != null) {
+ count++;
+ }
+ }
+
+ // allocate the table with an extra to store the size
+ mIndices = new int[count+1];
+ mIndices[0] = count;
+
+ // fill the array with the indices.
+ int index = 1;
+ for (int i = 0 ; i < mResourceData.length ; i++) {
+ if (mResourceData[i] != null) {
+ mIndices[index++] = i;
+ }
+ }
+ }
+
+ /**
+ * Return the number of values in this array.
+ */
+ @Override
+ public int length() {
+ return mResourceData.length;
+ }
+
+ /**
+ * Return the Resources object this array was loaded from.
+ */
+ @Override
+ public Resources getResources() {
+ return mBridgeResources;
+ }
+
+ /**
+ * Retrieve the styled string value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return CharSequence holding string data. May be styled. Returns
+ * null if the attribute is not defined.
+ */
+ @Override
+ public CharSequence getText(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (mResourceData[index] != null) {
+ // FIXME: handle styled strings!
+ return mResourceData[index].getValue();
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve the string value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return String holding string data. Any styling information is
+ * removed. Returns null if the attribute is not defined.
+ */
+ @Override
+ public String getString(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (mResourceData[index] != null) {
+ return mResourceData[index].getValue();
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve the boolean value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined.
+ *
+ * @return Attribute boolean value, or defValue if not defined.
+ */
+ @Override
+ public boolean getBoolean(int index, boolean defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ String s = mResourceData[index].getValue();
+ if (s != null) {
+ return XmlUtils.convertValueToBoolean(s, defValue);
+ }
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve the integer value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined.
+ *
+ * @return Attribute int value, or defValue if not defined.
+ */
+ @Override
+ public int getInt(int index, int defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ String s = mResourceData[index].getValue();
+
+ if (RenderResources.REFERENCE_NULL.equals(s)) {
+ return defValue;
+ }
+
+ if (s == null || s.length() == 0) {
+ return defValue;
+ }
+
+ try {
+ return XmlUtils.convertValueToInt(s, defValue);
+ } catch (NumberFormatException e) {
+ // pass
+ }
+
+ // Field is not null and is not an integer.
+ // Check for possible constants and try to find them.
+ // Get the map of attribute-constant -> IntegerValue
+ Map<String, Integer> map = null;
+ if (mIsFramework[index]) {
+ map = Bridge.getEnumValues(mNames[index]);
+ } else {
+ // get the styleable matching the resolved name
+ RenderResources res = mContext.getRenderResources();
+ ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]);
+ if (attr instanceof AttrResourceValue) {
+ map = ((AttrResourceValue) attr).getAttributeValues();
+ }
+ }
+
+ if (map != null) {
+ // accumulator to store the value of the 1+ constants.
+ int result = 0;
+
+ // split the value in case this is a mix of several flags.
+ String[] keywords = s.split("\\|");
+ for (String keyword : keywords) {
+ Integer i = map.get(keyword.trim());
+ if (i != null) {
+ result |= i.intValue();
+ } else {
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
+ String.format(
+ "\"%s\" in attribute \"%2$s\" is not a valid value",
+ keyword, mNames[index]), null /*data*/);
+ }
+ }
+ return result;
+ }
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve the float value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Attribute float value, or defValue if not defined..
+ */
+ @Override
+ public float getFloat(int index, float defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ String s = mResourceData[index].getValue();
+
+ if (s != null) {
+ try {
+ return Float.parseFloat(s);
+ } catch (NumberFormatException e) {
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
+ String.format(
+ "\"%s\" in attribute \"%2$s\" cannot be converted to float.",
+ s, mNames[index]), null /*data*/);
+
+ // we'll return the default value below.
+ }
+ }
+ return defValue;
+ }
+
+ /**
+ * Retrieve the color value for the attribute at <var>index</var>. If
+ * the attribute references a color resource holding a complex
+ * {@link android.content.res.ColorStateList}, then the default color from
+ * the set is returned.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute color value, or defValue if not defined.
+ */
+ @Override
+ public int getColor(int index, int defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ ColorStateList colorStateList = ResourceHelper.getColorStateList(
+ mResourceData[index], mContext);
+ if (colorStateList != null) {
+ return colorStateList.getDefaultColor();
+ }
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve the ColorStateList for the attribute at <var>index</var>.
+ * The value may be either a single solid color or a reference to
+ * a color or complex {@link android.content.res.ColorStateList} description.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return ColorStateList for the attribute, or null if not defined.
+ */
+ @Override
+ public ColorStateList getColorStateList(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (mResourceData[index] == null) {
+ return null;
+ }
+
+ ResourceValue resValue = mResourceData[index];
+ String value = resValue.getValue();
+
+ if (value == null) {
+ return null;
+ }
+
+ if (RenderResources.REFERENCE_NULL.equals(value)) {
+ return null;
+ }
+
+ // let the framework inflate the ColorStateList from the XML file.
+ File f = new File(value);
+ if (f.isFile()) {
+ try {
+ XmlPullParser parser = ParserFactory.create(f);
+
+ BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
+ parser, mContext, resValue.isFramework());
+ try {
+ return ColorStateList.createFromXml(mContext.getResources(), blockParser);
+ } finally {
+ blockParser.ensurePopped();
+ }
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to configure parser for " + value, e, null /*data*/);
+ return null;
+ } catch (Exception e) {
+ // this is an error and not warning since the file existence is checked before
+ // attempting to parse it.
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed to parse file " + value, e, null /*data*/);
+
+ return null;
+ }
+ }
+
+ try {
+ int color = ResourceHelper.getColor(value);
+ return ColorStateList.valueOf(color);
+ } catch (NumberFormatException e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, null /*data*/);
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve the integer value for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute integer value, or defValue if not defined.
+ */
+ @Override
+ public int getInteger(int index, int defValue) {
+ return getInt(index, defValue);
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var>. Unit
+ * conversions are based on the current {@link DisplayMetrics}
+ * associated with the resources this {@link TypedArray} object
+ * came from.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric, or defValue if not defined.
+ *
+ * @see #getDimensionPixelOffset
+ * @see #getDimensionPixelSize
+ */
+ @Override
+ public float getDimension(int index, float defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ String s = mResourceData[index].getValue();
+
+ if (s == null) {
+ return defValue;
+ } else if (s.equals(BridgeConstants.MATCH_PARENT) ||
+ s.equals(BridgeConstants.FILL_PARENT)) {
+ return LayoutParams.MATCH_PARENT;
+ } else if (s.equals(BridgeConstants.WRAP_CONTENT)) {
+ return LayoutParams.WRAP_CONTENT;
+ } else if (RenderResources.REFERENCE_NULL.equals(s)) {
+ return defValue;
+ }
+
+ if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true /*requireUnit*/)) {
+ return mValue.getDimension(mBridgeResources.getDisplayMetrics());
+ }
+
+ // looks like we were unable to resolve the dimension value
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
+ String.format(
+ "\"%1$s\" in attribute \"%2$s\" is not a valid format.",
+ s, mNames[index]), null /*data*/);
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var> for use
+ * as an offset in raw pixels. This is the same as
+ * {@link #getDimension}, except the returned value is converted to
+ * integer pixels for you. An offset conversion involves simply
+ * truncating the base value to an integer.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels, or defValue if not defined.
+ *
+ * @see #getDimension
+ * @see #getDimensionPixelSize
+ */
+ @Override
+ public int getDimensionPixelOffset(int index, int defValue) {
+ return (int) getDimension(index, defValue);
+ }
+
+ /**
+ * Retrieve a dimensional unit attribute at <var>index</var> for use
+ * as a size in raw pixels. This is the same as
+ * {@link #getDimension}, except the returned value is converted to
+ * integer pixels for use as a size. A size conversion involves
+ * rounding the base value, and ensuring that a non-zero base value
+ * is at least one pixel in size.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels, or defValue if not defined.
+ *
+ * @see #getDimension
+ * @see #getDimensionPixelOffset
+ */
+ @Override
+ public int getDimensionPixelSize(int index, int defValue) {
+ try {
+ return getDimension(index);
+ } catch (RuntimeException e) {
+ if (mResourceData[index] != null) {
+ String s = mResourceData[index].getValue();
+
+ if (s != null) {
+ // looks like we were unable to resolve the dimension value
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
+ String.format(
+ "\"%1$s\" in attribute \"%2$s\" is not a valid format.",
+ s, mNames[index]), null /*data*/);
+ }
+ }
+
+ return defValue;
+ }
+ }
+
+ /**
+ * Special version of {@link #getDimensionPixelSize} for retrieving
+ * {@link android.view.ViewGroup}'s layout_width and layout_height
+ * attributes. This is only here for performance reasons; applications
+ * should use {@link #getDimensionPixelSize}.
+ *
+ * @param index Index of the attribute to retrieve.
+ * @param name Textual name of attribute for error reporting.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels.
+ */
+ @Override
+ public int getLayoutDimension(int index, String name) {
+ try {
+ // this will throw an exception
+ return getDimension(index);
+ } catch (RuntimeException e) {
+
+ if (LayoutInflater_Delegate.sIsInInclude) {
+ throw new RuntimeException();
+ }
+
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
+ "You must supply a " + name + " attribute.", null);
+
+ return 0;
+ }
+ }
+
+ @Override
+ public int getLayoutDimension(int index, int defValue) {
+ return getDimensionPixelSize(index, defValue);
+ }
+
+ private int getDimension(int index) {
+ if (mResourceData[index] == null) {
+ throw new RuntimeException();
+ }
+
+ String s = mResourceData[index].getValue();
+
+ if (s == null) {
+ throw new RuntimeException();
+ } else if (s.equals(BridgeConstants.MATCH_PARENT) ||
+ s.equals(BridgeConstants.FILL_PARENT)) {
+ return LayoutParams.MATCH_PARENT;
+ } else if (s.equals(BridgeConstants.WRAP_CONTENT)) {
+ return LayoutParams.WRAP_CONTENT;
+ } else if (RenderResources.REFERENCE_NULL.equals(s)) {
+ throw new RuntimeException();
+ }
+
+ if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true /*requireUnit*/)) {
+ float f = mValue.getDimension(mBridgeResources.getDisplayMetrics());
+
+ final int res = (int)(f+0.5f);
+ if (res != 0) return res;
+ if (f == 0) return 0;
+ if (f > 0) return 1;
+ }
+
+ throw new RuntimeException();
+ }
+
+ /**
+ * Retrieve a fractional unit attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param base The base value of this fraction. In other words, a
+ * standard fraction is multiplied by this value.
+ * @param pbase The parent base value of this fraction. In other
+ * words, a parent fraction (nn%p) is multiplied by this
+ * value.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute fractional value multiplied by the appropriate
+ * base value, or defValue if not defined.
+ */
+ @Override
+ public float getFraction(int index, int base, int pbase, float defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ if (mResourceData[index] == null) {
+ return defValue;
+ }
+
+ String value = mResourceData[index].getValue();
+ if (value == null) {
+ return defValue;
+ }
+
+ if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue,
+ false /*requireUnit*/)) {
+ return mValue.getFraction(base, pbase);
+ }
+
+ // looks like we were unable to resolve the fraction value
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
+ String.format(
+ "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.",
+ value, mNames[index]), null /*data*/);
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve the resource identifier for the attribute at
+ * <var>index</var>. Note that attribute resource as resolved when
+ * the overall {@link TypedArray} object is retrieved. As a
+ * result, this function will return the resource identifier of the
+ * final resource value that was found, <em>not</em> necessarily the
+ * original resource that was specified by the attribute.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or
+ * not a resource.
+ *
+ * @return Attribute resource identifier, or defValue if not defined.
+ */
+ @Override
+ public int getResourceId(int index, int defValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return defValue;
+ }
+
+ // get the Resource for this index
+ ResourceValue resValue = mResourceData[index];
+
+ // no data, return the default value.
+ if (resValue == null) {
+ return defValue;
+ }
+
+ // check if this is a style resource
+ if (resValue instanceof StyleResourceValue) {
+ // get the id that will represent this style.
+ return mContext.getDynamicIdByStyle((StyleResourceValue)resValue);
+ }
+
+ if (RenderResources.REFERENCE_NULL.equals(resValue.getValue())) {
+ return defValue;
+ }
+
+ // if the attribute was a reference to a resource, and not a declaration of an id (@+id),
+ // then the xml attribute value was "resolved" which leads us to a ResourceValue with a
+ // valid getType() and getName() returning a resource name.
+ // (and getValue() returning null!). We need to handle this!
+ if (resValue.getResourceType() != null) {
+ // if this is a framework id
+ if (mPlatformFile || resValue.isFramework()) {
+ // look for idName in the android R classes
+ return mContext.getFrameworkResourceValue(
+ resValue.getResourceType(), resValue.getName(), defValue);
+ }
+
+ // look for idName in the project R class.
+ return mContext.getProjectResourceValue(
+ resValue.getResourceType(), resValue.getName(), defValue);
+ }
+
+ // else, try to get the value, and resolve it somehow.
+ String value = resValue.getValue();
+ if (value == null) {
+ return defValue;
+ }
+
+ // if the value is just an integer, return it.
+ try {
+ int i = Integer.parseInt(value);
+ if (Integer.toString(i).equals(value)) {
+ return i;
+ }
+ } catch (NumberFormatException e) {
+ // pass
+ }
+
+ // Handle the @id/<name>, @+id/<name> and @android:id/<name>
+ // We need to return the exact value that was compiled (from the various R classes),
+ // as these values can be reused internally with calls to findViewById().
+ // There's a trick with platform layouts that not use "android:" but their IDs are in
+ // fact in the android.R and com.android.internal.R classes.
+ // The field mPlatformFile will indicate that all IDs are to be looked up in the android R
+ // classes exclusively.
+
+ // if this is a reference to an id, find it.
+ if (value.startsWith("@id/") || value.startsWith("@+") ||
+ value.startsWith("@android:id/")) {
+
+ int pos = value.indexOf('/');
+ String idName = value.substring(pos + 1);
+
+ // if this is a framework id
+ if (mPlatformFile || value.startsWith("@android") || value.startsWith("@+android")) {
+ // look for idName in the android R classes
+ return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue);
+ }
+
+ // look for idName in the project R class.
+ return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue);
+ }
+
+ // not a direct id valid reference? resolve it
+ Integer idValue = null;
+
+ if (resValue.isFramework()) {
+ idValue = Bridge.getResourceId(resValue.getResourceType(),
+ resValue.getName());
+ } else {
+ idValue = mContext.getProjectCallback().getResourceId(
+ resValue.getResourceType(), resValue.getName());
+ }
+
+ if (idValue != null) {
+ return idValue.intValue();
+ }
+
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE,
+ String.format(
+ "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index]),
+ resValue);
+
+ return defValue;
+ }
+
+ /**
+ * Retrieve the Drawable for the attribute at <var>index</var>. This
+ * gets the resource ID of the selected attribute, and uses
+ * {@link Resources#getDrawable Resources.getDrawable} of the owning
+ * Resources object to retrieve its Drawable.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Drawable for the attribute, or null if not defined.
+ */
+ @Override
+ public Drawable getDrawable(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (mResourceData[index] == null) {
+ return null;
+ }
+
+ ResourceValue value = mResourceData[index];
+ String stringValue = value.getValue();
+ if (stringValue == null || RenderResources.REFERENCE_NULL.equals(stringValue)) {
+ return null;
+ }
+
+ return ResourceHelper.getDrawable(value, mContext);
+ }
+
+
+ /**
+ * Retrieve the CharSequence[] for the attribute at <var>index</var>.
+ * This gets the resource ID of the selected attribute, and uses
+ * {@link Resources#getTextArray Resources.getTextArray} of the owning
+ * Resources object to retrieve its String[].
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return CharSequence[] for the attribute, or null if not defined.
+ */
+ @Override
+ public CharSequence[] getTextArray(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (mResourceData[index] == null) {
+ return null;
+ }
+
+ String value = mResourceData[index].getValue();
+ if (value != null) {
+ if (RenderResources.REFERENCE_NULL.equals(value)) {
+ return null;
+ }
+
+ return new CharSequence[] { value };
+ }
+
+ Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
+ String.format(
+ String.format("Unknown value for getTextArray(%d) => %s", //DEBUG
+ index, mResourceData[index].getName())), null /*data*/);
+
+ return null;
+ }
+
+ /**
+ * Retrieve the raw TypedValue for the attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param outValue TypedValue object in which to place the attribute's
+ * data.
+ *
+ * @return Returns true if the value was retrieved, else false.
+ */
+ @Override
+ public boolean getValue(int index, TypedValue outValue) {
+ if (index < 0 || index >= mResourceData.length) {
+ return false;
+ }
+
+ if (mResourceData[index] == null) {
+ return false;
+ }
+
+ String s = mResourceData[index].getValue();
+
+ return ResourceHelper.parseFloatAttribute(mNames[index], s, outValue,
+ false /*requireUnit*/);
+ }
+
+ /**
+ * Determines whether there is an attribute at <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return True if the attribute has a value, false otherwise.
+ */
+ @Override
+ public boolean hasValue(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return false;
+ }
+
+ return mResourceData[index] != null;
+ }
+
+ /**
+ * Retrieve the raw TypedValue for the attribute at <var>index</var>
+ * and return a temporary object holding its data. This object is only
+ * valid until the next call on to {@link TypedArray}.
+ *
+ * @param index Index of attribute to retrieve.
+ *
+ * @return Returns a TypedValue object if the attribute is defined,
+ * containing its data; otherwise returns null. (You will not
+ * receive a TypedValue whose type is TYPE_NULL.)
+ */
+ @Override
+ public TypedValue peekValue(int index) {
+ if (index < 0 || index >= mResourceData.length) {
+ return null;
+ }
+
+ if (getValue(index, mValue)) {
+ return mValue;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a message about the parser state suitable for printing error messages.
+ */
+ @Override
+ public String getPositionDescription() {
+ return "<internal -- stub if needed>";
+ }
+
+ /**
+ * Give back a previously retrieved TypedArray, for later re-use.
+ */
+ @Override
+ public void recycle() {
+ // pass
+ }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(mResourceData);
+ }
+ }
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java
new file mode 100644
index 0000000..c9d615c
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011 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 android.content.res;
+
+import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link Resources$Theme}
+ *
+ * Through the layoutlib_create tool, the original methods of Theme have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class Resources_Theme_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static TypedArray obtainStyledAttributes(
+ Resources thisResources, Theme thisTheme,
+ int[] attrs) {
+ return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static TypedArray obtainStyledAttributes(
+ Resources thisResources, Theme thisTheme,
+ int resid, int[] attrs)
+ throws NotFoundException {
+ return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid, attrs);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static TypedArray obtainStyledAttributes(
+ Resources thisResources, Theme thisTheme,
+ AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
+ return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(
+ set, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean resolveAttribute(
+ Resources thisResources, Theme thisTheme,
+ int resid, TypedValue outValue,
+ boolean resolveRefs) {
+ return RenderSessionImpl.getCurrentContext().resolveThemeAttribute(
+ resid, outValue, resolveRefs);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java
new file mode 100644
index 0000000..0a7899a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 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 android.content.res;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.util.TypedValue;
+
+public class TypedArray_Delegate {
+
+ @LayoutlibDelegate
+ public static boolean getValueAt(TypedArray theTypedArray, int index, TypedValue outValue) {
+ // pass
+ return false;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java
new file mode 100644
index 0000000..a50a2bd
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/AvoidXfermode_Delegate.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Composite;
+
+/**
+ * Delegate implementing the native methods of android.graphics.AvoidXfermode
+ *
+ * Through the layoutlib_create tool, the original native methods of AvoidXfermode have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original AvoidXfermode class.
+ *
+ * Because this extends {@link Xfermode_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by
+ * {@link Xfermode_Delegate}.
+ *
+ */
+public class AvoidXfermode_Delegate extends Xfermode_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public Composite getComposite(int alpha) {
+ // FIXME
+ return null;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Avoid Xfermodes are not supported in Layout Preview mode.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate(int opColor, int tolerance, int nativeMode) {
+ AvoidXfermode_Delegate newDelegate = new AvoidXfermode_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
new file mode 100644
index 0000000..62d0a0d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2013 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 android.graphics;
+
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.ibm.icu.lang.UScript;
+import com.ibm.icu.lang.UScriptRun;
+
+import android.graphics.Paint_Delegate.FontInfo;
+
+/**
+ * Render the text by breaking it into various scripts and using the right font for each script.
+ * Can be used to measure the text without actually drawing it.
+ */
+@SuppressWarnings("deprecation")
+public class BidiRenderer {
+
+ /* package */ static class ScriptRun {
+ int start;
+ int limit;
+ boolean isRtl;
+ int scriptCode;
+ FontInfo font;
+
+ public ScriptRun(int start, int limit, boolean isRtl) {
+ this.start = start;
+ this.limit = limit;
+ this.isRtl = isRtl;
+ this.scriptCode = UScript.INVALID_CODE;
+ }
+ }
+
+ /* package */ Graphics2D graphics;
+ /* package */ Paint_Delegate paint;
+ /* package */ char[] text;
+
+ /**
+ * @param graphics May be null.
+ * @param paint The Paint to use to get the fonts. Should not be null.
+ * @param text Unidirectional text. Should not be null.
+ */
+ /* package */ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) {
+ assert (paint != null);
+ this.graphics = graphics;
+ this.paint = paint;
+ this.text = text;
+ }
+
+ /**
+ * Render unidirectional text.
+ *
+ * This method can also be used to measure the width of the text without actually drawing it.
+ *
+ * @param start index of the first character
+ * @param limit index of the first character that should not be rendered.
+ * @param isRtl is the text right-to-left
+ * @param advances If not null, then advances for each character to be rendered are returned
+ * here.
+ * @param advancesIndex index into advances from where the advances need to be filled.
+ * @param draw If true and {@link graphics} is not null, draw the rendered text on the graphics
+ * at the given co-ordinates
+ * @param x The x-coordinate of the left edge of where the text should be drawn on the given
+ * graphics.
+ * @param y The y-coordinate at which to draw the text on the given graphics.
+ * @return The x-coordinate of the right edge of the drawn text. In other words,
+ * x + the width of the text.
+ */
+ /* package */ float renderText(int start, int limit, boolean isRtl, float advances[],
+ int advancesIndex, boolean draw, float x, float y) {
+ // We break the text into scripts and then select font based on it and then render each of
+ // the script runs.
+ for (ScriptRun run : getScriptRuns(text, start, limit, isRtl, paint.getFonts())) {
+ int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT;
+ flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT;
+ x = renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw,
+ x, y);
+ advancesIndex += run.limit - run.start;
+ }
+ return x;
+ }
+
+ /**
+ * Render a script run. Use the preferred font to render as much as possible. This also
+ * implements a fallback mechanism to render characters that cannot be drawn using the
+ * preferred font.
+ *
+ * @return x + width of the text drawn.
+ */
+ private float renderScript(int start, int limit, FontInfo preferredFont, int flag,
+ float advances[], int advancesIndex, boolean draw, float x, float y) {
+ List<FontInfo> fonts = paint.getFonts();
+ if (fonts == null || preferredFont == null) {
+ return x;
+ }
+
+ while (start < limit) {
+ boolean foundFont = false;
+ int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(text, start, limit);
+ if (canDisplayUpTo == -1) {
+ return render(start, limit, preferredFont, flag, advances, advancesIndex, draw,
+ x, y);
+ } else if (canDisplayUpTo > start) { // can draw something
+ x = render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex,
+ draw, x, y);
+ advancesIndex += canDisplayUpTo - start;
+ start = canDisplayUpTo;
+ }
+
+ int charCount = Character.isHighSurrogate(text[start]) ? 2 : 1;
+ for (FontInfo font : fonts) {
+ canDisplayUpTo = font.mFont.canDisplayUpTo(text, start, start + charCount);
+ if (canDisplayUpTo == -1) {
+ x = render(start, start+charCount, font, flag, advances, advancesIndex, draw,
+ x, y);
+ start += charCount;
+ advancesIndex += charCount;
+ foundFont = true;
+ break;
+ }
+ }
+ if (!foundFont) {
+ // No font can display this char. Use the preferred font. The char will most
+ // probably appear as a box or a blank space. We could, probably, use some
+ // heuristics and break the character into the base character and diacritics and
+ // then draw it, but it's probably not worth the effort.
+ x = render(start, start + charCount, preferredFont, flag, advances, advancesIndex,
+ draw, x, y);
+ start += charCount;
+ advancesIndex += charCount;
+ }
+ }
+ return x;
+ }
+
+ /**
+ * Render the text with the given font.
+ */
+ private float render(int start, int limit, FontInfo font, int flag, float advances[],
+ int advancesIndex, boolean draw, float x, float y) {
+
+ float totalAdvance = 0;
+ // Since the metrics don't have anti-aliasing set, we create a new FontRenderContext with
+ // the anti-aliasing set.
+ FontRenderContext f = font.mMetrics.getFontRenderContext();
+ FontRenderContext frc = new FontRenderContext(f.getTransform(), paint.isAntiAliased(),
+ f.usesFractionalMetrics());
+ GlyphVector gv = font.mFont.layoutGlyphVector(frc, text, start, limit, flag);
+ int ng = gv.getNumGlyphs();
+ int[] ci = gv.getGlyphCharIndices(0, ng, null);
+ for (int i = 0; i < ng; i++) {
+ float adv = gv.getGlyphMetrics(i).getAdvanceX();
+ if (advances != null) {
+ int adv_idx = advancesIndex + ci[i];
+ advances[adv_idx] += adv;
+ }
+ totalAdvance += adv;
+ }
+ if (draw && graphics != null) {
+ graphics.drawGlyphVector(gv, x, y);
+ }
+ return x + totalAdvance;
+ }
+
+ // --- Static helper methods ---
+
+ /* package */ static List<ScriptRun> getScriptRuns(char[] text, int start, int limit,
+ boolean isRtl, List<FontInfo> fonts) {
+ LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>();
+
+ int count = limit - start;
+ UScriptRun uScriptRun = new UScriptRun(text, start, count);
+ while (uScriptRun.next()) {
+ int scriptStart = uScriptRun.getScriptStart();
+ int scriptLimit = uScriptRun.getScriptLimit();
+ ScriptRun run = new ScriptRun(scriptStart, scriptLimit, isRtl);
+ run.scriptCode = uScriptRun.getScriptCode();
+ setScriptFont(text, run, fonts);
+ scriptRuns.add(run);
+ }
+
+ return scriptRuns;
+ }
+
+ // TODO: Replace this method with one which returns the font based on the scriptCode.
+ private static void setScriptFont(char[] text, ScriptRun run,
+ List<FontInfo> fonts) {
+ for (FontInfo fontInfo : fonts) {
+ if (fontInfo.mFont.canDisplayUpTo(text, run.start, run.limit) == -1) {
+ run.font = fontInfo;
+ return;
+ }
+ }
+ run.font = fonts.get(0);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
new file mode 100644
index 0000000..04ce9d0
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2011 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 android.graphics;
+
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.ninepatch.NinePatchChunk;
+import com.android.resources.Density;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.res.BridgeResources.NinePatchInputStream;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.Bitmap_Delegate.BitmapCreateFlags;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * Delegate implementing the native methods of android.graphics.BitmapFactory
+ *
+ * Through the layoutlib_create tool, the original native methods of BitmapFactory have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ */
+/*package*/ class BitmapFactory_Delegate {
+
+ // ------ Java delegates ------
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
+ if (bm == null || opts == null) {
+ return bm;
+ }
+
+ final int density = opts.inDensity;
+ if (density == 0) {
+ return bm;
+ }
+
+ bm.setDensity(density);
+ final int targetDensity = opts.inTargetDensity;
+ if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
+ return bm;
+ }
+
+ byte[] np = bm.getNinePatchChunk();
+ final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
+ // DELEGATE CHANGE: never scale 9-patch
+ if (opts.inScaled && isNinePatch == false) {
+ float scale = targetDensity / (float)density;
+ // TODO: This is very inefficient and should be done in native by Skia
+ final Bitmap oldBitmap = bm;
+ bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
+ (int) (bm.getHeight() * scale + 0.5f), true);
+ oldBitmap.recycle();
+
+ if (isNinePatch) {
+ np = nativeScaleNinePatch(np, scale, outPadding);
+ bm.setNinePatchChunk(np);
+ }
+ bm.setDensity(targetDensity);
+ }
+
+ return bm;
+ }
+
+
+ // ------ Native Delegates ------
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage,
+ Rect padding, Options opts) {
+ return nativeDecodeStream(is, storage, padding, opts, false, 1.f);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage,
+ Rect padding, Options opts, boolean applyScale, float scale) {
+ Bitmap bm = null;
+
+ //TODO support rescaling
+
+ Density density = Density.MEDIUM;
+ Set<BitmapCreateFlags> bitmapCreateFlags = EnumSet.of(BitmapCreateFlags.MUTABLE);
+ if (opts != null) {
+ density = Density.getEnum(opts.inDensity);
+ if (opts.inPremultiplied) {
+ bitmapCreateFlags.add(BitmapCreateFlags.PREMULTIPLIED);
+ }
+ }
+
+ try {
+ if (is instanceof NinePatchInputStream) {
+ NinePatchInputStream npis = (NinePatchInputStream) is;
+ npis.disableFakeMarkSupport();
+
+ // load the bitmap as a nine patch
+ com.android.ninepatch.NinePatch ninePatch = com.android.ninepatch.NinePatch.load(
+ npis, true /*is9Patch*/, false /*convert*/);
+
+ // get the bitmap and chunk objects.
+ bm = Bitmap_Delegate.createBitmap(ninePatch.getImage(), bitmapCreateFlags,
+ density);
+ NinePatchChunk chunk = ninePatch.getChunk();
+
+ // put the chunk in the bitmap
+ bm.setNinePatchChunk(NinePatch_Delegate.serialize(chunk));
+
+ // read the padding
+ int[] paddingarray = chunk.getPadding();
+ padding.left = paddingarray[0];
+ padding.top = paddingarray[1];
+ padding.right = paddingarray[2];
+ padding.bottom = paddingarray[3];
+ } else {
+ // load the bitmap directly.
+ bm = Bitmap_Delegate.createBitmap(is, bitmapCreateFlags, density);
+ }
+ } catch (IOException e) {
+ Bridge.getLog().error(null,"Failed to load image" , e, null);
+ }
+
+ return bm;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
+ Rect padding, Options opts) {
+ opts.inBitmap = null;
+ return null;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts) {
+ opts.inBitmap = null;
+ return null;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts,
+ boolean applyScale, float scale) {
+ opts.inBitmap = null;
+ return null;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap nativeDecodeByteArray(byte[] data, int offset,
+ int length, Options opts) {
+ opts.inBitmap = null;
+ return null;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static byte[] nativeScaleNinePatch(byte[] chunk, float scale, Rect pad) {
+ // don't scale for now. This should not be called anyway since we re-implement
+ // BitmapFactory.finishDecode();
+ return chunk;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeIsSeekable(FileDescriptor fd) {
+ return true;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
new file mode 100644
index 0000000..65a75b0
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Shader.TileMode;
+
+/**
+ * Delegate implementing the native methods of android.graphics.BitmapShader
+ *
+ * Through the layoutlib_create tool, the original native methods of BitmapShader have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original BitmapShader class.
+ *
+ * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
+ *
+ * @see Shader_Delegate
+ *
+ */
+public class BitmapShader_Delegate extends Shader_Delegate {
+
+ // ---- delegate data ----
+ private java.awt.Paint mJavaPaint;
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public java.awt.Paint getJavaPaint() {
+ return mJavaPaint;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return true;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ // no message since isSupported returns true;
+ return null;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate(int native_bitmap, int shaderTileModeX,
+ int shaderTileModeY) {
+ Bitmap_Delegate bitmap = Bitmap_Delegate.getDelegate(native_bitmap);
+ if (bitmap == null) {
+ return 0;
+ }
+
+ BitmapShader_Delegate newDelegate = new BitmapShader_Delegate(
+ bitmap.getImage(),
+ Shader_Delegate.getTileMode(shaderTileModeX),
+ Shader_Delegate.getTileMode(shaderTileModeY));
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativePostCreate(int native_shader, int native_bitmap,
+ int shaderTileModeX, int shaderTileModeY) {
+ // pass, not needed.
+ return 0;
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ private BitmapShader_Delegate(java.awt.image.BufferedImage image,
+ TileMode tileModeX, TileMode tileModeY) {
+ mJavaPaint = new BitmapShaderPaint(image, tileModeX, tileModeY);
+ }
+
+ private class BitmapShaderPaint implements java.awt.Paint {
+ private final java.awt.image.BufferedImage mImage;
+ private final TileMode mTileModeX;
+ private final TileMode mTileModeY;
+
+ BitmapShaderPaint(java.awt.image.BufferedImage image,
+ TileMode tileModeX, TileMode tileModeY) {
+ mImage = image;
+ mTileModeX = tileModeX;
+ mTileModeY = tileModeY;
+ }
+
+ @Override
+ public java.awt.PaintContext createContext(
+ java.awt.image.ColorModel colorModel,
+ java.awt.Rectangle deviceBounds,
+ java.awt.geom.Rectangle2D userBounds,
+ java.awt.geom.AffineTransform xform,
+ java.awt.RenderingHints hints) {
+
+ java.awt.geom.AffineTransform canvasMatrix;
+ try {
+ canvasMatrix = xform.createInverse();
+ } catch (java.awt.geom.NoninvertibleTransformException e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+ "Unable to inverse matrix in BitmapShader", e, null /*data*/);
+ canvasMatrix = new java.awt.geom.AffineTransform();
+ }
+
+ java.awt.geom.AffineTransform localMatrix = getLocalMatrix();
+ try {
+ localMatrix = localMatrix.createInverse();
+ } catch (java.awt.geom.NoninvertibleTransformException e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+ "Unable to inverse matrix in BitmapShader", e, null /*data*/);
+ localMatrix = new java.awt.geom.AffineTransform();
+ }
+
+ return new BitmapShaderContext(canvasMatrix, localMatrix, colorModel);
+ }
+
+ private class BitmapShaderContext implements java.awt.PaintContext {
+
+ private final java.awt.geom.AffineTransform mCanvasMatrix;
+ private final java.awt.geom.AffineTransform mLocalMatrix;
+ private final java.awt.image.ColorModel mColorModel;
+
+ public BitmapShaderContext(
+ java.awt.geom.AffineTransform canvasMatrix,
+ java.awt.geom.AffineTransform localMatrix,
+ java.awt.image.ColorModel colorModel) {
+ mCanvasMatrix = canvasMatrix;
+ mLocalMatrix = localMatrix;
+ mColorModel = colorModel;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public java.awt.image.ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ @Override
+ public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+ java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h,
+ java.awt.image.BufferedImage.TYPE_INT_ARGB);
+
+ int[] data = new int[w*h];
+
+ int index = 0;
+ float[] pt1 = new float[2];
+ float[] pt2 = new float[2];
+ for (int iy = 0 ; iy < h ; iy++) {
+ for (int ix = 0 ; ix < w ; ix++) {
+ // handle the canvas transform
+ pt1[0] = x + ix;
+ pt1[1] = y + iy;
+ mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
+
+ // handle the local matrix.
+ pt1[0] = pt2[0];
+ pt1[1] = pt2[1];
+ mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
+
+ data[index++] = getColor(pt2[0], pt2[1]);
+ }
+ }
+
+ image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
+
+ return image.getRaster();
+ }
+ }
+
+ /**
+ * Returns a color for an arbitrary point.
+ */
+ private int getColor(float fx, float fy) {
+ int x = getCoordinate(Math.round(fx), mImage.getWidth(), mTileModeX);
+ int y = getCoordinate(Math.round(fy), mImage.getHeight(), mTileModeY);
+
+ return mImage.getRGB(x, y);
+ }
+
+ private int getCoordinate(int i, int size, TileMode mode) {
+ if (i < 0) {
+ switch (mode) {
+ case CLAMP:
+ i = 0;
+ break;
+ case REPEAT:
+ i = size - 1 - (-i % size);
+ break;
+ case MIRROR:
+ // this is the same as the positive side, just make the value positive
+ // first.
+ i = -i;
+ int count = i / size;
+ i = i % size;
+
+ if ((count % 2) == 1) {
+ i = size - 1 - i;
+ }
+ break;
+ }
+ } else if (i >= size) {
+ switch (mode) {
+ case CLAMP:
+ i = size - 1;
+ break;
+ case REPEAT:
+ i = i % size;
+ break;
+ case MIRROR:
+ int count = i / size;
+ i = i % size;
+
+ if ((count % 2) == 1) {
+ i = size - 1 - i;
+ }
+ break;
+ }
+ }
+
+ return i;
+ }
+
+
+ @Override
+ public int getTransparency() {
+ return java.awt.Paint.TRANSLUCENT;
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
new file mode 100644
index 0000000..2787eca
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -0,0 +1,656 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.resources.Density;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Bitmap.Config;
+import android.os.Parcel;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.Buffer;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Set;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Bitmap
+ *
+ * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Bitmap class.
+ *
+ * @see DelegateManager
+ *
+ */
+public final class Bitmap_Delegate {
+
+ public enum BitmapCreateFlags {
+ PREMULTIPLIED, MUTABLE
+ }
+
+ // ---- delegate manager ----
+ private static final DelegateManager<Bitmap_Delegate> sManager =
+ new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class);
+
+ // ---- delegate helper data ----
+
+ // ---- delegate data ----
+ private final Config mConfig;
+ private BufferedImage mImage;
+ private boolean mHasAlpha = true;
+ private boolean mHasMipMap = false; // TODO: check the default.
+ private int mGenerationId = 0;
+
+
+ // ---- Public Helper methods ----
+
+ /**
+ * Returns the native delegate associated to a given {@link Bitmap_Delegate} object.
+ */
+ public static Bitmap_Delegate getDelegate(Bitmap bitmap) {
+ return sManager.getDelegate(bitmap.mNativeBitmap);
+ }
+
+ /**
+ * Returns the native delegate associated to a given an int referencing a {@link Bitmap} object.
+ */
+ public static Bitmap_Delegate getDelegate(int native_bitmap) {
+ return sManager.getDelegate(native_bitmap);
+ }
+
+ /**
+ * Creates and returns a {@link Bitmap} initialized with the given file content.
+ *
+ * @param input the file from which to read the bitmap content
+ * @param isMutable whether the bitmap is mutable
+ * @param density the density associated with the bitmap
+ *
+ * @see Bitmap#isMutable()
+ * @see Bitmap#getDensity()
+ */
+ public static Bitmap createBitmap(File input, boolean isMutable, Density density)
+ throws IOException {
+ return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
+ }
+ /**
+ * Creates and returns a {@link Bitmap} initialized with the given file content.
+ *
+ * @param input the file from which to read the bitmap content
+ * @param density the density associated with the bitmap
+ *
+ * @see Bitmap#isPremultiplied()
+ * @see Bitmap#isMutable()
+ * @see Bitmap#getDensity()
+ */
+ public static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
+ Density density) throws IOException {
+ // create a delegate with the content of the file.
+ Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
+
+ return createBitmap(delegate, createFlags, density.getDpiValue());
+ }
+
+ /**
+ * Creates and returns a {@link Bitmap} initialized with the given stream content.
+ *
+ * @param input the stream from which to read the bitmap content
+ * @param isMutable whether the bitmap is mutable
+ * @param density the density associated with the bitmap
+ *
+ * @see Bitmap#isMutable()
+ * @see Bitmap#getDensity()
+ */
+ public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density)
+ throws IOException {
+ return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
+ }
+
+ /**
+ * Creates and returns a {@link Bitmap} initialized with the given stream content.
+ *
+ * @param input the stream from which to read the bitmap content
+ * @param createFlags
+ * @param density the density associated with the bitmap
+ *
+ * @see Bitmap#isPremultiplied()
+ * @see Bitmap#isMutable()
+ * @see Bitmap#getDensity()
+ */
+ public static Bitmap createBitmap(InputStream input, Set<BitmapCreateFlags> createFlags,
+ Density density) throws IOException {
+ // create a delegate with the content of the stream.
+ Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
+
+ return createBitmap(delegate, createFlags, density.getDpiValue());
+ }
+
+ /**
+ * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
+ *
+ * @param image the bitmap content
+ * @param isMutable whether the bitmap is mutable
+ * @param density the density associated with the bitmap
+ *
+ * @see Bitmap#isMutable()
+ * @see Bitmap#getDensity()
+ */
+ public static Bitmap createBitmap(BufferedImage image, boolean isMutable,
+ Density density) throws IOException {
+ return createBitmap(image, getPremultipliedBitmapCreateFlags(isMutable), density);
+ }
+
+ /**
+ * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
+ *
+ * @param image the bitmap content
+ * @param createFlags
+ * @param density the density associated with the bitmap
+ *
+ * @see Bitmap#isMutable()
+ * @see Bitmap#getDensity()
+ */
+ public static Bitmap createBitmap(BufferedImage image, Set<BitmapCreateFlags> createFlags,
+ Density density) throws IOException {
+ // create a delegate with the given image.
+ Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
+
+ return createBitmap(delegate, createFlags, density.getDpiValue());
+ }
+
+ /**
+ * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}.
+ */
+ public static BufferedImage getImage(Bitmap bitmap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(bitmap.mNativeBitmap);
+ if (delegate == null) {
+ return null;
+ }
+
+ return delegate.mImage;
+ }
+
+ public static int getBufferedImageType(int nativeBitmapConfig) {
+ switch (Config.nativeToConfig(nativeBitmapConfig)) {
+ case ALPHA_8:
+ return BufferedImage.TYPE_INT_ARGB;
+ case RGB_565:
+ return BufferedImage.TYPE_INT_ARGB;
+ case ARGB_4444:
+ return BufferedImage.TYPE_INT_ARGB;
+ case ARGB_8888:
+ return BufferedImage.TYPE_INT_ARGB;
+ }
+
+ return BufferedImage.TYPE_INT_ARGB;
+ }
+
+ /**
+ * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}.
+ */
+ public BufferedImage getImage() {
+ return mImage;
+ }
+
+ /**
+ * Returns the Android bitmap config. Note that this not the config of the underlying
+ * Java2D bitmap.
+ */
+ public Config getConfig() {
+ return mConfig;
+ }
+
+ /**
+ * Returns the hasAlpha rendering hint
+ * @return true if the bitmap alpha should be used at render time
+ */
+ public boolean hasAlpha() {
+ return mHasAlpha && mConfig != Config.RGB_565;
+ }
+
+ public boolean hasMipMap() {
+ // TODO: check if more checks are required as in hasAlpha.
+ return mHasMipMap;
+ }
+ /**
+ * Update the generationId.
+ *
+ * @see Bitmap#getGenerationId()
+ */
+ public void change() {
+ mGenerationId++;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
+ int height, int nativeConfig, boolean isMutable) {
+ int imageType = getBufferedImageType(nativeConfig);
+
+ // create the image
+ BufferedImage image = new BufferedImage(width, height, imageType);
+
+ if (colors != null) {
+ image.setRGB(0, 0, width, height, colors, offset, stride);
+ }
+
+ // create a delegate with the content of the stream.
+ Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
+
+ return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
+ Bitmap.getDefaultDensity());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) {
+ Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap);
+ if (srcBmpDelegate == null) {
+ return null;
+ }
+
+ BufferedImage srcImage = srcBmpDelegate.getImage();
+
+ int width = srcImage.getWidth();
+ int height = srcImage.getHeight();
+
+ int imageType = getBufferedImageType(nativeConfig);
+
+ // create the image
+ BufferedImage image = new BufferedImage(width, height, imageType);
+
+ // copy the source image into the image.
+ int[] argb = new int[width * height];
+ srcImage.getRGB(0, 0, width, height, argb, 0, width);
+ image.setRGB(0, 0, width, height, argb, 0, width);
+
+ // create a delegate with the content of the stream.
+ Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
+
+ return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
+ Bitmap.getDefaultDensity());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDestructor(int nativeBitmap) {
+ sManager.removeJavaReferenceFor(nativeBitmap);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeRecycle(int nativeBitmap) {
+ sManager.removeJavaReferenceFor(nativeBitmap);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality,
+ OutputStream stream, byte[] tempStorage) {
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "Bitmap.compress() is not supported", null /*data*/);
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeErase(int nativeBitmap, int color) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return;
+ }
+
+ BufferedImage image = delegate.mImage;
+
+ Graphics2D g = image.createGraphics();
+ try {
+ g.setColor(new java.awt.Color(color, true));
+
+ g.fillRect(0, 0, image.getWidth(), image.getHeight());
+ } finally {
+ g.dispose();
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeWidth(int nativeBitmap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mImage.getWidth();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeHeight(int nativeBitmap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mImage.getHeight();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeRowBytes(int nativeBitmap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mImage.getWidth();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeConfig(int nativeBitmap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mConfig.nativeInt;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeHasAlpha(int nativeBitmap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return true;
+ }
+
+ return delegate.mHasAlpha;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeHasMipMap(int nativeBitmap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return true;
+ }
+
+ return delegate.mHasMipMap;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mImage.getRGB(x, y);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset,
+ int stride, int x, int y, int width, int height) {
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride);
+ }
+
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) {
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.getImage().setRGB(x, y, color);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset,
+ int stride, int x, int y, int width, int height) {
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.getImage().setRGB(x, y, width, height, colors, offset, stride);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) {
+ // FIXME implement native delegate
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) {
+ // FIXME implement native delegate
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeGenerationId(int nativeBitmap) {
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mGenerationId;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) {
+ // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
+ // used during aidl call so really this should not be called.
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.",
+ null /*data*/);
+ return null;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable,
+ int density, Parcel p) {
+ // This is only called when sending a bitmap through aidl, so really this should not
+ // be called.
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.",
+ null /*data*/);
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint,
+ int[] offsetXY) {
+ Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap);
+ if (bitmap == null) {
+ return null;
+ }
+
+ // get the paint which can be null if nativePaint is 0.
+ Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
+
+ if (paint != null && paint.getMaskFilter() != null) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
+ "MaskFilter not supported in Bitmap.extractAlpha",
+ null, null /*data*/);
+ }
+
+ int alpha = paint != null ? paint.getAlpha() : 0xFF;
+ BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha);
+
+ // create the delegate. The actual Bitmap config is only an alpha channel
+ Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
+
+ // the density doesn't matter, it's set by the Java method.
+ return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.MUTABLE),
+ Density.DEFAULT_DENSITY /*density*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativePrepareToDraw(int nativeBitmap) {
+ // nothing to be done here.
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mHasAlpha = hasAlpha;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeSetHasMipMap(int nativeBitmap, boolean hasMipMap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mHasMipMap = hasMipMap;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeSameAs(int nb0, int nb1) {
+ Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
+ if (delegate1 == null) {
+ return false;
+ }
+
+ Bitmap_Delegate delegate2 = sManager.getDelegate(nb1);
+ if (delegate2 == null) {
+ return false;
+ }
+
+ BufferedImage image1 = delegate1.getImage();
+ BufferedImage image2 = delegate2.getImage();
+ if (delegate1.mConfig != delegate2.mConfig ||
+ image1.getWidth() != image2.getWidth() ||
+ image1.getHeight() != image2.getHeight()) {
+ return false;
+ }
+
+ // get the internal data
+ int w = image1.getWidth();
+ int h = image2.getHeight();
+ int[] argb1 = new int[w*h];
+ int[] argb2 = new int[w*h];
+
+ image1.getRGB(0, 0, w, h, argb1, 0, w);
+ image2.getRGB(0, 0, w, h, argb2, 0, w);
+
+ // compares
+ if (delegate1.mConfig == Config.ALPHA_8) {
+ // in this case we have to manually compare the alpha channel as the rest is garbage.
+ final int length = w*h;
+ for (int i = 0 ; i < length ; i++) {
+ if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return Arrays.equals(argb1, argb2);
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ private Bitmap_Delegate(BufferedImage image, Config config) {
+ mImage = image;
+ mConfig = config;
+ }
+
+ private static Bitmap createBitmap(Bitmap_Delegate delegate,
+ Set<BitmapCreateFlags> createFlags, int density) {
+ // get its native_int
+ int nativeInt = sManager.addNewDelegate(delegate);
+
+ int width = delegate.mImage.getWidth();
+ int height = delegate.mImage.getHeight();
+ boolean isMutable = createFlags.contains(BitmapCreateFlags.MUTABLE);
+ boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED);
+
+ // and create/return a new Bitmap with it
+ return new Bitmap(nativeInt, null /* buffer */, width, height, density, isMutable,
+ isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */);
+ }
+
+ private static Set<BitmapCreateFlags> getPremultipliedBitmapCreateFlags(boolean isMutable) {
+ Set<BitmapCreateFlags> createFlags = EnumSet.of(BitmapCreateFlags.PREMULTIPLIED);
+ if (isMutable) {
+ createFlags.add(BitmapCreateFlags.MUTABLE);
+ }
+ return createFlags;
+ }
+ /**
+ * Creates and returns a copy of a given BufferedImage.
+ * <p/>
+ * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
+ *
+ * @param image the image to copy
+ * @param imageType the type of the new image
+ * @param alpha an optional alpha modifier
+ * @return a new BufferedImage
+ */
+ /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
+ int w = image.getWidth();
+ int h = image.getHeight();
+
+ BufferedImage result = new BufferedImage(w, h, imageType);
+
+ int[] argb = new int[w * h];
+ image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
+
+ if (alpha != 255) {
+ final int length = argb.length;
+ for (int i = 0 ; i < length; i++) {
+ int a = (argb[i] >>> 24 * alpha) / 255;
+ argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
+ }
+ }
+
+ result.setRGB(0, 0, w, h, argb, 0, w);
+
+ return result;
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java
new file mode 100644
index 0000000..4becba1
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.BlurMaskFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of BlurMaskFilter have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original BlurMaskFilter class.
+ *
+ * Because this extends {@link MaskFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link MaskFilter_Delegate}.
+ *
+ * @see MaskFilter_Delegate
+ *
+ */
+public class BlurMaskFilter_Delegate extends MaskFilter_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Blur Mask Filters are not supported.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeConstructor(float radius, int style) {
+ BlurMaskFilter_Delegate newDelegate = new BlurMaskFilter_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
new file mode 100644
index 0000000..62b47bd
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -0,0 +1,1289 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.GcSnapshot;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Bitmap.Config;
+import android.text.TextUtils;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.image.BufferedImage;
+
+
+/**
+ * Delegate implementing the native methods of android.graphics.Canvas
+ *
+ * Through the layoutlib_create tool, the original native methods of Canvas have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Canvas class.
+ *
+ * @see DelegateManager
+ *
+ */
+public final class Canvas_Delegate {
+
+ // ---- delegate manager ----
+ private static final DelegateManager<Canvas_Delegate> sManager =
+ new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class);
+
+ // ---- delegate helper data ----
+
+ private final static boolean[] sBoolOut = new boolean[1];
+
+ // ---- delegate data ----
+ private Bitmap_Delegate mBitmap;
+ private GcSnapshot mSnapshot;
+
+ private DrawFilter_Delegate mDrawFilter = null;
+
+ // ---- Public Helper methods ----
+
+ /**
+ * Returns the native delegate associated to a given {@link Canvas} object.
+ */
+ public static Canvas_Delegate getDelegate(Canvas canvas) {
+ return sManager.getDelegate(canvas.mNativeCanvas);
+ }
+
+ /**
+ * Returns the native delegate associated to a given an int referencing a {@link Canvas} object.
+ */
+ public static Canvas_Delegate getDelegate(int native_canvas) {
+ return sManager.getDelegate(native_canvas);
+ }
+
+ /**
+ * Returns the current {@link Graphics2D} used to draw.
+ */
+ public GcSnapshot getSnapshot() {
+ return mSnapshot;
+ }
+
+ /**
+ * Returns the {@link DrawFilter} delegate or null if none have been set.
+ *
+ * @return the delegate or null.
+ */
+ public DrawFilter_Delegate getDrawFilter() {
+ return mDrawFilter;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static boolean isOpaque(Canvas thisCanvas) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return false;
+ }
+
+ return canvasDelegate.mBitmap.getConfig() == Config.RGB_565;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int getWidth(Canvas thisCanvas) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return 0;
+ }
+
+ return canvasDelegate.mBitmap.getImage().getWidth();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int getHeight(Canvas thisCanvas) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return 0;
+ }
+
+ return canvasDelegate.mBitmap.getImage().getHeight();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void translate(Canvas thisCanvas, float dx, float dy) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ canvasDelegate.getSnapshot().translate(dx, dy);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void rotate(Canvas thisCanvas, float degrees) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void scale(Canvas thisCanvas, float sx, float sy) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ canvasDelegate.getSnapshot().scale(sx, sy);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void skew(Canvas thisCanvas, float kx, float ky) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ // get the current top graphics2D object.
+ GcSnapshot g = canvasDelegate.getSnapshot();
+
+ // get its current matrix
+ AffineTransform currentTx = g.getTransform();
+ // get the AffineTransform for the given skew.
+ float[] mtx = Matrix_Delegate.getSkew(kx, ky);
+ AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx);
+
+ // combine them so that the given matrix is applied after.
+ currentTx.preConcatenate(matrixTx);
+
+ // give it to the graphics2D as a new matrix replacing all previous transform
+ g.setTransform(currentTx);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean clipRect(Canvas thisCanvas, RectF rect) {
+ return clipRect(thisCanvas, rect.left, rect.top, rect.right, rect.bottom);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean clipRect(Canvas thisCanvas, Rect rect) {
+ return clipRect(thisCanvas, (float) rect.left, (float) rect.top,
+ (float) rect.right, (float) rect.bottom);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean clipRect(Canvas thisCanvas, float left, float top, float right,
+ float bottom) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return false;
+ }
+
+ return canvasDelegate.clipRect(left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean clipRect(Canvas thisCanvas, int left, int top, int right,
+ int bottom) {
+
+ return clipRect(thisCanvas, (float) left, (float) top, (float) right, (float) bottom);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int save(Canvas thisCanvas) {
+ return save(thisCanvas, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int save(Canvas thisCanvas, int saveFlags) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return 0;
+ }
+
+ return canvasDelegate.save(saveFlags);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void restore(Canvas thisCanvas) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ canvasDelegate.restore();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int getSaveCount(Canvas thisCanvas) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return 0;
+ }
+
+ return canvasDelegate.getSnapshot().size();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ canvasDelegate.restoreTo(saveCount);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void drawPoints(Canvas thisCanvas, float[] pts, int offset, int count,
+ Paint paint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawPoint is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void drawPoint(Canvas thisCanvas, float x, float y, Paint paint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawPoint is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void drawLines(Canvas thisCanvas,
+ final float[] pts, final int offset, final int count,
+ Paint paint) {
+ draw(thisCanvas.mNativeCanvas, paint.mNativePaint, false /*compositeOnly*/,
+ false /*forceSrcMode*/, new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ for (int i = 0 ; i < count ; i += 4) {
+ graphics.drawLine((int)pts[i + offset], (int)pts[i + offset + 1],
+ (int)pts[i + offset + 2], (int)pts[i + offset + 3]);
+ }
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void freeCaches() {
+ // nothing to be done here.
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void freeTextLayoutCaches() {
+ // nothing to be done here yet.
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int initRaster(int nativeBitmapOrZero) {
+ if (nativeBitmapOrZero > 0) {
+ // get the Bitmap from the int
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero);
+
+ // create a new Canvas_Delegate with the given bitmap and return its new native int.
+ Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate);
+
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // create a new Canvas_Delegate and return its new native int.
+ Canvas_Delegate newDelegate = new Canvas_Delegate();
+
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void copyNativeCanvasState(int srcCanvas, int dstCanvas) {
+ // get the delegate from the native int.
+ Canvas_Delegate srcCanvasDelegate = sManager.getDelegate(srcCanvas);
+ if (srcCanvasDelegate == null) {
+ return;
+ }
+
+ // get the delegate from the native int.
+ Canvas_Delegate dstCanvasDelegate = sManager.getDelegate(dstCanvas);
+ if (dstCanvasDelegate == null) {
+ return;
+ }
+ // TODO: actually copy the canvas state.
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_saveLayer(int nativeCanvas, RectF bounds,
+ int paint, int layerFlags) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return 0;
+ }
+
+ Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
+ if (paintDelegate == null) {
+ return 0;
+ }
+
+ return canvasDelegate.saveLayer(bounds, paintDelegate, layerFlags);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_saveLayer(int nativeCanvas, float l,
+ float t, float r, float b,
+ int paint, int layerFlags) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return 0;
+ }
+
+ Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
+ if (paintDelegate == null) {
+ return 0;
+ }
+
+ return canvasDelegate.saveLayer(new RectF(l, t, r, b),
+ paintDelegate, layerFlags);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_saveLayerAlpha(int nativeCanvas,
+ RectF bounds, int alpha,
+ int layerFlags) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return 0;
+ }
+
+ return canvasDelegate.saveLayerAlpha(bounds, alpha, layerFlags);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_saveLayerAlpha(int nativeCanvas, float l,
+ float t, float r, float b,
+ int alpha, int layerFlags) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return 0;
+ }
+
+ return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags);
+ }
+
+
+ @LayoutlibDelegate
+ /*package*/ static void native_concat(int nCanvas, int nMatrix) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+ if (matrixDelegate == null) {
+ return;
+ }
+
+ // get the current top graphics2D object.
+ GcSnapshot snapshot = canvasDelegate.getSnapshot();
+
+ // get its current matrix
+ AffineTransform currentTx = snapshot.getTransform();
+ // get the AffineTransform of the given matrix
+ AffineTransform matrixTx = matrixDelegate.getAffineTransform();
+
+ // combine them so that the given matrix is applied after.
+ currentTx.concatenate(matrixTx);
+
+ // give it to the graphics2D as a new matrix replacing all previous transform
+ snapshot.setTransform(currentTx);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setMatrix(int nCanvas, int nMatrix) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+ if (matrixDelegate == null) {
+ return;
+ }
+
+ // get the current top graphics2D object.
+ GcSnapshot snapshot = canvasDelegate.getSnapshot();
+
+ // get the AffineTransform of the given matrix
+ AffineTransform matrixTx = matrixDelegate.getAffineTransform();
+
+ // give it to the graphics2D as a new matrix replacing all previous transform
+ snapshot.setTransform(matrixTx);
+
+ if (matrixDelegate.hasPerspective()) {
+ assert false;
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
+ "android.graphics.Canvas#setMatrix(android.graphics.Matrix) only " +
+ "supports affine transformations.", null, null /*data*/);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_clipRect(int nCanvas,
+ float left, float top,
+ float right, float bottom,
+ int regionOp) {
+
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ return false;
+ }
+
+ return canvasDelegate.clipRect(left, top, right, bottom, regionOp);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_clipPath(int nativeCanvas,
+ int nativePath,
+ int regionOp) {
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return true;
+ }
+
+ Path_Delegate pathDelegate = Path_Delegate.getDelegate(nativePath);
+ if (pathDelegate == null) {
+ return true;
+ }
+
+ return canvasDelegate.mSnapshot.clip(pathDelegate.getJavaShape(), regionOp);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_clipRegion(int nativeCanvas,
+ int nativeRegion,
+ int regionOp) {
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return true;
+ }
+
+ Region_Delegate region = Region_Delegate.getDelegate(nativeRegion);
+ if (region == null) {
+ return true;
+ }
+
+ return canvasDelegate.mSnapshot.clip(region.getJavaArea(), regionOp);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeSetDrawFilter(int nativeCanvas, int nativeFilter) {
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ canvasDelegate.mDrawFilter = DrawFilter_Delegate.getDelegate(nativeFilter);
+
+ if (canvasDelegate.mDrawFilter != null &&
+ canvasDelegate.mDrawFilter.isSupported() == false) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_DRAWFILTER,
+ canvasDelegate.mDrawFilter.getSupportMessage(), null, null /*data*/);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_getClipBounds(int nativeCanvas,
+ Rect bounds) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return false;
+ }
+
+ Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds();
+ if (rect != null && rect.isEmpty() == false) {
+ bounds.left = rect.x;
+ bounds.top = rect.y;
+ bounds.right = rect.x + rect.width;
+ bounds.bottom = rect.y + rect.height;
+ return true;
+ }
+
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_getCTM(int canvas, int matrix) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
+ if (matrixDelegate == null) {
+ return;
+ }
+
+ AffineTransform transform = canvasDelegate.getSnapshot().getTransform();
+ matrixDelegate.set(Matrix_Delegate.makeValues(transform));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_quickReject(int nativeCanvas,
+ RectF rect) {
+ // FIXME properly implement quickReject
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_quickReject(int nativeCanvas,
+ int path) {
+ // FIXME properly implement quickReject
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_quickReject(int nativeCanvas,
+ float left, float top,
+ float right, float bottom) {
+ // FIXME properly implement quickReject
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawRGB(int nativeCanvas, int r, int g, int b) {
+ native_drawColor(nativeCanvas, 0xFF000000 | r << 16 | (g&0xFF) << 8 | (b&0xFF),
+ PorterDuff.Mode.SRC_OVER.nativeInt);
+
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawARGB(int nativeCanvas, int a, int r, int g, int b) {
+ native_drawColor(nativeCanvas, a << 24 | (r&0xFF) << 16 | (g&0xFF) << 8 | (b&0xFF),
+ PorterDuff.Mode.SRC_OVER.nativeInt);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawColor(int nativeCanvas, int color) {
+ native_drawColor(nativeCanvas, color, PorterDuff.Mode.SRC_OVER.nativeInt);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawColor(int nativeCanvas, final int color, final int mode) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ final int w = canvasDelegate.mBitmap.getImage().getWidth();
+ final int h = canvasDelegate.mBitmap.getImage().getHeight();
+ draw(nativeCanvas, new GcSnapshot.Drawable() {
+
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paint) {
+ // reset its transform just in case
+ graphics.setTransform(new AffineTransform());
+
+ // set the color
+ graphics.setColor(new Color(color, true /*alpha*/));
+
+ Composite composite = PorterDuffXfermode_Delegate.getComposite(
+ PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF);
+ if (composite != null) {
+ graphics.setComposite(composite);
+ }
+
+ graphics.fillRect(0, 0, w, h);
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawPaint(int nativeCanvas, int paint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawPaint is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawLine(int nativeCanvas,
+ final float startX, final float startY, final float stopX, final float stopY,
+ int paint) {
+
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawRect(int nativeCanvas, RectF rect,
+ int paint) {
+ native_drawRect(nativeCanvas, rect.left, rect.top, rect.right, rect.bottom, paint);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawRect(int nativeCanvas,
+ final float left, final float top, final float right, final float bottom, int paint) {
+
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ int style = paintDelegate.getStyle();
+
+ // draw
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.fillRect((int)left, (int)top,
+ (int)(right-left), (int)(bottom-top));
+ }
+
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.drawRect((int)left, (int)top,
+ (int)(right-left), (int)(bottom-top));
+ }
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawOval(int nativeCanvas, final RectF oval, int paint) {
+ if (oval.right > oval.left && oval.bottom > oval.top) {
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ int style = paintDelegate.getStyle();
+
+ // draw
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.fillOval((int)oval.left, (int)oval.top,
+ (int)oval.width(), (int)oval.height());
+ }
+
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.drawOval((int)oval.left, (int)oval.top,
+ (int)oval.width(), (int)oval.height());
+ }
+ }
+ });
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawCircle(int nativeCanvas,
+ float cx, float cy, float radius, int paint) {
+ native_drawOval(nativeCanvas,
+ new RectF(cx - radius, cy - radius, cx + radius, cy + radius),
+ paint);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawArc(int nativeCanvas,
+ final RectF oval, final float startAngle, final float sweep,
+ final boolean useCenter, int paint) {
+ if (oval.right > oval.left && oval.bottom > oval.top) {
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ int style = paintDelegate.getStyle();
+
+ Arc2D.Float arc = new Arc2D.Float(
+ oval.left, oval.top, oval.width(), oval.height(),
+ -startAngle, -sweep,
+ useCenter ? Arc2D.PIE : Arc2D.OPEN);
+
+ // draw
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.fill(arc);
+ }
+
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.draw(arc);
+ }
+ }
+ });
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawRoundRect(int nativeCanvas,
+ final RectF rect, final float rx, final float ry, int paint) {
+
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ int style = paintDelegate.getStyle();
+
+ // draw
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.fillRoundRect(
+ (int)rect.left, (int)rect.top,
+ (int)rect.width(), (int)rect.height(),
+ (int)rx, (int)ry);
+ }
+
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.drawRoundRect(
+ (int)rect.left, (int)rect.top,
+ (int)rect.width(), (int)rect.height(),
+ (int)rx, (int)ry);
+ }
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawPath(int nativeCanvas, int path, int paint) {
+ final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ Shape shape = pathDelegate.getJavaShape();
+ int style = paintDelegate.getStyle();
+
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.fill(shape);
+ }
+
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.draw(shape);
+ }
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawBitmap(Canvas thisCanvas, int nativeCanvas, int bitmap,
+ float left, float top,
+ int nativePaintOrZero,
+ int canvasDensity,
+ int screenDensity,
+ int bitmapDensity) {
+ // get the delegate from the native int.
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+ if (bitmapDelegate == null) {
+ return;
+ }
+
+ BufferedImage image = bitmapDelegate.getImage();
+ float right = left + image.getWidth();
+ float bottom = top + image.getHeight();
+
+ drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
+ 0, 0, image.getWidth(), image.getHeight(),
+ (int)left, (int)top, (int)right, (int)bottom);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawBitmap(Canvas thisCanvas, int nativeCanvas, int bitmap,
+ Rect src, RectF dst,
+ int nativePaintOrZero,
+ int screenDensity,
+ int bitmapDensity) {
+ // get the delegate from the native int.
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+ if (bitmapDelegate == null) {
+ return;
+ }
+
+ BufferedImage image = bitmapDelegate.getImage();
+
+ if (src == null) {
+ drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
+ 0, 0, image.getWidth(), image.getHeight(),
+ (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom);
+ } else {
+ drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
+ src.left, src.top, src.width(), src.height(),
+ (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawBitmap(int nativeCanvas, int bitmap,
+ Rect src, Rect dst,
+ int nativePaintOrZero,
+ int screenDensity,
+ int bitmapDensity) {
+ // get the delegate from the native int.
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+ if (bitmapDelegate == null) {
+ return;
+ }
+
+ BufferedImage image = bitmapDelegate.getImage();
+
+ if (src == null) {
+ drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
+ 0, 0, image.getWidth(), image.getHeight(),
+ dst.left, dst.top, dst.right, dst.bottom);
+ } else {
+ drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
+ src.left, src.top, src.width(), src.height(),
+ dst.left, dst.top, dst.right, dst.bottom);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawBitmap(int nativeCanvas, int[] colors,
+ int offset, int stride, final float x,
+ final float y, int width, int height,
+ boolean hasAlpha,
+ int nativePaintOrZero) {
+
+ // create a temp BufferedImage containing the content.
+ final BufferedImage image = new BufferedImage(width, height,
+ hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
+ image.setRGB(0, 0, width, height, colors, offset, stride);
+
+ draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/,
+ new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paint) {
+ if (paint != null && paint.isFilterBitmap()) {
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ }
+
+ graphics.drawImage(image, (int) x, (int) y, null);
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDrawBitmapMatrix(int nCanvas, int nBitmap,
+ int nMatrix, int nPaint) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ // get the delegate from the native int, which can be null
+ Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
+
+ // get the delegate from the native int.
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nBitmap);
+ if (bitmapDelegate == null) {
+ return;
+ }
+
+ final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut);
+
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+ if (matrixDelegate == null) {
+ return;
+ }
+
+ final AffineTransform mtx = matrixDelegate.getAffineTransform();
+
+ canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paint) {
+ if (paint != null && paint.isFilterBitmap()) {
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ }
+
+ //FIXME add support for canvas, screen and bitmap densities.
+ graphics.drawImage(image, mtx, null);
+ }
+ }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDrawBitmapMesh(int nCanvas, int nBitmap,
+ int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
+ int colorOffset, int nPaint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawBitmapMesh is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDrawVertices(int nCanvas, int mode, int n,
+ float[] verts, int vertOffset,
+ float[] texs, int texOffset,
+ int[] colors, int colorOffset,
+ short[] indices, int indexOffset,
+ int indexCount, int nPaint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawVertices is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawText(int nativeCanvas,
+ final char[] text, final int index, final int count,
+ final float startX, final float startY, final int flags, int paint) {
+
+ draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
+ // WARNING: the logic in this method is similar to Paint_Delegate.measureText.
+ // Any change to this method should be reflected in Paint.measureText
+ // Paint.TextAlign indicates how the text is positioned relative to X.
+ // LEFT is the default and there's nothing to do.
+ float x = startX;
+ int limit = index + count;
+ boolean isRtl = flags == Canvas.DIRECTION_RTL;
+ if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
+ float m = paintDelegate.measureText(text, index, count, isRtl);
+ if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
+ x -= m / 2;
+ } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
+ x -= m;
+ }
+ }
+
+ new BidiRenderer(graphics, paintDelegate, text).renderText(
+ index, limit, isRtl, null, 0, true, x, startY);
+ }
+ });
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawText(int nativeCanvas, String text,
+ int start, int end, float x, float y, final int flags, int paint) {
+ int count = end - start;
+ char[] buffer = TemporaryBuffer.obtain(count);
+ TextUtils.getChars(text, start, end, buffer, 0);
+
+ native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawTextRun(int nativeCanvas, String text,
+ int start, int end, int contextStart, int contextEnd,
+ float x, float y, int flags, int paint) {
+ int count = end - start;
+ char[] buffer = TemporaryBuffer.obtain(count);
+ TextUtils.getChars(text, start, end, buffer, 0);
+
+ native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawTextRun(int nativeCanvas, char[] text,
+ int start, int count, int contextStart, int contextCount,
+ float x, float y, int flags, int paint) {
+ native_drawText(nativeCanvas, text, start, count, x, y, flags, paint);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawPosText(int nativeCanvas,
+ char[] text, int index,
+ int count, float[] pos,
+ int paint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawPosText is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawPosText(int nativeCanvas,
+ String text, float[] pos,
+ int paint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawPosText is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawTextOnPath(int nativeCanvas,
+ char[] text, int index,
+ int count, int path,
+ float hOffset,
+ float vOffset, int bidiFlags,
+ int paint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_drawTextOnPath(int nativeCanvas,
+ String text, int path,
+ float hOffset,
+ float vOffset,
+ int flags, int paint) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void finalizer(int nativeCanvas) {
+ // get the delegate from the native int so that it can be disposed.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ canvasDelegate.dispose();
+
+ // remove it from the manager.
+ sManager.removeJavaReferenceFor(nativeCanvas);
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ /**
+ * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint.
+ * <p>Note that the drawable may actually be executed several times if there are
+ * layers involved (see {@link #saveLayer(RectF, int, int)}.
+ */
+ private static void draw(int nCanvas, int nPaint, boolean compositeOnly, boolean forceSrcMode,
+ GcSnapshot.Drawable drawable) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ // get the paint which can be null if nPaint is 0;
+ Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
+
+ canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode);
+ }
+
+ /**
+ * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided
+ * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
+ * <p>Note that the drawable may actually be executed several times if there are
+ * layers involved (see {@link #saveLayer(RectF, int, int)}.
+ */
+ private static void draw(int nCanvas, GcSnapshot.Drawable drawable) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ canvasDelegate.mSnapshot.draw(drawable);
+ }
+
+ private Canvas_Delegate(Bitmap_Delegate bitmap) {
+ mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap);
+ }
+
+ private Canvas_Delegate() {
+ mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
+ }
+
+ /**
+ * Disposes of the {@link Graphics2D} stack.
+ */
+ private void dispose() {
+ mSnapshot.dispose();
+ }
+
+ private int save(int saveFlags) {
+ // get the current save count
+ int count = mSnapshot.size();
+
+ mSnapshot = mSnapshot.save(saveFlags);
+
+ // return the old save count
+ return count;
+ }
+
+ private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
+ Paint_Delegate paint = new Paint_Delegate();
+ paint.setAlpha(alpha);
+ return saveLayer(rect, paint, saveFlags);
+ }
+
+ private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
+ // get the current save count
+ int count = mSnapshot.size();
+
+ mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
+
+ // return the old save count
+ return count;
+ }
+
+ /**
+ * Restores the {@link GcSnapshot} to <var>saveCount</var>
+ * @param saveCount the saveCount
+ */
+ private void restoreTo(int saveCount) {
+ mSnapshot = mSnapshot.restoreTo(saveCount);
+ }
+
+ /**
+ * Restores the {@link GcSnapshot} to <var>saveCount</var>
+ * @param saveCount the saveCount
+ */
+ private void restore() {
+ mSnapshot = mSnapshot.restore();
+ }
+
+ private boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
+ return mSnapshot.clipRect(left, top, right, bottom, regionOp);
+ }
+
+ private void setBitmap(Bitmap_Delegate bitmap) {
+ mBitmap = bitmap;
+ assert mSnapshot.size() == 1;
+ mSnapshot.setBitmap(mBitmap);
+ }
+
+ private static void drawBitmap(
+ int nativeCanvas,
+ Bitmap_Delegate bitmap,
+ int nativePaintOrZero,
+ final int sleft, final int stop, final int sright, final int sbottom,
+ final int dleft, final int dtop, final int dright, final int dbottom) {
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+ if (canvasDelegate == null) {
+ return;
+ }
+
+ // get the paint, which could be null if the int is 0
+ Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
+
+ final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut);
+
+ draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0],
+ new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paint) {
+ if (paint != null && paint.isFilterBitmap()) {
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ }
+
+ //FIXME add support for canvas, screen and bitmap densities.
+ graphics.drawImage(image, dleft, dtop, dright, dbottom,
+ sleft, stop, sright, sbottom, null);
+ }
+ });
+ }
+
+
+ /**
+ * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate.
+ * The image returns, through a 1-size boolean array, whether the drawing code should
+ * use a SRC composite no matter what the paint says.
+ *
+ * @param bitmap the bitmap
+ * @param paint the paint that will be used to draw
+ * @param forceSrcMode whether the composite will have to be SRC
+ * @return the image to draw
+ */
+ private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint,
+ boolean[] forceSrcMode) {
+ BufferedImage image = bitmap.getImage();
+ forceSrcMode[0] = false;
+
+ // if the bitmap config is alpha_8, then we erase all color value from it
+ // before drawing it.
+ if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
+ fixAlpha8Bitmap(image);
+ } else if (bitmap.hasAlpha() == false) {
+ // hasAlpha is merely a rendering hint. There can in fact be alpha values
+ // in the bitmap but it should be ignored at drawing time.
+ // There is two ways to do this:
+ // - override the composite to be SRC. This can only be used if the composite
+ // was going to be SRC or SRC_OVER in the first place
+ // - Create a different bitmap to draw in which all the alpha channel values is set
+ // to 0xFF.
+ if (paint != null) {
+ Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
+ if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) {
+ PorterDuff.Mode mode =
+ ((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode();
+
+ forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER ||
+ mode == PorterDuff.Mode.SRC;
+ }
+ }
+
+ // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
+ if (forceSrcMode[0] == false) {
+ image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
+ }
+ }
+
+ return image;
+ }
+
+ private static void fixAlpha8Bitmap(final BufferedImage image) {
+ int w = image.getWidth();
+ int h = image.getHeight();
+ int[] argb = new int[w * h];
+ image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
+
+ final int length = argb.length;
+ for (int i = 0 ; i < length; i++) {
+ argb[i] &= 0xFF000000;
+ }
+ image.setRGB(0, 0, w, h, argb, 0, w);
+ }
+}
+
diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java
new file mode 100644
index 0000000..e5a7ab6
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.ColorFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of ColorFilter have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original ColorFilter class.
+ *
+ * This also serve as a base class for all ColorFilter delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class ColorFilter_Delegate {
+
+ // ---- delegate manager ----
+ protected static final DelegateManager<ColorFilter_Delegate> sManager =
+ new DelegateManager<ColorFilter_Delegate>(ColorFilter_Delegate.class);
+
+ // ---- delegate helper data ----
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ public static ColorFilter_Delegate getDelegate(int nativeShader) {
+ return sManager.getDelegate(nativeShader);
+ }
+
+ public abstract boolean isSupported();
+ public abstract String getSupportMessage();
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static void finalizer(int native_instance, int nativeColorFilter) {
+ sManager.removeJavaReferenceFor(native_instance);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java
new file mode 100644
index 0000000..2de344b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.ColorMatrixColorFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of ColorMatrixColorFilter have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original ColorMatrixColorFilter class.
+ *
+ * Because this extends {@link ColorFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link ColorFilter_Delegate}.
+ *
+ * @see ColorFilter_Delegate
+ *
+ */
+public class ColorMatrixColorFilter_Delegate extends ColorFilter_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "ColorMatrix Color Filters are not supported.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeColorMatrixFilter(float[] array) {
+ ColorMatrixColorFilter_Delegate newDelegate = new ColorMatrixColorFilter_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nColorMatrixFilter(int nativeFilter, float[] array) {
+ // pass
+ return 0;
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java
new file mode 100644
index 0000000..7c04a87
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.ComposePathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of ComposePathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original ComposePathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public class ComposePathEffect_Delegate extends PathEffect_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public Stroke getStroke(Paint_Delegate paint) {
+ // FIXME
+ return null;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Compose Path Effects are not supported in Layout Preview mode.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate(int outerpe, int innerpe) {
+ ComposePathEffect_Delegate newDelegate = new ComposePathEffect_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
new file mode 100644
index 0000000..f6e1d00
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Paint;
+
+/**
+ * Delegate implementing the native methods of android.graphics.ComposeShader
+ *
+ * Through the layoutlib_create tool, the original native methods of ComposeShader have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original ComposeShader class.
+ *
+ * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
+ *
+ * @see Shader_Delegate
+ *
+ */
+public class ComposeShader_Delegate extends Shader_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public Paint getJavaPaint() {
+ // FIXME
+ return null;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Compose Shaders are not supported in Layout Preview mode.";
+ }
+
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate1(int native_shaderA, int native_shaderB,
+ int native_mode) {
+ // FIXME not supported yet.
+ ComposeShader_Delegate newDelegate = new ComposeShader_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate2(int native_shaderA, int native_shaderB,
+ int porterDuffMode) {
+ // FIXME not supported yet.
+ ComposeShader_Delegate newDelegate = new ComposeShader_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativePostCreate1(int native_shader, int native_skiaShaderA,
+ int native_skiaShaderB, int native_mode) {
+ // pass, not needed.
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativePostCreate2(int native_shader, int native_skiaShaderA,
+ int native_skiaShaderB, int porterDuffMode) {
+ // pass, not needed.
+ return 0;
+ }
+
+ // ---- Private delegate/helper methods ----
+
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java
new file mode 100644
index 0000000..b0f8168
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.CornerPathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of CornerPathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original CornerPathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public class CornerPathEffect_Delegate extends PathEffect_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public Stroke getStroke(Paint_Delegate paint) {
+ // FIXME
+ return null;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Corner Path Effects are not supported in Layout Preview mode.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate(float radius) {
+ CornerPathEffect_Delegate newDelegate = new CornerPathEffect_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java
new file mode 100644
index 0000000..d97c2ec
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.BasicStroke;
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.DashPathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of DashPathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original DashPathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by
+ * {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public final class DashPathEffect_Delegate extends PathEffect_Delegate {
+
+ // ---- delegate data ----
+
+ private final float[] mIntervals;
+ private final float mPhase;
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public Stroke getStroke(Paint_Delegate paint) {
+ return new BasicStroke(
+ paint.getStrokeWidth(),
+ paint.getJavaCap(),
+ paint.getJavaJoin(),
+ paint.getJavaStrokeMiter(),
+ mIntervals,
+ mPhase);
+ }
+
+ @Override
+ public boolean isSupported() {
+ return true;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ // no message since isSupported returns true;
+ return null;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate(float intervals[], float phase) {
+ DashPathEffect_Delegate newDelegate = new DashPathEffect_Delegate(intervals, phase);
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ private DashPathEffect_Delegate(float intervals[], float phase) {
+ mIntervals = new float[intervals.length];
+ System.arraycopy(intervals, 0, mIntervals, 0, intervals.length);
+ mPhase = phase;
+ }
+}
+
diff --git a/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java
new file mode 100644
index 0000000..ec4a810
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.DiscretePathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of DiscretePathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original DiscretePathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public class DiscretePathEffect_Delegate extends PathEffect_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public Stroke getStroke(Paint_Delegate paint) {
+ // FIXME
+ return null;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Discrete Path Effects are not supported in Layout Preview mode.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate(float length, float deviation) {
+ DiscretePathEffect_Delegate newDelegate = new DiscretePathEffect_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java
new file mode 100644
index 0000000..870c46b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.DrawFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of DrawFilter have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original DrawFilter class.
+ *
+ * This also serve as a base class for all DrawFilter delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class DrawFilter_Delegate {
+
+ // ---- delegate manager ----
+ protected static final DelegateManager<DrawFilter_Delegate> sManager =
+ new DelegateManager<DrawFilter_Delegate>(DrawFilter_Delegate.class);
+
+ // ---- delegate helper data ----
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ public static DrawFilter_Delegate getDelegate(int nativeDrawFilter) {
+ return sManager.getDelegate(nativeDrawFilter);
+ }
+
+ public abstract boolean isSupported();
+ public abstract String getSupportMessage();
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDestructor(int nativeDrawFilter) {
+ sManager.removeJavaReferenceFor(nativeDrawFilter);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java
new file mode 100644
index 0000000..ebc1c1d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.EmbossMaskFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of EmbossMaskFilter have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original EmbossMaskFilter class.
+ *
+ * Because this extends {@link MaskFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link MaskFilter_Delegate}.
+ *
+ * @see MaskFilter_Delegate
+ *
+ */
+public class EmbossMaskFilter_Delegate extends MaskFilter_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Emboss Mask Filters are not supported.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeConstructor(float[] direction, float ambient,
+ float specular, float blurRadius) {
+ EmbossMaskFilter_Delegate newDelegate = new EmbossMaskFilter_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java
new file mode 100644
index 0000000..7475c22
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import android.graphics.Shader.TileMode;
+
+/**
+ * Base class for true Gradient shader delegate.
+ */
+public abstract class Gradient_Delegate extends Shader_Delegate {
+
+ protected final int[] mColors;
+ protected final float[] mPositions;
+
+ @Override
+ public boolean isSupported() {
+ // all gradient shaders are supported.
+ return true;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ // all gradient shaders are supported, no need for a gradient support
+ return null;
+ }
+
+ /**
+ * Creates the base shader and do some basic test on the parameters.
+ *
+ * @param colors The colors to be distributed along the gradient line
+ * @param positions May be null. The relative positions [0..1] of each
+ * corresponding color in the colors array. If this is null, the
+ * the colors are distributed evenly along the gradient line.
+ */
+ protected Gradient_Delegate(int colors[], float positions[]) {
+ if (colors.length < 2) {
+ throw new IllegalArgumentException("needs >= 2 number of colors");
+ }
+ if (positions != null && colors.length != positions.length) {
+ throw new IllegalArgumentException("color and position arrays must be of equal length");
+ }
+
+ if (positions == null) {
+ float spacing = 1.f / (colors.length - 1);
+ positions = new float[colors.length];
+ positions[0] = 0.f;
+ positions[colors.length-1] = 1.f;
+ for (int i = 1; i < colors.length - 1 ; i++) {
+ positions[i] = spacing * i;
+ }
+ }
+
+ mColors = colors;
+ mPositions = positions;
+ }
+
+ /**
+ * Base class for (Java) Gradient Paints. This handles computing the gradient colors based
+ * on the color and position lists, as well as the {@link TileMode}
+ *
+ */
+ protected abstract static class GradientPaint implements java.awt.Paint {
+ private final static int GRADIENT_SIZE = 100;
+
+ private final int[] mColors;
+ private final float[] mPositions;
+ private final TileMode mTileMode;
+ private int[] mGradient;
+
+ protected GradientPaint(int[] colors, float[] positions, TileMode tileMode) {
+ mColors = colors;
+ mPositions = positions;
+ mTileMode = tileMode;
+ }
+
+ @Override
+ public int getTransparency() {
+ return java.awt.Paint.TRANSLUCENT;
+ }
+
+ /**
+ * Pre-computes the colors for the gradient. This must be called once before any call
+ * to {@link #getGradientColor(float)}
+ */
+ protected void precomputeGradientColors() {
+ if (mGradient == null) {
+ // actually create an array with an extra size, so that we can really go
+ // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
+ mGradient = new int[GRADIENT_SIZE+1];
+
+ int prevPos = 0;
+ int nextPos = 1;
+ for (int i = 0 ; i <= GRADIENT_SIZE ; i++) {
+ // compute current position
+ float currentPos = (float)i/GRADIENT_SIZE;
+ while (currentPos > mPositions[nextPos]) {
+ prevPos = nextPos++;
+ }
+
+ float percent = (currentPos - mPositions[prevPos]) /
+ (mPositions[nextPos] - mPositions[prevPos]);
+
+ mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
+ }
+ }
+ }
+
+ /**
+ * Returns the color based on the position in the gradient.
+ * <var>pos</var> can be anything, even < 0 or > > 1, as the gradient
+ * will use {@link TileMode} value to convert it into a [0,1] value.
+ */
+ protected int getGradientColor(float pos) {
+ if (pos < 0.f) {
+ if (mTileMode != null) {
+ switch (mTileMode) {
+ case CLAMP:
+ pos = 0.f;
+ break;
+ case REPEAT:
+ // remove the integer part to stay in the [0,1] range.
+ // we also need to invert the value from [-1,0] to [0, 1]
+ pos = pos - (float)Math.floor(pos);
+ break;
+ case MIRROR:
+ // this is the same as the positive side, just make the value positive
+ // first.
+ pos = Math.abs(pos);
+
+ // get the integer and the decimal part
+ int intPart = (int)Math.floor(pos);
+ pos = pos - intPart;
+ // 0 -> 1 : normal order
+ // 1 -> 2: mirrored
+ // etc..
+ // this means if the intpart is odd we invert
+ if ((intPart % 2) == 1) {
+ pos = 1.f - pos;
+ }
+ break;
+ }
+ } else {
+ pos = 0.0f;
+ }
+ } else if (pos > 1f) {
+ if (mTileMode != null) {
+ switch (mTileMode) {
+ case CLAMP:
+ pos = 1.f;
+ break;
+ case REPEAT:
+ // remove the integer part to stay in the [0,1] range
+ pos = pos - (float)Math.floor(pos);
+ break;
+ case MIRROR:
+ // get the integer and the decimal part
+ int intPart = (int)Math.floor(pos);
+ pos = pos - intPart;
+ // 0 -> 1 : normal order
+ // 1 -> 2: mirrored
+ // etc..
+ // this means if the intpart is odd we invert
+ if ((intPart % 2) == 1) {
+ pos = 1.f - pos;
+ }
+ break;
+ }
+ } else {
+ pos = 1.0f;
+ }
+ }
+
+ int index = (int)((pos * GRADIENT_SIZE) + .5);
+
+ return mGradient[index];
+ }
+
+ /**
+ * Returns the color between c1, and c2, based on the percent of the distance
+ * between c1 and c2.
+ */
+ private int computeColor(int c1, int c2, float percent) {
+ int a = computeChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
+ int r = computeChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
+ int g = computeChannel((c1 >> 8) & 0xFF, (c2 >> 8) & 0xFF, percent);
+ int b = computeChannel((c1 ) & 0xFF, (c2 ) & 0xFF, percent);
+ return a << 24 | r << 16 | g << 8 | b;
+ }
+
+ /**
+ * Returns the channel value between 2 values based on the percent of the distance between
+ * the 2 values..
+ */
+ private int computeChannel(int c1, int c2, float percent) {
+ return c1 + (int)((percent * (c2-c1)) + .5);
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java
new file mode 100644
index 0000000..51e0576
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/LayerRasterizer_Delegate.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.LayerRasterizer
+ *
+ * Through the layoutlib_create tool, the original native methods of LayerRasterizer have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original LayerRasterizer class.
+ *
+ * Because this extends {@link Rasterizer_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link Rasterizer_Delegate}.
+ *
+ * @see Rasterizer_Delegate
+ *
+ */
+public class LayerRasterizer_Delegate extends Rasterizer_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Layer Rasterizers are not supported.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeConstructor() {
+ LayerRasterizer_Delegate newDelegate = new LayerRasterizer_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeAddLayer(int native_layer, int native_paint, float dx, float dy) {
+
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java
new file mode 100644
index 0000000..0ee883d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.LightingColorFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of LightingColorFilter have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original LightingColorFilter class.
+ *
+ * Because this extends {@link ColorFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link ColorFilter_Delegate}.
+ *
+ * @see ColorFilter_Delegate
+ *
+ */
+public class LightingColorFilter_Delegate extends ColorFilter_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Lighting Color Filters are not supported.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int native_CreateLightingFilter(int mul, int add) {
+ LightingColorFilter_Delegate newDelegate = new LightingColorFilter_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nCreateLightingFilter(int nativeFilter, int mul, int add) {
+ // pass
+ return 0;
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
new file mode 100644
index 0000000..f117fca
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Shader.TileMode;
+
+/**
+ * Delegate implementing the native methods of android.graphics.LinearGradient
+ *
+ * Through the layoutlib_create tool, the original native methods of LinearGradient have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original LinearGradient class.
+ *
+ * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
+ *
+ * @see Shader_Delegate
+ *
+ */
+public final class LinearGradient_Delegate extends Gradient_Delegate {
+
+ // ---- delegate data ----
+ private java.awt.Paint mJavaPaint;
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public java.awt.Paint getJavaPaint() {
+ return mJavaPaint;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate1(LinearGradient thisGradient,
+ float x0, float y0, float x1, float y1,
+ int colors[], float positions[], int tileMode) {
+ LinearGradient_Delegate newDelegate = new LinearGradient_Delegate(x0, y0, x1, y1,
+ colors, positions, Shader_Delegate.getTileMode(tileMode));
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate2(LinearGradient thisGradient,
+ float x0, float y0, float x1, float y1,
+ int color0, int color1, int tileMode) {
+ return nativeCreate1(thisGradient,
+ x0, y0, x1, y1, new int[] { color0, color1}, null /*positions*/,
+ tileMode);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativePostCreate1(LinearGradient thisGradient,
+ int native_shader, float x0, float y0, float x1, float y1,
+ int colors[], float positions[], int tileMode) {
+ // nothing to be done here.
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativePostCreate2(LinearGradient thisGradient,
+ int native_shader, float x0, float y0, float x1, float y1,
+ int color0, int color1, int tileMode) {
+ // nothing to be done here.
+ return 0;
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ /**
+ * Create a shader that draws a linear gradient along a line.
+ *
+ * @param x0 The x-coordinate for the start of the gradient line
+ * @param y0 The y-coordinate for the start of the gradient line
+ * @param x1 The x-coordinate for the end of the gradient line
+ * @param y1 The y-coordinate for the end of the gradient line
+ * @param colors The colors to be distributed along the gradient line
+ * @param positions May be null. The relative positions [0..1] of each
+ * corresponding color in the colors array. If this is null, the
+ * the colors are distributed evenly along the gradient line.
+ * @param tile The Shader tiling mode
+ */
+ private LinearGradient_Delegate(float x0, float y0, float x1, float y1,
+ int colors[], float positions[], TileMode tile) {
+ super(colors, positions);
+ mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile);
+ }
+
+ // ---- Custom Java Paint ----
+ /**
+ * Linear Gradient (Java) Paint able to handle more than 2 points, as
+ * {@link java.awt.GradientPaint} only supports 2 points and does not support Android's tile
+ * modes.
+ */
+ private class LinearGradientPaint extends GradientPaint {
+
+ private final float mX0;
+ private final float mY0;
+ private final float mDx;
+ private final float mDy;
+ private final float mDSize2;
+
+ public LinearGradientPaint(float x0, float y0, float x1, float y1, int colors[],
+ float positions[], TileMode tile) {
+ super(colors, positions, tile);
+ mX0 = x0;
+ mY0 = y0;
+ mDx = x1 - x0;
+ mDy = y1 - y0;
+ mDSize2 = mDx * mDx + mDy * mDy;
+ }
+
+ @Override
+ public java.awt.PaintContext createContext(
+ java.awt.image.ColorModel colorModel,
+ java.awt.Rectangle deviceBounds,
+ java.awt.geom.Rectangle2D userBounds,
+ java.awt.geom.AffineTransform xform,
+ java.awt.RenderingHints hints) {
+ precomputeGradientColors();
+
+ java.awt.geom.AffineTransform canvasMatrix;
+ try {
+ canvasMatrix = xform.createInverse();
+ } catch (java.awt.geom.NoninvertibleTransformException e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+ "Unable to inverse matrix in LinearGradient", e, null /*data*/);
+ canvasMatrix = new java.awt.geom.AffineTransform();
+ }
+
+ java.awt.geom.AffineTransform localMatrix = getLocalMatrix();
+ try {
+ localMatrix = localMatrix.createInverse();
+ } catch (java.awt.geom.NoninvertibleTransformException e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+ "Unable to inverse matrix in LinearGradient", e, null /*data*/);
+ localMatrix = new java.awt.geom.AffineTransform();
+ }
+
+ return new LinearGradientPaintContext(canvasMatrix, localMatrix, colorModel);
+ }
+
+ private class LinearGradientPaintContext implements java.awt.PaintContext {
+
+ private final java.awt.geom.AffineTransform mCanvasMatrix;
+ private final java.awt.geom.AffineTransform mLocalMatrix;
+ private final java.awt.image.ColorModel mColorModel;
+
+ private LinearGradientPaintContext(
+ java.awt.geom.AffineTransform canvasMatrix,
+ java.awt.geom.AffineTransform localMatrix,
+ java.awt.image.ColorModel colorModel) {
+ mCanvasMatrix = canvasMatrix;
+ mLocalMatrix = localMatrix;
+ mColorModel = colorModel;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public java.awt.image.ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ @Override
+ public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+ java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h,
+ java.awt.image.BufferedImage.TYPE_INT_ARGB);
+
+ int[] data = new int[w*h];
+
+ int index = 0;
+ float[] pt1 = new float[2];
+ float[] pt2 = new float[2];
+ for (int iy = 0 ; iy < h ; iy++) {
+ for (int ix = 0 ; ix < w ; ix++) {
+ // handle the canvas transform
+ pt1[0] = x + ix;
+ pt1[1] = y + iy;
+ mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
+
+ // handle the local matrix.
+ pt1[0] = pt2[0];
+ pt1[1] = pt2[1];
+ mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
+
+ data[index++] = getColor(pt2[0], pt2[1]);
+ }
+ }
+
+ image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
+
+ return image.getRaster();
+ }
+ }
+
+ /**
+ * Returns a color for an arbitrary point.
+ */
+ private int getColor(float x, float y) {
+ float pos;
+ if (mDx == 0) {
+ pos = (y - mY0) / mDy;
+ } else if (mDy == 0) {
+ pos = (x - mX0) / mDx;
+ } else {
+ // find the x position on the gradient vector.
+ float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2;
+ // from it get the position relative to the vector
+ pos = (_x - mX0) / mDx;
+ }
+
+ return getGradientColor(pos);
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java
new file mode 100644
index 0000000..c2f27e4
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.MaskFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of MaskFilter have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original MaskFilter class.
+ *
+ * This also serve as a base class for all MaskFilter delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class MaskFilter_Delegate {
+
+ // ---- delegate manager ----
+ protected static final DelegateManager<MaskFilter_Delegate> sManager =
+ new DelegateManager<MaskFilter_Delegate>(MaskFilter_Delegate.class);
+
+ // ---- delegate helper data ----
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ public static MaskFilter_Delegate getDelegate(int nativeShader) {
+ return sManager.getDelegate(nativeShader);
+ }
+
+ public abstract boolean isSupported();
+ public abstract String getSupportMessage();
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDestructor(int native_filter) {
+ sManager.removeJavaReferenceFor(native_filter);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
new file mode 100644
index 0000000..5df2a21
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
@@ -0,0 +1,1129 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Matrix.ScaleToFit;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Matrix
+ *
+ * Through the layoutlib_create tool, the original native methods of Matrix have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Matrix class.
+ *
+ * @see DelegateManager
+ *
+ */
+public final class Matrix_Delegate {
+
+ private final static int MATRIX_SIZE = 9;
+
+ // ---- delegate manager ----
+ private static final DelegateManager<Matrix_Delegate> sManager =
+ new DelegateManager<Matrix_Delegate>(Matrix_Delegate.class);
+
+ // ---- delegate data ----
+ private float mValues[] = new float[MATRIX_SIZE];
+
+ // ---- Public Helper methods ----
+
+ public static Matrix_Delegate getDelegate(int native_instance) {
+ return sManager.getDelegate(native_instance);
+ }
+
+ /**
+ * Returns an {@link AffineTransform} matching the given Matrix.
+ */
+ public static AffineTransform getAffineTransform(Matrix m) {
+ Matrix_Delegate delegate = sManager.getDelegate(m.native_instance);
+ if (delegate == null) {
+ return null;
+ }
+
+ return delegate.getAffineTransform();
+ }
+
+ public static boolean hasPerspective(Matrix m) {
+ Matrix_Delegate delegate = sManager.getDelegate(m.native_instance);
+ if (delegate == null) {
+ return false;
+ }
+
+ return delegate.hasPerspective();
+ }
+
+ /**
+ * Sets the content of the matrix with the content of another matrix.
+ */
+ public void set(Matrix_Delegate matrix) {
+ System.arraycopy(matrix.mValues, 0, mValues, 0, MATRIX_SIZE);
+ }
+
+ /**
+ * Sets the content of the matrix with the content of another matrix represented as an array
+ * of values.
+ */
+ public void set(float[] values) {
+ System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE);
+ }
+
+ /**
+ * Resets the matrix to be the identity matrix.
+ */
+ public void reset() {
+ reset(mValues);
+ }
+
+ /**
+ * Returns whether or not the matrix is identity.
+ */
+ public boolean isIdentity() {
+ for (int i = 0, k = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++, k++) {
+ if (mValues[k] != ((i==j) ? 1 : 0)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public static float[] makeValues(AffineTransform matrix) {
+ float[] values = new float[MATRIX_SIZE];
+ values[0] = (float) matrix.getScaleX();
+ values[1] = (float) matrix.getShearX();
+ values[2] = (float) matrix.getTranslateX();
+ values[3] = (float) matrix.getShearY();
+ values[4] = (float) matrix.getScaleY();
+ values[5] = (float) matrix.getTranslateY();
+ values[6] = 0.f;
+ values[7] = 0.f;
+ values[8] = 1.f;
+
+ return values;
+ }
+
+ public static Matrix_Delegate make(AffineTransform matrix) {
+ return new Matrix_Delegate(makeValues(matrix));
+ }
+
+ public boolean mapRect(RectF dst, RectF src) {
+ // array with 4 corners
+ float[] corners = new float[] {
+ src.left, src.top,
+ src.right, src.top,
+ src.right, src.bottom,
+ src.left, src.bottom,
+ };
+
+ // apply the transform to them.
+ mapPoints(corners);
+
+ // now put the result in the rect. We take the min/max of Xs and min/max of Ys
+ dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
+ dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
+
+ dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
+ dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
+
+
+ return (computeTypeMask() & kRectStaysRect_Mask) != 0;
+ }
+
+
+ /**
+ * Returns an {@link AffineTransform} matching the matrix.
+ */
+ public AffineTransform getAffineTransform() {
+ return getAffineTransform(mValues);
+ }
+
+ public boolean hasPerspective() {
+ return (mValues[6] != 0 || mValues[7] != 0 || mValues[8] != 1);
+ }
+
+
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int native_create(int native_src_or_zero) {
+ // create the delegate
+ Matrix_Delegate newDelegate = new Matrix_Delegate();
+
+ // copy from values if needed.
+ if (native_src_or_zero > 0) {
+ Matrix_Delegate oldDelegate = sManager.getDelegate(native_src_or_zero);
+ if (oldDelegate != null) {
+ System.arraycopy(
+ oldDelegate.mValues, 0,
+ newDelegate.mValues, 0,
+ MATRIX_SIZE);
+ }
+ }
+
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_isIdentity(int native_object) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ return d.isIdentity();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_rectStaysRect(int native_object) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return true;
+ }
+
+ return (d.computeTypeMask() & kRectStaysRect_Mask) != 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_reset(int native_object) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ reset(d.mValues);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_set(int native_object, int other) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ Matrix_Delegate src = sManager.getDelegate(other);
+ if (src == null) {
+ return;
+ }
+
+ System.arraycopy(src.mValues, 0, d.mValues, 0, MATRIX_SIZE);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setTranslate(int native_object, float dx, float dy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ setTranslate(d.mValues, dx, dy);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setScale(int native_object, float sx, float sy,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ d.mValues = getScale(sx, sy, px, py);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setScale(int native_object, float sx, float sy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ d.mValues[0] = sx;
+ d.mValues[1] = 0;
+ d.mValues[2] = 0;
+ d.mValues[3] = 0;
+ d.mValues[4] = sy;
+ d.mValues[5] = 0;
+ d.mValues[6] = 0;
+ d.mValues[7] = 0;
+ d.mValues[8] = 1;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setRotate(int native_object, float degrees, float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ d.mValues = getRotate(degrees, px, py);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setRotate(int native_object, float degrees) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ setRotate(d.mValues, degrees);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setSinCos(int native_object, float sinValue, float cosValue,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ // TODO: do it in one pass
+
+ // translate so that the pivot is in 0,0
+ setTranslate(d.mValues, -px, -py);
+
+ // scale
+ d.postTransform(getRotate(sinValue, cosValue));
+ // translate back the pivot
+ d.postTransform(getTranslate(px, py));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setSinCos(int native_object, float sinValue, float cosValue) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ setRotate(d.mValues, sinValue, cosValue);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setSkew(int native_object, float kx, float ky,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ d.mValues = getSkew(kx, ky, px, py);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setSkew(int native_object, float kx, float ky) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ d.mValues[0] = 1;
+ d.mValues[1] = kx;
+ d.mValues[2] = -0;
+ d.mValues[3] = ky;
+ d.mValues[4] = 1;
+ d.mValues[5] = 0;
+ d.mValues[6] = 0;
+ d.mValues[7] = 0;
+ d.mValues[8] = 1;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_setConcat(int native_object, int a, int b) {
+ if (a == native_object) {
+ return native_preConcat(native_object, b);
+ } else if (b == native_object) {
+ return native_postConcat(native_object, a);
+ }
+
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ Matrix_Delegate a_mtx = sManager.getDelegate(a);
+ if (a_mtx == null) {
+ return false;
+ }
+
+ Matrix_Delegate b_mtx = sManager.getDelegate(b);
+ if (b_mtx == null) {
+ return false;
+ }
+
+ multiply(d.mValues, a_mtx.mValues, b_mtx.mValues);
+
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_preTranslate(int native_object, float dx, float dy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.preTransform(getTranslate(dx, dy));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_preScale(int native_object, float sx, float sy,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.preTransform(getScale(sx, sy, px, py));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_preScale(int native_object, float sx, float sy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.preTransform(getScale(sx, sy));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_preRotate(int native_object, float degrees,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.preTransform(getRotate(degrees, px, py));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_preRotate(int native_object, float degrees) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ double rad = Math.toRadians(degrees);
+ float sin = (float)Math.sin(rad);
+ float cos = (float)Math.cos(rad);
+
+ d.preTransform(getRotate(sin, cos));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_preSkew(int native_object, float kx, float ky,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.preTransform(getSkew(kx, ky, px, py));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_preSkew(int native_object, float kx, float ky) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.preTransform(getSkew(kx, ky));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_preConcat(int native_object, int other_matrix) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ Matrix_Delegate other = sManager.getDelegate(other_matrix);
+ if (other == null) {
+ return false;
+ }
+
+ d.preTransform(other.mValues);
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_postTranslate(int native_object, float dx, float dy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.postTransform(getTranslate(dx, dy));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_postScale(int native_object, float sx, float sy,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.postTransform(getScale(sx, sy, px, py));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_postScale(int native_object, float sx, float sy) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.postTransform(getScale(sx, sy));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_postRotate(int native_object, float degrees,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.postTransform(getRotate(degrees, px, py));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_postRotate(int native_object, float degrees) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.postTransform(getRotate(degrees));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_postSkew(int native_object, float kx, float ky,
+ float px, float py) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.postTransform(getSkew(kx, ky, px, py));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_postSkew(int native_object, float kx, float ky) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ d.postTransform(getSkew(kx, ky));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_postConcat(int native_object, int other_matrix) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ Matrix_Delegate other = sManager.getDelegate(other_matrix);
+ if (other == null) {
+ return false;
+ }
+
+ d.postTransform(other.mValues);
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_setRectToRect(int native_object, RectF src,
+ RectF dst, int stf) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ if (src.isEmpty()) {
+ reset(d.mValues);
+ return false;
+ }
+
+ if (dst.isEmpty()) {
+ d.mValues[0] = d.mValues[1] = d.mValues[2] = d.mValues[3] = d.mValues[4] = d.mValues[5]
+ = d.mValues[6] = d.mValues[7] = 0;
+ d.mValues[8] = 1;
+ } else {
+ float tx, sx = dst.width() / src.width();
+ float ty, sy = dst.height() / src.height();
+ boolean xLarger = false;
+
+ if (stf != ScaleToFit.FILL.nativeInt) {
+ if (sx > sy) {
+ xLarger = true;
+ sx = sy;
+ } else {
+ sy = sx;
+ }
+ }
+
+ tx = dst.left - src.left * sx;
+ ty = dst.top - src.top * sy;
+ if (stf == ScaleToFit.CENTER.nativeInt || stf == ScaleToFit.END.nativeInt) {
+ float diff;
+
+ if (xLarger) {
+ diff = dst.width() - src.width() * sy;
+ } else {
+ diff = dst.height() - src.height() * sy;
+ }
+
+ if (stf == ScaleToFit.CENTER.nativeInt) {
+ diff = diff / 2;
+ }
+
+ if (xLarger) {
+ tx += diff;
+ } else {
+ ty += diff;
+ }
+ }
+
+ d.mValues[0] = sx;
+ d.mValues[4] = sy;
+ d.mValues[2] = tx;
+ d.mValues[5] = ty;
+ d.mValues[1] = d.mValues[3] = d.mValues[6] = d.mValues[7] = 0;
+
+ }
+ // shared cleanup
+ d.mValues[8] = 1;
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_setPolyToPoly(int native_object, float[] src, int srcIndex,
+ float[] dst, int dstIndex, int pointCount) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Matrix.setPolyToPoly is not supported.",
+ null, null /*data*/);
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_invert(int native_object, int inverse) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ Matrix_Delegate inv_mtx = sManager.getDelegate(inverse);
+ if (inv_mtx == null) {
+ return false;
+ }
+
+ try {
+ AffineTransform affineTransform = d.getAffineTransform();
+ AffineTransform inverseTransform = affineTransform.createInverse();
+ inv_mtx.mValues[0] = (float)inverseTransform.getScaleX();
+ inv_mtx.mValues[1] = (float)inverseTransform.getShearX();
+ inv_mtx.mValues[2] = (float)inverseTransform.getTranslateX();
+ inv_mtx.mValues[3] = (float)inverseTransform.getScaleX();
+ inv_mtx.mValues[4] = (float)inverseTransform.getShearY();
+ inv_mtx.mValues[5] = (float)inverseTransform.getTranslateY();
+
+ return true;
+ } catch (NoninvertibleTransformException e) {
+ return false;
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_mapPoints(int native_object, float[] dst, int dstIndex,
+ float[] src, int srcIndex, int ptCount, boolean isPts) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ if (isPts) {
+ d.mapPoints(dst, dstIndex, src, srcIndex, ptCount);
+ } else {
+ d.mapVectors(dst, dstIndex, src, srcIndex, ptCount);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_mapRect(int native_object, RectF dst, RectF src) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return false;
+ }
+
+ return d.mapRect(dst, src);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float native_mapRadius(int native_object, float radius) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return 0.f;
+ }
+
+ float[] src = new float[] { radius, 0.f, 0.f, radius };
+ d.mapVectors(src, 0, src, 0, 2);
+
+ float l1 = getPointLength(src, 0);
+ float l2 = getPointLength(src, 2);
+
+ return (float) Math.sqrt(l1 * l2);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_getValues(int native_object, float[] values) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ System.arraycopy(d.mValues, 0, d.mValues, 0, MATRIX_SIZE);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setValues(int native_object, float[] values) {
+ Matrix_Delegate d = sManager.getDelegate(native_object);
+ if (d == null) {
+ return;
+ }
+
+ System.arraycopy(values, 0, d.mValues, 0, MATRIX_SIZE);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_equals(int native_a, int native_b) {
+ Matrix_Delegate a = sManager.getDelegate(native_a);
+ if (a == null) {
+ return false;
+ }
+
+ Matrix_Delegate b = sManager.getDelegate(native_b);
+ if (b == null) {
+ return false;
+ }
+
+ for (int i = 0 ; i < MATRIX_SIZE ; i++) {
+ if (a.mValues[i] != b.mValues[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void finalizer(int native_instance) {
+ sManager.removeJavaReferenceFor(native_instance);
+ }
+
+ // ---- Private helper methods ----
+
+ /*package*/ static AffineTransform getAffineTransform(float[] matrix) {
+ // the AffineTransform constructor takes the value in a different order
+ // for a matrix [ 0 1 2 ]
+ // [ 3 4 5 ]
+ // the order is 0, 3, 1, 4, 2, 5...
+ return new AffineTransform(
+ matrix[0], matrix[3], matrix[1],
+ matrix[4], matrix[2], matrix[5]);
+ }
+
+ /**
+ * Reset a matrix to the identity
+ */
+ private static void reset(float[] mtx) {
+ for (int i = 0, k = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++, k++) {
+ mtx[k] = ((i==j) ? 1 : 0);
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private final static int kIdentity_Mask = 0;
+ private final static int kTranslate_Mask = 0x01; //!< set if the matrix has translation
+ private final static int kScale_Mask = 0x02; //!< set if the matrix has X or Y scale
+ private final static int kAffine_Mask = 0x04; //!< set if the matrix skews or rotates
+ private final static int kPerspective_Mask = 0x08; //!< set if the matrix is in perspective
+ private final static int kRectStaysRect_Mask = 0x10;
+ @SuppressWarnings("unused")
+ private final static int kUnknown_Mask = 0x80;
+
+ @SuppressWarnings("unused")
+ private final static int kAllMasks = kTranslate_Mask |
+ kScale_Mask |
+ kAffine_Mask |
+ kPerspective_Mask |
+ kRectStaysRect_Mask;
+
+ // these guys align with the masks, so we can compute a mask from a variable 0/1
+ @SuppressWarnings("unused")
+ private final static int kTranslate_Shift = 0;
+ @SuppressWarnings("unused")
+ private final static int kScale_Shift = 1;
+ @SuppressWarnings("unused")
+ private final static int kAffine_Shift = 2;
+ @SuppressWarnings("unused")
+ private final static int kPerspective_Shift = 3;
+ private final static int kRectStaysRect_Shift = 4;
+
+ private int computeTypeMask() {
+ int mask = 0;
+
+ if (mValues[6] != 0. || mValues[7] != 0. || mValues[8] != 1.) {
+ mask |= kPerspective_Mask;
+ }
+
+ if (mValues[2] != 0. || mValues[5] != 0.) {
+ mask |= kTranslate_Mask;
+ }
+
+ float m00 = mValues[0];
+ float m01 = mValues[1];
+ float m10 = mValues[3];
+ float m11 = mValues[4];
+
+ if (m01 != 0. || m10 != 0.) {
+ mask |= kAffine_Mask;
+ }
+
+ if (m00 != 1. || m11 != 1.) {
+ mask |= kScale_Mask;
+ }
+
+ if ((mask & kPerspective_Mask) == 0) {
+ // map non-zero to 1
+ int im00 = m00 != 0 ? 1 : 0;
+ int im01 = m01 != 0 ? 1 : 0;
+ int im10 = m10 != 0 ? 1 : 0;
+ int im11 = m11 != 0 ? 1 : 0;
+
+ // record if the (p)rimary and (s)econdary diagonals are all 0 or
+ // all non-zero (answer is 0 or 1)
+ int dp0 = (im00 | im11) ^ 1; // true if both are 0
+ int dp1 = im00 & im11; // true if both are 1
+ int ds0 = (im01 | im10) ^ 1; // true if both are 0
+ int ds1 = im01 & im10; // true if both are 1
+
+ // return 1 if primary is 1 and secondary is 0 or
+ // primary is 0 and secondary is 1
+ mask |= ((dp0 & ds1) | (dp1 & ds0)) << kRectStaysRect_Shift;
+ }
+
+ return mask;
+ }
+
+ private Matrix_Delegate() {
+ reset();
+ }
+
+ private Matrix_Delegate(float[] values) {
+ System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE);
+ }
+
+ /**
+ * Adds the given transformation to the current Matrix
+ * <p/>This in effect does this = this*matrix
+ * @param matrix
+ */
+ private void postTransform(float[] matrix) {
+ float[] tmp = new float[9];
+ multiply(tmp, mValues, matrix);
+ mValues = tmp;
+ }
+
+ /**
+ * Adds the given transformation to the current Matrix
+ * <p/>This in effect does this = matrix*this
+ * @param matrix
+ */
+ private void preTransform(float[] matrix) {
+ float[] tmp = new float[9];
+ multiply(tmp, matrix, mValues);
+ mValues = tmp;
+ }
+
+ /**
+ * Apply this matrix to the array of 2D points specified by src, and write
+ * the transformed points into the array of points specified by dst. The
+ * two arrays represent their "points" as pairs of floats [x, y].
+ *
+ * @param dst The array of dst points (x,y pairs)
+ * @param dstIndex The index of the first [x,y] pair of dst floats
+ * @param src The array of src points (x,y pairs)
+ * @param srcIndex The index of the first [x,y] pair of src floats
+ * @param pointCount The number of points (x,y pairs) to transform
+ */
+
+ private void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,
+ int pointCount) {
+ final int count = pointCount * 2;
+
+ float[] tmpDest = dst;
+ boolean inPlace = dst == src;
+ if (inPlace) {
+ tmpDest = new float[dstIndex + count];
+ }
+
+ for (int i = 0 ; i < count ; i += 2) {
+ // just in case we are doing in place, we better put this in temp vars
+ float x = mValues[0] * src[i + srcIndex] +
+ mValues[1] * src[i + srcIndex + 1] +
+ mValues[2];
+ float y = mValues[3] * src[i + srcIndex] +
+ mValues[4] * src[i + srcIndex + 1] +
+ mValues[5];
+
+ tmpDest[i + dstIndex] = x;
+ tmpDest[i + dstIndex + 1] = y;
+ }
+
+ if (inPlace) {
+ System.arraycopy(tmpDest, dstIndex, dst, dstIndex, count);
+ }
+ }
+
+ /**
+ * Apply this matrix to the array of 2D points, and write the transformed
+ * points back into the array
+ *
+ * @param pts The array [x0, y0, x1, y1, ...] of points to transform.
+ */
+
+ private void mapPoints(float[] pts) {
+ mapPoints(pts, 0, pts, 0, pts.length >> 1);
+ }
+
+ private void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int ptCount) {
+ if (hasPerspective()) {
+ // transform the (0,0) point
+ float[] origin = new float[] { 0.f, 0.f};
+ mapPoints(origin);
+
+ // translate the vector data as points
+ mapPoints(dst, dstIndex, src, srcIndex, ptCount);
+
+ // then substract the transformed origin.
+ final int count = ptCount * 2;
+ for (int i = 0 ; i < count ; i += 2) {
+ dst[dstIndex + i] = dst[dstIndex + i] - origin[0];
+ dst[dstIndex + i + 1] = dst[dstIndex + i + 1] - origin[1];
+ }
+ } else {
+ // make a copy of the matrix
+ Matrix_Delegate copy = new Matrix_Delegate(mValues);
+
+ // remove the translation
+ setTranslate(copy.mValues, 0, 0);
+
+ // map the content as points.
+ copy.mapPoints(dst, dstIndex, src, srcIndex, ptCount);
+ }
+ }
+
+ private static float getPointLength(float[] src, int index) {
+ return (float) Math.sqrt(src[index] * src[index] + src[index + 1] * src[index + 1]);
+ }
+
+ /**
+ * multiply two matrices and store them in a 3rd.
+ * <p/>This in effect does dest = a*b
+ * dest cannot be the same as a or b.
+ */
+ /*package*/ static void multiply(float dest[], float[] a, float[] b) {
+ // first row
+ dest[0] = b[0] * a[0] + b[1] * a[3] + b[2] * a[6];
+ dest[1] = b[0] * a[1] + b[1] * a[4] + b[2] * a[7];
+ dest[2] = b[0] * a[2] + b[1] * a[5] + b[2] * a[8];
+
+ // 2nd row
+ dest[3] = b[3] * a[0] + b[4] * a[3] + b[5] * a[6];
+ dest[4] = b[3] * a[1] + b[4] * a[4] + b[5] * a[7];
+ dest[5] = b[3] * a[2] + b[4] * a[5] + b[5] * a[8];
+
+ // 3rd row
+ dest[6] = b[6] * a[0] + b[7] * a[3] + b[8] * a[6];
+ dest[7] = b[6] * a[1] + b[7] * a[4] + b[8] * a[7];
+ dest[8] = b[6] * a[2] + b[7] * a[5] + b[8] * a[8];
+ }
+
+ /**
+ * Returns a matrix that represents a given translate
+ * @param dx
+ * @param dy
+ * @return
+ */
+ /*package*/ static float[] getTranslate(float dx, float dy) {
+ return setTranslate(new float[9], dx, dy);
+ }
+
+ /*package*/ static float[] setTranslate(float[] dest, float dx, float dy) {
+ dest[0] = 1;
+ dest[1] = 0;
+ dest[2] = dx;
+ dest[3] = 0;
+ dest[4] = 1;
+ dest[5] = dy;
+ dest[6] = 0;
+ dest[7] = 0;
+ dest[8] = 1;
+ return dest;
+ }
+
+ /*package*/ static float[] getScale(float sx, float sy) {
+ return new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 };
+ }
+
+ /**
+ * Returns a matrix that represents the given scale info.
+ * @param sx
+ * @param sy
+ * @param px
+ * @param py
+ */
+ /*package*/ static float[] getScale(float sx, float sy, float px, float py) {
+ float[] tmp = new float[9];
+ float[] tmp2 = new float[9];
+
+ // TODO: do it in one pass
+
+ // translate tmp so that the pivot is in 0,0
+ setTranslate(tmp, -px, -py);
+
+ // scale into tmp2
+ multiply(tmp2, tmp, getScale(sx, sy));
+
+ // translate back the pivot back into tmp
+ multiply(tmp, tmp2, getTranslate(px, py));
+
+ return tmp;
+ }
+
+
+ /*package*/ static float[] getRotate(float degrees) {
+ double rad = Math.toRadians(degrees);
+ float sin = (float)Math.sin(rad);
+ float cos = (float)Math.cos(rad);
+
+ return getRotate(sin, cos);
+ }
+
+ /*package*/ static float[] getRotate(float sin, float cos) {
+ return setRotate(new float[9], sin, cos);
+ }
+
+ /*package*/ static float[] setRotate(float[] dest, float degrees) {
+ double rad = Math.toRadians(degrees);
+ float sin = (float)Math.sin(rad);
+ float cos = (float)Math.cos(rad);
+
+ return setRotate(dest, sin, cos);
+ }
+
+ /*package*/ static float[] setRotate(float[] dest, float sin, float cos) {
+ dest[0] = cos;
+ dest[1] = -sin;
+ dest[2] = 0;
+ dest[3] = sin;
+ dest[4] = cos;
+ dest[5] = 0;
+ dest[6] = 0;
+ dest[7] = 0;
+ dest[8] = 1;
+ return dest;
+ }
+
+ /*package*/ static float[] getRotate(float degrees, float px, float py) {
+ float[] tmp = new float[9];
+ float[] tmp2 = new float[9];
+
+ // TODO: do it in one pass
+
+ // translate so that the pivot is in 0,0
+ setTranslate(tmp, -px, -py);
+
+ // rotate into tmp2
+ double rad = Math.toRadians(degrees);
+ float cos = (float)Math.cos(rad);
+ float sin = (float)Math.sin(rad);
+ multiply(tmp2, tmp, getRotate(sin, cos));
+
+ // translate back the pivot back into tmp
+ multiply(tmp, tmp2, getTranslate(px, py));
+
+ return tmp;
+ }
+
+ /*package*/ static float[] getSkew(float kx, float ky) {
+ return new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 };
+ }
+
+ /*package*/ static float[] getSkew(float kx, float ky, float px, float py) {
+ float[] tmp = new float[9];
+ float[] tmp2 = new float[9];
+
+ // TODO: do it in one pass
+
+ // translate so that the pivot is in 0,0
+ setTranslate(tmp, -px, -py);
+
+ // skew into tmp2
+ multiply(tmp2, tmp, new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 });
+ // translate back the pivot back into tmp
+ multiply(tmp, tmp2, getTranslate(px, py));
+
+ return tmp;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
new file mode 100644
index 0000000..fa68796
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.GcSnapshot;
+import com.android.ninepatch.NinePatchChunk;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.drawable.NinePatchDrawable;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Delegate implementing the native methods of android.graphics.NinePatch
+ *
+ * Through the layoutlib_create tool, the original native methods of NinePatch have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ */
+public final class NinePatch_Delegate {
+
+ // ---- delegate manager ----
+ private static final DelegateManager<NinePatch_Delegate> sManager =
+ new DelegateManager<NinePatch_Delegate>(NinePatch_Delegate.class);
+
+ // ---- delegate helper data ----
+ /**
+ * Cache map for {@link NinePatchChunk}.
+ * When the chunks are created they are serialized into a byte[], and both are put
+ * in the cache, using a {@link SoftReference} for the chunk. The default Java classes
+ * for {@link NinePatch} and {@link NinePatchDrawable} only reference to the byte[] data, and
+ * provide this for drawing.
+ * Using the cache map allows us to not have to deserialize the byte[] back into a
+ * {@link NinePatchChunk} every time a rendering is done.
+ */
+ private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache =
+ new HashMap<byte[], SoftReference<NinePatchChunk>>();
+
+ // ---- delegate data ----
+ private byte[] chunk;
+
+
+ // ---- Public Helper methods ----
+
+ /**
+ * Serializes the given chunk.
+ *
+ * @return the serialized data for the chunk.
+ */
+ public static byte[] serialize(NinePatchChunk chunk) {
+ // serialize the chunk to get a byte[]
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = null;
+ try {
+ oos = new ObjectOutputStream(baos);
+ oos.writeObject(chunk);
+ } catch (IOException e) {
+ Bridge.getLog().error(null, "Failed to serialize NinePatchChunk.", e, null /*data*/);
+ return null;
+ } finally {
+ if (oos != null) {
+ try {
+ oos.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ // get the array and add it to the cache
+ byte[] array = baos.toByteArray();
+ sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
+ return array;
+ }
+
+ /**
+ * Returns a {@link NinePatchChunk} object for the given serialized representation.
+ *
+ * If the chunk is present in the cache then the object from the cache is returned, otherwise
+ * the array is deserialized into a {@link NinePatchChunk} object.
+ *
+ * @param array the serialized representation of the chunk.
+ * @return the NinePatchChunk or null if deserialization failed.
+ */
+ public static NinePatchChunk getChunk(byte[] array) {
+ SoftReference<NinePatchChunk> chunkRef = sChunkCache.get(array);
+ NinePatchChunk chunk = chunkRef.get();
+ if (chunk == null) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(array);
+ ObjectInputStream ois = null;
+ try {
+ ois = new ObjectInputStream(bais);
+ chunk = (NinePatchChunk) ois.readObject();
+
+ // put back the chunk in the cache
+ if (chunk != null) {
+ sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
+ }
+ } catch (IOException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to deserialize NinePatchChunk content.", e, null /*data*/);
+ return null;
+ } catch (ClassNotFoundException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to deserialize NinePatchChunk class.", e, null /*data*/);
+ return null;
+ } finally {
+ if (ois != null) {
+ try {
+ ois.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ return chunk;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static boolean isNinePatchChunk(byte[] chunk) {
+ NinePatchChunk chunkObject = getChunk(chunk);
+ if (chunkObject != null) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int validateNinePatchChunk(int bitmap, byte[] chunk) {
+ // the default JNI implementation only checks that the byte[] has the same
+ // size as the C struct it represent. Since we cannot do the same check (serialization
+ // will return different size depending on content), we do nothing.
+ NinePatch_Delegate newDelegate = new NinePatch_Delegate();
+ newDelegate.chunk = chunk;
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ /*package*/ static void nativeFinalize(int chunk) {
+ sManager.removeJavaReferenceFor(chunk);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance,
+ int chunk, int paint_instance_or_null, int destDensity, int srcDensity) {
+ draw(canvas_instance,
+ (int) loc.left, (int) loc.top, (int) loc.width(), (int) loc.height(),
+ bitmap_instance, chunk, paint_instance_or_null,
+ destDensity, srcDensity);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance,
+ int chunk, int paint_instance_or_null, int destDensity, int srcDensity) {
+ draw(canvas_instance,
+ loc.left, loc.top, loc.width(), loc.height(),
+ bitmap_instance, chunk, paint_instance_or_null,
+ destDensity, srcDensity);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeGetTransparentRegion(int bitmap, int chunk, Rect location) {
+ return 0;
+ }
+
+ // ---- Private Helper methods ----
+
+ private static void draw(int canvas_instance,
+ final int left, final int top, final int right, final int bottom,
+ int bitmap_instance, int chunk, int paint_instance_or_null,
+ final int destDensity, final int srcDensity) {
+ // get the delegate from the native int.
+ final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
+ if (bitmap_delegate == null) {
+ return;
+ }
+
+ byte[] c = null;
+ NinePatch_Delegate delegate = sManager.getDelegate(chunk);
+ if (delegate != null) {
+ c = delegate.chunk;
+ }
+ if (c == null) {
+ // not a 9-patch?
+ BufferedImage image = bitmap_delegate.getImage();
+ Canvas_Delegate.native_drawBitmap(canvas_instance, bitmap_instance,
+ new Rect(0, 0, image.getWidth(), image.getHeight()),
+ new Rect(left, top, right, bottom),
+ paint_instance_or_null, destDensity, srcDensity);
+ return;
+ }
+
+ final NinePatchChunk chunkObject = getChunk(c);
+ assert chunkObject != null;
+ if (chunkObject == null) {
+ return;
+ }
+
+ Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance);
+ if (canvas_delegate == null) {
+ return;
+ }
+
+ // this one can be null
+ Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null);
+
+ canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() {
+ @Override
+ public void draw(Graphics2D graphics, Paint_Delegate paint) {
+ chunkObject.draw(bitmap_delegate.getImage(), graphics,
+ left, top, right - left, bottom - top, destDensity, srcDensity);
+ }
+ }, paint_delegate, true /*compositeOnly*/, false /*forceSrcMode*/);
+
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java
new file mode 100644
index 0000000..71d346a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.PaintFlagsDrawFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of PaintFlagsDrawFilter have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original PaintFlagsDrawFilter class.
+ *
+ * Because this extends {@link DrawFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the DrawFilter classes will be added to the manager owned by
+ * {@link DrawFilter_Delegate}.
+ *
+ * @see DrawFilter_Delegate
+ *
+ */
+public class PaintFlagsDrawFilter_Delegate extends DrawFilter_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Paint Flags Draw Filters are not supported.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeConstructor(int clearBits, int setBits) {
+ PaintFlagsDrawFilter_Delegate newDelegate = new PaintFlagsDrawFilter_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
new file mode 100644
index 0000000..41953ed
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -0,0 +1,1208 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Paint.FontMetrics;
+import android.graphics.Paint.FontMetricsInt;
+import android.text.TextUtils;
+
+import java.awt.BasicStroke;
+import java.awt.Font;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.Toolkit;
+import java.awt.font.FontRenderContext;
+import java.awt.geom.AffineTransform;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Paint
+ *
+ * Through the layoutlib_create tool, the original native methods of Paint have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Paint class.
+ *
+ * @see DelegateManager
+ *
+ */
+public class Paint_Delegate {
+
+ /**
+ * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}.
+ */
+ /*package*/ static final class FontInfo {
+ Font mFont;
+ java.awt.FontMetrics mMetrics;
+ }
+
+ // ---- delegate manager ----
+ private static final DelegateManager<Paint_Delegate> sManager =
+ new DelegateManager<Paint_Delegate>(Paint_Delegate.class);
+
+ // ---- delegate helper data ----
+ private List<FontInfo> mFonts;
+ private final FontRenderContext mFontContext = new FontRenderContext(
+ new AffineTransform(), true, true);
+
+ // ---- delegate data ----
+ private int mFlags;
+ private int mColor;
+ private int mStyle;
+ private int mCap;
+ private int mJoin;
+ private int mTextAlign;
+ private Typeface_Delegate mTypeface;
+ private float mStrokeWidth;
+ private float mStrokeMiter;
+ private float mTextSize;
+ private float mTextScaleX;
+ private float mTextSkewX;
+ private int mHintingMode = Paint.HINTING_ON;
+
+ private Xfermode_Delegate mXfermode;
+ private ColorFilter_Delegate mColorFilter;
+ private Shader_Delegate mShader;
+ private PathEffect_Delegate mPathEffect;
+ private MaskFilter_Delegate mMaskFilter;
+ private Rasterizer_Delegate mRasterizer;
+
+ private Locale mLocale = Locale.getDefault();
+
+
+ // ---- Public Helper methods ----
+
+ public static Paint_Delegate getDelegate(int native_paint) {
+ return sManager.getDelegate(native_paint);
+ }
+
+ /**
+ * Returns the list of {@link Font} objects. The first item is the main font, the rest
+ * are fall backs for characters not present in the main font.
+ */
+ public List<FontInfo> getFonts() {
+ return mFonts;
+ }
+
+ public boolean isAntiAliased() {
+ return (mFlags & Paint.ANTI_ALIAS_FLAG) != 0;
+ }
+
+ public boolean isFilterBitmap() {
+ return (mFlags & Paint.FILTER_BITMAP_FLAG) != 0;
+ }
+
+ public int getStyle() {
+ return mStyle;
+ }
+
+ public int getColor() {
+ return mColor;
+ }
+
+ public int getAlpha() {
+ return mColor >>> 24;
+ }
+
+ public void setAlpha(int alpha) {
+ mColor = (alpha << 24) | (mColor & 0x00FFFFFF);
+ }
+
+ public int getTextAlign() {
+ return mTextAlign;
+ }
+
+ public float getStrokeWidth() {
+ return mStrokeWidth;
+ }
+
+ /**
+ * returns the value of stroke miter needed by the java api.
+ */
+ public float getJavaStrokeMiter() {
+ float miter = mStrokeMiter * mStrokeWidth;
+ if (miter < 1.f) {
+ miter = 1.f;
+ }
+ return miter;
+ }
+
+ public int getJavaCap() {
+ switch (Paint.sCapArray[mCap]) {
+ case BUTT:
+ return BasicStroke.CAP_BUTT;
+ case ROUND:
+ return BasicStroke.CAP_ROUND;
+ default:
+ case SQUARE:
+ return BasicStroke.CAP_SQUARE;
+ }
+ }
+
+ public int getJavaJoin() {
+ switch (Paint.sJoinArray[mJoin]) {
+ default:
+ case MITER:
+ return BasicStroke.JOIN_MITER;
+ case ROUND:
+ return BasicStroke.JOIN_ROUND;
+ case BEVEL:
+ return BasicStroke.JOIN_BEVEL;
+ }
+ }
+
+ public Stroke getJavaStroke() {
+ if (mPathEffect != null) {
+ if (mPathEffect.isSupported()) {
+ Stroke stroke = mPathEffect.getStroke(this);
+ assert stroke != null;
+ if (stroke != null) {
+ return stroke;
+ }
+ } else {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_PATHEFFECT,
+ mPathEffect.getSupportMessage(),
+ null, null /*data*/);
+ }
+ }
+
+ // if no custom stroke as been set, set the default one.
+ return new BasicStroke(
+ getStrokeWidth(),
+ getJavaCap(),
+ getJavaJoin(),
+ getJavaStrokeMiter());
+ }
+
+ /**
+ * Returns the {@link Xfermode} delegate or null if none have been set
+ *
+ * @return the delegate or null.
+ */
+ public Xfermode_Delegate getXfermode() {
+ return mXfermode;
+ }
+
+ /**
+ * Returns the {@link ColorFilter} delegate or null if none have been set
+ *
+ * @return the delegate or null.
+ */
+ public ColorFilter_Delegate getColorFilter() {
+ return mColorFilter;
+ }
+
+ /**
+ * Returns the {@link Shader} delegate or null if none have been set
+ *
+ * @return the delegate or null.
+ */
+ public Shader_Delegate getShader() {
+ return mShader;
+ }
+
+ /**
+ * Returns the {@link MaskFilter} delegate or null if none have been set
+ *
+ * @return the delegate or null.
+ */
+ public MaskFilter_Delegate getMaskFilter() {
+ return mMaskFilter;
+ }
+
+ /**
+ * Returns the {@link Rasterizer} delegate or null if none have been set
+ *
+ * @return the delegate or null.
+ */
+ public Rasterizer_Delegate getRasterizer() {
+ return mRasterizer;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int getFlags(Paint thisPaint) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mFlags;
+ }
+
+
+
+ @LayoutlibDelegate
+ /*package*/ static void setFlags(Paint thisPaint, int flags) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mFlags = flags;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setFilterBitmap(Paint thisPaint, boolean filter) {
+ setFlag(thisPaint, Paint.FILTER_BITMAP_FLAG, filter);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int getHinting(Paint thisPaint) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return Paint.HINTING_ON;
+ }
+
+ return delegate.mHintingMode;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setHinting(Paint thisPaint, int mode) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mHintingMode = mode;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setAntiAlias(Paint thisPaint, boolean aa) {
+ setFlag(thisPaint, Paint.ANTI_ALIAS_FLAG, aa);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setSubpixelText(Paint thisPaint, boolean subpixelText) {
+ setFlag(thisPaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setUnderlineText(Paint thisPaint, boolean underlineText) {
+ setFlag(thisPaint, Paint.UNDERLINE_TEXT_FLAG, underlineText);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setStrikeThruText(Paint thisPaint, boolean strikeThruText) {
+ setFlag(thisPaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setFakeBoldText(Paint thisPaint, boolean fakeBoldText) {
+ setFlag(thisPaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setDither(Paint thisPaint, boolean dither) {
+ setFlag(thisPaint, Paint.DITHER_FLAG, dither);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setLinearText(Paint thisPaint, boolean linearText) {
+ setFlag(thisPaint, Paint.LINEAR_TEXT_FLAG, linearText);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int getColor(Paint thisPaint) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mColor;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setColor(Paint thisPaint, int color) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mColor = color;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int getAlpha(Paint thisPaint) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.getAlpha();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setAlpha(Paint thisPaint, int a) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.setAlpha(a);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float getStrokeWidth(Paint thisPaint) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 1.f;
+ }
+
+ return delegate.mStrokeWidth;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setStrokeWidth(Paint thisPaint, float width) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mStrokeWidth = width;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float getStrokeMiter(Paint thisPaint) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 1.f;
+ }
+
+ return delegate.mStrokeMiter;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setStrokeMiter(Paint thisPaint, float miter) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mStrokeMiter = miter;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nSetShadowLayer(Paint thisPaint, float radius, float dx, float dy,
+ int color) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Paint.setShadowLayer is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float getTextSize(Paint thisPaint) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 1.f;
+ }
+
+ return delegate.mTextSize;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setTextSize(Paint thisPaint, float textSize) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mTextSize = textSize;
+ delegate.updateFontObject();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float getTextScaleX(Paint thisPaint) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 1.f;
+ }
+
+ return delegate.mTextScaleX;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setTextScaleX(Paint thisPaint, float scaleX) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mTextScaleX = scaleX;
+ delegate.updateFontObject();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float getTextSkewX(Paint thisPaint) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 1.f;
+ }
+
+ return delegate.mTextSkewX;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void setTextSkewX(Paint thisPaint, float skewX) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mTextSkewX = skewX;
+ delegate.updateFontObject();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float ascent(Paint thisPaint) {
+ // get the delegate
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+
+ if (delegate.mFonts.size() > 0) {
+ java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
+ // Android expects negative ascent so we invert the value from Java.
+ return - javaMetrics.getAscent();
+ }
+
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float descent(Paint thisPaint) {
+ // get the delegate
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+
+ if (delegate.mFonts.size() > 0) {
+ java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
+ return javaMetrics.getDescent();
+ }
+
+ return 0;
+
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float getFontMetrics(Paint thisPaint, FontMetrics metrics) {
+ // get the delegate
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.getFontMetrics(metrics);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int getFontMetricsInt(Paint thisPaint, FontMetricsInt fmi) {
+ // get the delegate
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+
+ if (delegate.mFonts.size() > 0) {
+ java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
+ if (fmi != null) {
+ // Android expects negative ascent so we invert the value from Java.
+ fmi.top = - javaMetrics.getMaxAscent();
+ fmi.ascent = - javaMetrics.getAscent();
+ fmi.descent = javaMetrics.getDescent();
+ fmi.bottom = javaMetrics.getMaxDescent();
+ fmi.leading = javaMetrics.getLeading();
+ }
+
+ return javaMetrics.getHeight();
+ }
+
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float native_measureText(Paint thisPaint, char[] text, int index,
+ int count, int bidiFlags) {
+ // get the delegate
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.measureText(text, index, count, isRtl(bidiFlags));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float native_measureText(Paint thisPaint, String text, int start, int end,
+ int bidiFlags) {
+ return native_measureText(thisPaint, text.toCharArray(), start, end - start, bidiFlags);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float native_measureText(Paint thisPaint, String text, int bidiFlags) {
+ return native_measureText(thisPaint, text.toCharArray(), 0, text.length(), bidiFlags);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_breakText(Paint thisPaint, char[] text, int index, int count,
+ float maxWidth, int bidiFlags, float[] measuredWidth) {
+
+ // get the delegate
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+
+ int inc = count > 0 ? 1 : -1;
+
+ int measureIndex = 0;
+ float measureAcc = 0;
+ for (int i = index; i != index + count; i += inc, measureIndex++) {
+ int start, end;
+ if (i < index) {
+ start = i;
+ end = index;
+ } else {
+ start = index;
+ end = i;
+ }
+
+ // measure from start to end
+ float res = delegate.measureText(text, start, end - start + 1, isRtl(bidiFlags));
+
+ if (measuredWidth != null) {
+ measuredWidth[measureIndex] = res;
+ }
+
+ measureAcc += res;
+ if (res > maxWidth) {
+ // we should not return this char index, but since it's 0-based
+ // and we need to return a count, we simply return measureIndex;
+ return measureIndex;
+ }
+
+ }
+
+ return measureIndex;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_breakText(Paint thisPaint, String text, boolean measureForwards,
+ float maxWidth, int bidiFlags, float[] measuredWidth) {
+ return native_breakText(thisPaint, text.toCharArray(), 0, text.length(), maxWidth,
+ bidiFlags, measuredWidth);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_init() {
+ Paint_Delegate newDelegate = new Paint_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_initWithPaint(int paint) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(paint);
+ if (delegate == null) {
+ return 0;
+ }
+
+ Paint_Delegate newDelegate = new Paint_Delegate(delegate);
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_reset(int native_object) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.reset();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_set(int native_dst, int native_src) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate_dst = sManager.getDelegate(native_dst);
+ if (delegate_dst == null) {
+ return;
+ }
+
+ // get the delegate from the native int.
+ Paint_Delegate delegate_src = sManager.getDelegate(native_src);
+ if (delegate_src == null) {
+ return;
+ }
+
+ delegate_dst.set(delegate_src);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getStyle(int native_object) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mStyle;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setStyle(int native_object, int style) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mStyle = style;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getStrokeCap(int native_object) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mCap;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setStrokeCap(int native_object, int cap) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mCap = cap;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getStrokeJoin(int native_object) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mJoin;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setStrokeJoin(int native_object, int join) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mJoin = join;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_getFillPath(int native_object, int src, int dst) {
+ Paint_Delegate paint = sManager.getDelegate(native_object);
+ if (paint == null) {
+ return false;
+ }
+
+ Path_Delegate srcPath = Path_Delegate.getDelegate(src);
+ if (srcPath == null) {
+ return true;
+ }
+
+ Path_Delegate dstPath = Path_Delegate.getDelegate(dst);
+ if (dstPath == null) {
+ return true;
+ }
+
+ Stroke stroke = paint.getJavaStroke();
+ Shape strokeShape = stroke.createStrokedShape(srcPath.getJavaShape());
+
+ dstPath.setJavaShape(strokeShape);
+
+ // FIXME figure out the return value?
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_setShader(int native_object, int shader) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return shader;
+ }
+
+ delegate.mShader = Shader_Delegate.getDelegate(shader);
+
+ return shader;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_setColorFilter(int native_object, int filter) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return filter;
+ }
+
+ delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter);;
+
+ // since none of those are supported, display a fidelity warning right away
+ if (delegate.mColorFilter != null && delegate.mColorFilter.isSupported() == false) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_COLORFILTER,
+ delegate.mColorFilter.getSupportMessage(), null, null /*data*/);
+ }
+
+ return filter;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_setXfermode(int native_object, int xfermode) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return xfermode;
+ }
+
+ delegate.mXfermode = Xfermode_Delegate.getDelegate(xfermode);
+
+ return xfermode;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_setPathEffect(int native_object, int effect) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return effect;
+ }
+
+ delegate.mPathEffect = PathEffect_Delegate.getDelegate(effect);
+
+ return effect;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_setMaskFilter(int native_object, int maskfilter) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return maskfilter;
+ }
+
+ delegate.mMaskFilter = MaskFilter_Delegate.getDelegate(maskfilter);
+
+ // since none of those are supported, display a fidelity warning right away
+ if (delegate.mMaskFilter != null && delegate.mMaskFilter.isSupported() == false) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
+ delegate.mMaskFilter.getSupportMessage(), null, null /*data*/);
+ }
+
+ return maskfilter;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_setTypeface(int native_object, int typeface) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return 0;
+ }
+
+ delegate.mTypeface = Typeface_Delegate.getDelegate(typeface);
+ delegate.updateFontObject();
+ return typeface;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_setRasterizer(int native_object, int rasterizer) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return rasterizer;
+ }
+
+ delegate.mRasterizer = Rasterizer_Delegate.getDelegate(rasterizer);
+
+ // since none of those are supported, display a fidelity warning right away
+ if (delegate.mRasterizer != null && delegate.mRasterizer.isSupported() == false) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_RASTERIZER,
+ delegate.mRasterizer.getSupportMessage(), null, null /*data*/);
+ }
+
+ return rasterizer;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getTextAlign(int native_object) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mTextAlign;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setTextAlign(int native_object, int align) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mTextAlign = align;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setTextLocale(int native_object, String locale) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.setTextLocale(locale);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getTextWidths(int native_object, char[] text, int index,
+ int count, int bidiFlags, float[] widths) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null) {
+ return 0;
+ }
+
+ if (delegate.mFonts.size() > 0) {
+ // FIXME: handle multi-char characters (see measureText)
+ float totalAdvance = 0;
+ for (int i = 0; i < count; i++) {
+ char c = text[i + index];
+ boolean found = false;
+ for (FontInfo info : delegate.mFonts) {
+ if (info.mFont.canDisplay(c)) {
+ float adv = info.mMetrics.charWidth(c);
+ totalAdvance += adv;
+ if (widths != null) {
+ widths[i] = adv;
+ }
+
+ found = true;
+ break;
+ }
+ }
+
+ if (found == false) {
+ // no advance for this char.
+ if (widths != null) {
+ widths[i] = 0.f;
+ }
+ }
+ }
+
+ return (int) totalAdvance;
+ }
+
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getTextWidths(int native_object, String text, int start,
+ int end, int bidiFlags, float[] widths) {
+ return native_getTextWidths(native_object, text.toCharArray(), start, end - start,
+ bidiFlags, widths);
+ }
+
+ @LayoutlibDelegate
+ /* package */static int native_getTextGlyphs(int native_object, String text, int start,
+ int end, int contextStart, int contextEnd, int flags, char[] glyphs) {
+ // FIXME
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float native_getTextRunAdvances(int native_object,
+ char[] text, int index, int count, int contextIndex, int contextCount,
+ int flags, float[] advances, int advancesIndex) {
+
+ if (advances != null)
+ for (int i = advancesIndex; i< advancesIndex+count; i++)
+ advances[i]=0;
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(native_object);
+ if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) {
+ return 0.f;
+ }
+ boolean isRtl = isRtl(flags);
+
+ int limit = index + count;
+ return new BidiRenderer(null, delegate, text).renderText(
+ index, limit, isRtl, advances, advancesIndex, false, 0, 0);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float native_getTextRunAdvances(int native_object,
+ String text, int start, int end, int contextStart, int contextEnd,
+ int flags, float[] advances, int advancesIndex) {
+ // FIXME: support contextStart and contextEnd
+ int count = end - start;
+ char[] buffer = TemporaryBuffer.obtain(count);
+ TextUtils.getChars(text, start, end, buffer, 0);
+
+ return native_getTextRunAdvances(native_object, buffer, 0, count, contextStart,
+ contextEnd - contextStart, flags, advances, advancesIndex);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getTextRunCursor(Paint thisPaint, int native_object, char[] text,
+ int contextStart, int contextLength, int flags, int offset, int cursorOpt) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Paint.getTextRunCursor is not supported.", null, null /*data*/);
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getTextRunCursor(Paint thisPaint, int native_object, String text,
+ int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Paint.getTextRunCursor is not supported.", null, null /*data*/);
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_getTextPath(int native_object, int bidiFlags,
+ char[] text, int index, int count, float x, float y, int path) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Paint.getTextPath is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_getTextPath(int native_object, int bidiFlags,
+ String text, int start, int end, float x, float y, int path) {
+ // FIXME
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Paint.getTextPath is not supported.", null, null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeGetStringBounds(int nativePaint, String text, int start,
+ int end, int bidiFlags, Rect bounds) {
+ nativeGetCharArrayBounds(nativePaint, text.toCharArray(), start, end - start, bidiFlags,
+ bounds);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeGetCharArrayBounds(int nativePaint, char[] text, int index,
+ int count, int bidiFlags, Rect bounds) {
+
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) {
+ return;
+ }
+ int w = (int) delegate.measureText(text, index, count, isRtl(bidiFlags));
+ int h= delegate.getFonts().get(0).mMetrics.getHeight();
+ bounds.set(0, 0, w, h);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void finalizer(int nativePaint) {
+ sManager.removeJavaReferenceFor(nativePaint);
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ /*package*/ Paint_Delegate() {
+ reset();
+ }
+
+ private Paint_Delegate(Paint_Delegate paint) {
+ set(paint);
+ }
+
+ private void set(Paint_Delegate paint) {
+ mFlags = paint.mFlags;
+ mColor = paint.mColor;
+ mStyle = paint.mStyle;
+ mCap = paint.mCap;
+ mJoin = paint.mJoin;
+ mTextAlign = paint.mTextAlign;
+ mTypeface = paint.mTypeface;
+ mStrokeWidth = paint.mStrokeWidth;
+ mStrokeMiter = paint.mStrokeMiter;
+ mTextSize = paint.mTextSize;
+ mTextScaleX = paint.mTextScaleX;
+ mTextSkewX = paint.mTextSkewX;
+ mXfermode = paint.mXfermode;
+ mColorFilter = paint.mColorFilter;
+ mShader = paint.mShader;
+ mPathEffect = paint.mPathEffect;
+ mMaskFilter = paint.mMaskFilter;
+ mRasterizer = paint.mRasterizer;
+ mHintingMode = paint.mHintingMode;
+ updateFontObject();
+ }
+
+ private void reset() {
+ mFlags = Paint.DEFAULT_PAINT_FLAGS;
+ mColor = 0xFF000000;
+ mStyle = Paint.Style.FILL.nativeInt;
+ mCap = Paint.Cap.BUTT.nativeInt;
+ mJoin = Paint.Join.MITER.nativeInt;
+ mTextAlign = 0;
+ mTypeface = Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance);
+ mStrokeWidth = 1.f;
+ mStrokeMiter = 4.f;
+ mTextSize = 20.f;
+ mTextScaleX = 1.f;
+ mTextSkewX = 0.f;
+ mXfermode = null;
+ mColorFilter = null;
+ mShader = null;
+ mPathEffect = null;
+ mMaskFilter = null;
+ mRasterizer = null;
+ updateFontObject();
+ mHintingMode = Paint.HINTING_ON;
+ }
+
+ /**
+ * Update the {@link Font} object from the typeface, text size and scaling
+ */
+ @SuppressWarnings("deprecation")
+ private void updateFontObject() {
+ if (mTypeface != null) {
+ // Get the fonts from the TypeFace object.
+ List<Font> fonts = mTypeface.getFonts();
+
+ // create new font objects as well as FontMetrics, based on the current text size
+ // and skew info.
+ ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size());
+ for (Font font : fonts) {
+ FontInfo info = new FontInfo();
+ info.mFont = font.deriveFont(mTextSize);
+ if (mTextScaleX != 1.0 || mTextSkewX != 0) {
+ // TODO: support skew
+ info.mFont = info.mFont.deriveFont(new AffineTransform(
+ mTextScaleX, mTextSkewX, 0, 1, 0, 0));
+ }
+ // The metrics here don't have anti-aliasing set.
+ info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);
+
+ infoList.add(info);
+ }
+
+ mFonts = Collections.unmodifiableList(infoList);
+ }
+ }
+
+ /*package*/ float measureText(char[] text, int index, int count, boolean isRtl) {
+ return new BidiRenderer(null, this, text).renderText(
+ index, index + count, isRtl, null, 0, false, 0, 0);
+ }
+
+ private float getFontMetrics(FontMetrics metrics) {
+ if (mFonts.size() > 0) {
+ java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
+ if (metrics != null) {
+ // Android expects negative ascent so we invert the value from Java.
+ metrics.top = - javaMetrics.getMaxAscent();
+ metrics.ascent = - javaMetrics.getAscent();
+ metrics.descent = javaMetrics.getDescent();
+ metrics.bottom = javaMetrics.getMaxDescent();
+ metrics.leading = javaMetrics.getLeading();
+ }
+
+ return javaMetrics.getHeight();
+ }
+
+ return 0;
+ }
+
+ private void setTextLocale(String locale) {
+ mLocale = new Locale(locale);
+ }
+
+ private static void setFlag(Paint thisPaint, int flagMask, boolean flagValue) {
+ // get the delegate from the native int.
+ Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
+ if (delegate == null) {
+ return;
+ }
+
+ if (flagValue) {
+ delegate.mFlags |= flagMask;
+ } else {
+ delegate.mFlags &= ~flagMask;
+ }
+ }
+
+ private static boolean isRtl(int flag) {
+ switch(flag) {
+ case Paint.BIDI_RTL:
+ case Paint.BIDI_FORCE_RTL:
+ case Paint.BIDI_DEFAULT_RTL:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java
new file mode 100644
index 0000000..c448f0e
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.PathDashPathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of PathDashPathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original PathDashPathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public class PathDashPathEffect_Delegate extends PathEffect_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public Stroke getStroke(Paint_Delegate paint) {
+ // FIXME
+ return null;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Path Dash Path Effects are not supported in Layout Preview mode.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate(int native_path, float advance, float phase,
+ int native_style) {
+ PathDashPathEffect_Delegate newDelegate = new PathDashPathEffect_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java
new file mode 100644
index 0000000..bd2b6de
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.PathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of PathEffect have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original PathEffect class.
+ *
+ * This also serve as a base class for all PathEffect delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class PathEffect_Delegate {
+
+ // ---- delegate manager ----
+ protected static final DelegateManager<PathEffect_Delegate> sManager =
+ new DelegateManager<PathEffect_Delegate>(PathEffect_Delegate.class);
+
+ // ---- delegate helper data ----
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ public static PathEffect_Delegate getDelegate(int nativeShader) {
+ return sManager.getDelegate(nativeShader);
+ }
+
+ public abstract Stroke getStroke(Paint_Delegate paint);
+ public abstract boolean isSupported();
+ public abstract String getSupportMessage();
+
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDestructor(int native_patheffect) {
+ sManager.removeJavaReferenceFor(native_patheffect);
+ }
+
+ // ---- Private delegate/helper methods ----
+
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
new file mode 100644
index 0000000..64f19d3
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Path.Direction;
+import android.graphics.Path.FillType;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Area;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Path
+ *
+ * Through the layoutlib_create tool, the original native methods of Path have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Path class.
+ *
+ * @see DelegateManager
+ *
+ */
+public final class Path_Delegate {
+
+ // ---- delegate manager ----
+ private static final DelegateManager<Path_Delegate> sManager =
+ new DelegateManager<Path_Delegate>(Path_Delegate.class);
+
+ // ---- delegate data ----
+ private FillType mFillType = FillType.WINDING;
+ private GeneralPath mPath = new GeneralPath();
+
+ private float mLastX = 0;
+ private float mLastY = 0;
+
+ // ---- Public Helper methods ----
+
+ public static Path_Delegate getDelegate(int nPath) {
+ return sManager.getDelegate(nPath);
+ }
+
+ public Shape getJavaShape() {
+ return mPath;
+ }
+
+ public void setJavaShape(Shape shape) {
+ mPath.reset();
+ mPath.append(shape, false /*connect*/);
+ }
+
+ public void reset() {
+ mPath.reset();
+ }
+
+ public void setPathIterator(PathIterator iterator) {
+ mPath.reset();
+ mPath.append(iterator, false /*connect*/);
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int init1() {
+ // create the delegate
+ Path_Delegate newDelegate = new Path_Delegate();
+
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int init2(int nPath) {
+ // create the delegate
+ Path_Delegate newDelegate = new Path_Delegate();
+
+ // get the delegate to copy, which could be null if nPath is 0
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate != null) {
+ newDelegate.set(pathDelegate);
+ }
+
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_reset(int nPath) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.mPath.reset();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_rewind(int nPath) {
+ // call out to reset since there's nothing to optimize in
+ // terms of data structs.
+ native_reset(nPath);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_set(int native_dst, int native_src) {
+ Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst);
+ if (pathDstDelegate == null) {
+ return;
+ }
+
+ Path_Delegate pathSrcDelegate = sManager.getDelegate(native_src);
+ if (pathSrcDelegate == null) {
+ return;
+ }
+
+ pathDstDelegate.set(pathSrcDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int native_getFillType(int nPath) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return 0;
+ }
+
+ return pathDelegate.mFillType.nativeInt;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setFillType(int nPath, int ft) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.mFillType = Path.sFillTypeArray[ft];
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_isEmpty(int nPath) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return true;
+ }
+
+ return pathDelegate.isEmpty();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean native_isRect(int nPath, RectF rect) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return false;
+ }
+
+ // create an Area that can test if the path is a rect
+ Area area = new Area(pathDelegate.mPath);
+ if (area.isRectangular()) {
+ if (rect != null) {
+ pathDelegate.fillBounds(rect);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_computeBounds(int nPath, RectF bounds) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.fillBounds(bounds);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_incReserve(int nPath, int extraPtCount) {
+ // since we use a java2D path, there's no way to pre-allocate new points,
+ // so we do nothing.
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_moveTo(int nPath, float x, float y) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.moveTo(x, y);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_rMoveTo(int nPath, float dx, float dy) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.rMoveTo(dx, dy);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_lineTo(int nPath, float x, float y) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.lineTo(x, y);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_rLineTo(int nPath, float dx, float dy) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.rLineTo(dx, dy);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_quadTo(int nPath, float x1, float y1, float x2, float y2) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.quadTo(x1, y1, x2, y2);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_rQuadTo(int nPath, float dx1, float dy1, float dx2, float dy2) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.rQuadTo(dx1, dy1, dx2, dy2);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_cubicTo(int nPath, float x1, float y1,
+ float x2, float y2, float x3, float y3) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_rCubicTo(int nPath, float x1, float y1,
+ float x2, float y2, float x3, float y3) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_arcTo(int nPath, RectF oval,
+ float startAngle, float sweepAngle, boolean forceMoveTo) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_close(int nPath) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.close();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addRect(int nPath, RectF rect, int dir) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addRect(int nPath,
+ float left, float top, float right, float bottom, int dir) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.addRect(left, top, right, bottom, dir);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addOval(int nPath, RectF oval, int dir) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.mPath.append(new Ellipse2D.Float(
+ oval.left, oval.top, oval.width(), oval.height()), false);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addCircle(int nPath, float x, float y, float radius, int dir) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ // because x/y is the center of the circle, need to offset this by the radius
+ pathDelegate.mPath.append(new Ellipse2D.Float(
+ x - radius, y - radius, radius * 2, radius * 2), false);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addArc(int nPath, RectF oval,
+ float startAngle, float sweepAngle) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ // because x/y is the center of the circle, need to offset this by the radius
+ pathDelegate.mPath.append(new Arc2D.Float(
+ oval.left, oval.top, oval.width(), oval.height(),
+ -startAngle, -sweepAngle, Arc2D.OPEN), false);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addRoundRect(
+ int nPath, RectF rect, float rx, float ry, int dir) {
+
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.mPath.append(new RoundRectangle2D.Float(
+ rect.left, rect.top, rect.width(), rect.height(), rx * 2, ry * 2), false);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addRoundRect(int nPath, RectF rect, float[] radii, int dir) {
+ // Java2D doesn't support different rounded corners in each corner, so just use the
+ // first value.
+ native_addRoundRect(nPath, rect, radii[0], radii[1], dir);
+
+ // there can be a case where this API is used but with similar values for all corners, so
+ // in that case we don't warn.
+ // we only care if 2 corners are different so just compare to the next one.
+ for (int i = 0 ; i < 3 ; i++) {
+ if (radii[i * 2] != radii[(i + 1) * 2] || radii[i * 2 + 1] != radii[(i + 1) * 2 + 1]) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Different corner sizes are not supported in Path.addRoundRect.",
+ null, null /*data*/);
+ break;
+ }
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addPath(int nPath, int src, float dx, float dy) {
+ addPath(nPath, src, AffineTransform.getTranslateInstance(dx, dy));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addPath(int nPath, int src) {
+ addPath(nPath, src, null /*transform*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_addPath(int nPath, int src, int matrix) {
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
+ if (matrixDelegate == null) {
+ return;
+ }
+
+ addPath(nPath, src, matrixDelegate.getAffineTransform());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_offset(int nPath, float dx, float dy, int dst_path) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ // could be null if the int is 0;
+ Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
+
+ pathDelegate.offset(dx, dy, dstDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_offset(int nPath, float dx, float dy) {
+ native_offset(nPath, dx, dy, 0);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_setLastPoint(int nPath, float dx, float dy) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ pathDelegate.mLastX = dx;
+ pathDelegate.mLastY = dy;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_transform(int nPath, int matrix,
+ int dst_path) {
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
+ }
+
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
+ if (matrixDelegate == null) {
+ return;
+ }
+
+ // this can be null if dst_path is 0
+ Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
+
+ pathDelegate.transform(matrixDelegate, dstDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_transform(int nPath, int matrix) {
+ native_transform(nPath, matrix, 0);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void finalizer(int nPath) {
+ sManager.removeJavaReferenceFor(nPath);
+ }
+
+
+ // ---- Private helper methods ----
+
+ private void set(Path_Delegate delegate) {
+ mPath.reset();
+ setFillType(delegate.mFillType);
+ mPath.append(delegate.mPath, false /*connect*/);
+ }
+
+ private void setFillType(FillType fillType) {
+ mFillType = fillType;
+ mPath.setWindingRule(getWindingRule(fillType));
+ }
+
+ /**
+ * Returns the Java2D winding rules matching a given Android {@link FillType}.
+ * @param type the android fill type
+ * @return the matching java2d winding rule.
+ */
+ private static int getWindingRule(FillType type) {
+ switch (type) {
+ case WINDING:
+ case INVERSE_WINDING:
+ return GeneralPath.WIND_NON_ZERO;
+ case EVEN_ODD:
+ case INVERSE_EVEN_ODD:
+ return GeneralPath.WIND_EVEN_ODD;
+ }
+
+ assert false;
+ throw new IllegalArgumentException();
+ }
+
+ private static Direction getDirection(int direction) {
+ for (Direction d : Direction.values()) {
+ if (direction == d.nativeInt) {
+ return d;
+ }
+ }
+
+ assert false;
+ return null;
+ }
+
+ private static void addPath(int destPath, int srcPath, AffineTransform transform) {
+ Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
+ if (destPathDelegate == null) {
+ return;
+ }
+
+ Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath);
+ if (srcPathDelegate == null) {
+ return;
+ }
+
+ if (transform != null) {
+ destPathDelegate.mPath.append(
+ srcPathDelegate.mPath.getPathIterator(transform), false);
+ } else {
+ destPathDelegate.mPath.append(srcPathDelegate.mPath, false);
+ }
+ }
+
+
+ /**
+ * Returns whether the path is empty.
+ * @return true if the path is empty.
+ */
+ private boolean isEmpty() {
+ return mPath.getCurrentPoint() == null;
+ }
+
+ /**
+ * Fills the given {@link RectF} with the path bounds.
+ * @param bounds the RectF to be filled.
+ */
+ private void fillBounds(RectF bounds) {
+ Rectangle2D rect = mPath.getBounds2D();
+ bounds.left = (float)rect.getMinX();
+ bounds.right = (float)rect.getMaxX();
+ bounds.top = (float)rect.getMinY();
+ bounds.bottom = (float)rect.getMaxY();
+ }
+
+ /**
+ * Set the beginning of the next contour to the point (x,y).
+ *
+ * @param x The x-coordinate of the start of a new contour
+ * @param y The y-coordinate of the start of a new contour
+ */
+ private void moveTo(float x, float y) {
+ mPath.moveTo(mLastX = x, mLastY = y);
+ }
+
+ /**
+ * Set the beginning of the next contour relative to the last point on the
+ * previous contour. If there is no previous contour, this is treated the
+ * same as moveTo().
+ *
+ * @param dx The amount to add to the x-coordinate of the end of the
+ * previous contour, to specify the start of a new contour
+ * @param dy The amount to add to the y-coordinate of the end of the
+ * previous contour, to specify the start of a new contour
+ */
+ private void rMoveTo(float dx, float dy) {
+ dx += mLastX;
+ dy += mLastY;
+ mPath.moveTo(mLastX = dx, mLastY = dy);
+ }
+
+ /**
+ * Add a line from the last point to the specified point (x,y).
+ * If no moveTo() call has been made for this contour, the first point is
+ * automatically set to (0,0).
+ *
+ * @param x The x-coordinate of the end of a line
+ * @param y The y-coordinate of the end of a line
+ */
+ private void lineTo(float x, float y) {
+ mPath.lineTo(mLastX = x, mLastY = y);
+ }
+
+ /**
+ * Same as lineTo, but the coordinates are considered relative to the last
+ * point on this contour. If there is no previous point, then a moveTo(0,0)
+ * is inserted automatically.
+ *
+ * @param dx The amount to add to the x-coordinate of the previous point on
+ * this contour, to specify a line
+ * @param dy The amount to add to the y-coordinate of the previous point on
+ * this contour, to specify a line
+ */
+ private void rLineTo(float dx, float dy) {
+ if (isEmpty()) {
+ mPath.moveTo(mLastX = 0, mLastY = 0);
+ }
+ dx += mLastX;
+ dy += mLastY;
+ mPath.lineTo(mLastX = dx, mLastY = dy);
+ }
+
+ /**
+ * Add a quadratic bezier from the last point, approaching control point
+ * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
+ * this contour, the first point is automatically set to (0,0).
+ *
+ * @param x1 The x-coordinate of the control point on a quadratic curve
+ * @param y1 The y-coordinate of the control point on a quadratic curve
+ * @param x2 The x-coordinate of the end point on a quadratic curve
+ * @param y2 The y-coordinate of the end point on a quadratic curve
+ */
+ private void quadTo(float x1, float y1, float x2, float y2) {
+ mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
+ }
+
+ /**
+ * Same as quadTo, but the coordinates are considered relative to the last
+ * point on this contour. If there is no previous point, then a moveTo(0,0)
+ * is inserted automatically.
+ *
+ * @param dx1 The amount to add to the x-coordinate of the last point on
+ * this contour, for the control point of a quadratic curve
+ * @param dy1 The amount to add to the y-coordinate of the last point on
+ * this contour, for the control point of a quadratic curve
+ * @param dx2 The amount to add to the x-coordinate of the last point on
+ * this contour, for the end point of a quadratic curve
+ * @param dy2 The amount to add to the y-coordinate of the last point on
+ * this contour, for the end point of a quadratic curve
+ */
+ private void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
+ if (isEmpty()) {
+ mPath.moveTo(mLastX = 0, mLastY = 0);
+ }
+ dx1 += mLastX;
+ dy1 += mLastY;
+ dx2 += mLastX;
+ dy2 += mLastY;
+ mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
+ }
+
+ /**
+ * Add a cubic bezier from the last point, approaching control points
+ * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
+ * made for this contour, the first point is automatically set to (0,0).
+ *
+ * @param x1 The x-coordinate of the 1st control point on a cubic curve
+ * @param y1 The y-coordinate of the 1st control point on a cubic curve
+ * @param x2 The x-coordinate of the 2nd control point on a cubic curve
+ * @param y2 The y-coordinate of the 2nd control point on a cubic curve
+ * @param x3 The x-coordinate of the end point on a cubic curve
+ * @param y3 The y-coordinate of the end point on a cubic curve
+ */
+ private void cubicTo(float x1, float y1, float x2, float y2,
+ float x3, float y3) {
+ mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
+ }
+
+ /**
+ * Same as cubicTo, but the coordinates are considered relative to the
+ * current point on this contour. If there is no previous point, then a
+ * moveTo(0,0) is inserted automatically.
+ */
+ private void rCubicTo(float dx1, float dy1, float dx2, float dy2,
+ float dx3, float dy3) {
+ if (isEmpty()) {
+ mPath.moveTo(mLastX = 0, mLastY = 0);
+ }
+ dx1 += mLastX;
+ dy1 += mLastY;
+ dx2 += mLastX;
+ dy2 += mLastY;
+ dx3 += mLastX;
+ dy3 += mLastY;
+ mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
+ }
+
+ /**
+ * Append the specified arc to the path as a new contour. If the start of
+ * the path is different from the path's current last point, then an
+ * automatic lineTo() is added to connect the current contour to the
+ * start of the arc. However, if the path is empty, then we call moveTo()
+ * with the first point of the arc. The sweep angle is tread mod 360.
+ *
+ * @param oval The bounds of oval defining shape and size of the arc
+ * @param startAngle Starting angle (in degrees) where the arc begins
+ * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated
+ * mod 360.
+ * @param forceMoveTo If true, always begin a new contour with the arc
+ */
+ private void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) {
+ Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), -startAngle,
+ -sweepAngle, Arc2D.OPEN);
+ mPath.append(arc, true /*connect*/);
+
+ resetLastPointFromPath();
+ }
+
+ /**
+ * Close the current contour. If the current point is not equal to the
+ * first point of the contour, a line segment is automatically added.
+ */
+ private void close() {
+ mPath.closePath();
+ }
+
+ private void resetLastPointFromPath() {
+ Point2D last = mPath.getCurrentPoint();
+ mLastX = (float) last.getX();
+ mLastY = (float) last.getY();
+ }
+
+ /**
+ * Add a closed rectangle contour to the path
+ *
+ * @param left The left side of a rectangle to add to the path
+ * @param top The top of a rectangle to add to the path
+ * @param right The right side of a rectangle to add to the path
+ * @param bottom The bottom of a rectangle to add to the path
+ * @param dir The direction to wind the rectangle's contour
+ */
+ private void addRect(float left, float top, float right, float bottom,
+ int dir) {
+ moveTo(left, top);
+
+ Direction direction = getDirection(dir);
+
+ switch (direction) {
+ case CW:
+ lineTo(right, top);
+ lineTo(right, bottom);
+ lineTo(left, bottom);
+ break;
+ case CCW:
+ lineTo(left, bottom);
+ lineTo(right, bottom);
+ lineTo(right, top);
+ break;
+ }
+
+ close();
+
+ resetLastPointFromPath();
+ }
+
+ /**
+ * Offset the path by (dx,dy), returning true on success
+ *
+ * @param dx The amount in the X direction to offset the entire path
+ * @param dy The amount in the Y direction to offset the entire path
+ * @param dst The translated path is written here. If this is null, then
+ * the original path is modified.
+ */
+ public void offset(float dx, float dy, Path_Delegate dst) {
+ GeneralPath newPath = new GeneralPath();
+
+ PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
+
+ newPath.append(iterator, false /*connect*/);
+
+ if (dst != null) {
+ dst.mPath = newPath;
+ } else {
+ mPath = newPath;
+ }
+ }
+
+ /**
+ * Transform the points in this path by matrix, and write the answer
+ * into dst. If dst is null, then the the original path is modified.
+ *
+ * @param matrix The matrix to apply to the path
+ * @param dst The transformed path is written here. If dst is null,
+ * then the the original path is modified
+ */
+ public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
+ if (matrix.hasPerspective()) {
+ assert false;
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
+ "android.graphics.Path#transform() only " +
+ "supports affine transformations.", null, null /*data*/);
+ }
+
+ GeneralPath newPath = new GeneralPath();
+
+ PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
+
+ newPath.append(iterator, false /*connect*/);
+
+ if (dst != null) {
+ dst.mPath = newPath;
+ } else {
+ mPath = newPath;
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java
new file mode 100644
index 0000000..4ab044b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/PixelXorXfermode_Delegate.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Composite;
+
+/**
+ * Delegate implementing the native methods of android.graphics.PixelXorXfermode
+ *
+ * Through the layoutlib_create tool, the original native methods of PixelXorXfermode have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original PixelXorXfermode class.
+ *
+ * Because this extends {@link Xfermode_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by
+ * {@link Xfermode_Delegate}.
+ *
+ * @see Xfermode_Delegate
+ */
+public class PixelXorXfermode_Delegate extends Xfermode_Delegate {
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public Composite getComposite(int alpha) {
+ // FIXME
+ return null;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Pixel XOR Xfermodes are not supported in Layout Preview mode.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate(int opColor) {
+ PixelXorXfermode_Delegate newDelegate = new PixelXorXfermode_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
new file mode 100644
index 0000000..c45dbaa
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.PorterDuffColorFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of PorterDuffColorFilter have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original PorterDuffColorFilter class.
+ *
+ * Because this extends {@link ColorFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link ColorFilter_Delegate}.
+ *
+ * @see ColorFilter_Delegate
+ *
+ */
+public class PorterDuffColorFilter_Delegate extends ColorFilter_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "PorterDuff Color Filters are not supported.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int native_CreatePorterDuffFilter(int srcColor, int porterDuffMode) {
+ PorterDuffColorFilter_Delegate newDelegate = new PorterDuffColorFilter_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nCreatePorterDuffFilter(int nativeFilter, int srcColor,
+ int porterDuffMode) {
+ // pass
+ return 0;
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
new file mode 100644
index 0000000..4301c1a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.AlphaComposite;
+import java.awt.Composite;
+
+/**
+ * Delegate implementing the native methods of android.graphics.PorterDuffXfermode
+ *
+ * Through the layoutlib_create tool, the original native methods of PorterDuffXfermode have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original PorterDuffXfermode class.
+ *
+ * Because this extends {@link Xfermode_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by
+ * {@link Xfermode_Delegate}.
+ *
+ */
+public class PorterDuffXfermode_Delegate extends Xfermode_Delegate {
+
+ // ---- delegate data ----
+
+ private final int mMode;
+
+ // ---- Public Helper methods ----
+
+ public PorterDuff.Mode getMode() {
+ return getPorterDuffMode(mMode);
+ }
+
+ @Override
+ public Composite getComposite(int alpha) {
+ return getComposite(getPorterDuffMode(mMode), alpha);
+ }
+
+ @Override
+ public boolean isSupported() {
+ return true;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ // no message since isSupported returns true;
+ return null;
+ }
+
+ public static PorterDuff.Mode getPorterDuffMode(int mode) {
+ for (PorterDuff.Mode m : PorterDuff.Mode.values()) {
+ if (m.nativeInt == mode) {
+ return m;
+ }
+ }
+
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ String.format("Unknown PorterDuff.Mode: %d", mode), null /*data*/);
+ assert false;
+ return PorterDuff.Mode.SRC_OVER;
+ }
+
+ public static Composite getComposite(PorterDuff.Mode mode, int alpha) {
+ float falpha = alpha != 0xFF ? (float)alpha / 255.f : 1.f;
+ switch (mode) {
+ case CLEAR:
+ return AlphaComposite.getInstance(AlphaComposite.CLEAR, falpha);
+ case DARKEN:
+ break;
+ case DST:
+ return AlphaComposite.getInstance(AlphaComposite.DST, falpha);
+ case DST_ATOP:
+ return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, falpha);
+ case DST_IN:
+ return AlphaComposite.getInstance(AlphaComposite.DST_IN, falpha);
+ case DST_OUT:
+ return AlphaComposite.getInstance(AlphaComposite.DST_OUT, falpha);
+ case DST_OVER:
+ return AlphaComposite.getInstance(AlphaComposite.DST_OVER, falpha);
+ case LIGHTEN:
+ break;
+ case MULTIPLY:
+ break;
+ case SCREEN:
+ break;
+ case SRC:
+ return AlphaComposite.getInstance(AlphaComposite.SRC, falpha);
+ case SRC_ATOP:
+ return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, falpha);
+ case SRC_IN:
+ return AlphaComposite.getInstance(AlphaComposite.SRC_IN, falpha);
+ case SRC_OUT:
+ return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, falpha);
+ case SRC_OVER:
+ return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha);
+ case XOR:
+ return AlphaComposite.getInstance(AlphaComposite.XOR, falpha);
+ }
+
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
+ String.format("Unsupported PorterDuff Mode: %s", mode.name()),
+ null, null /*data*/);
+
+ return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha);
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreateXfermode(int mode) {
+ PorterDuffXfermode_Delegate newDelegate = new PorterDuffXfermode_Delegate(mode);
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ private PorterDuffXfermode_Delegate(int mode) {
+ mMode = mode;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
new file mode 100644
index 0000000..3fe45fa
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Shader.TileMode;
+
+/**
+ * Delegate implementing the native methods of android.graphics.RadialGradient
+ *
+ * Through the layoutlib_create tool, the original native methods of RadialGradient have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original RadialGradient class.
+ *
+ * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
+ *
+ * @see Shader_Delegate
+ *
+ */
+public class RadialGradient_Delegate extends Gradient_Delegate {
+
+ // ---- delegate data ----
+ private java.awt.Paint mJavaPaint;
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public java.awt.Paint getJavaPaint() {
+ return mJavaPaint;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate1(float x, float y, float radius,
+ int colors[], float positions[], int tileMode) {
+ RadialGradient_Delegate newDelegate = new RadialGradient_Delegate(x, y, radius,
+ colors, positions, Shader_Delegate.getTileMode(tileMode));
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate2(float x, float y, float radius,
+ int color0, int color1, int tileMode) {
+ return nativeCreate1(x, y, radius, new int[] { color0, color1 }, null /*positions*/,
+ tileMode);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativePostCreate1(int native_shader, float x, float y, float radius,
+ int colors[], float positions[], int tileMode) {
+ // nothing to be done here.
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativePostCreate2(int native_shader, float x, float y, float radius,
+ int color0, int color1, int tileMode) {
+ // nothing to be done here.
+ return 0;
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ /**
+ * Create a shader that draws a radial gradient given the center and radius.
+ *
+ * @param x The x-coordinate of the center of the radius
+ * @param y The y-coordinate of the center of the radius
+ * @param radius Must be positive. The radius of the circle for this
+ * gradient
+ * @param colors The colors to be distributed between the center and edge of
+ * the circle
+ * @param positions May be NULL. The relative position of each corresponding
+ * color in the colors array. If this is NULL, the the colors are
+ * distributed evenly between the center and edge of the circle.
+ * @param tile The Shader tiling mode
+ */
+ private RadialGradient_Delegate(float x, float y, float radius, int colors[], float positions[],
+ TileMode tile) {
+ super(colors, positions);
+ mJavaPaint = new RadialGradientPaint(x, y, radius, mColors, mPositions, tile);
+ }
+
+ private class RadialGradientPaint extends GradientPaint {
+
+ private final float mX;
+ private final float mY;
+ private final float mRadius;
+
+ public RadialGradientPaint(float x, float y, float radius,
+ int[] colors, float[] positions, TileMode mode) {
+ super(colors, positions, mode);
+ mX = x;
+ mY = y;
+ mRadius = radius;
+ }
+
+ @Override
+ public java.awt.PaintContext createContext(
+ java.awt.image.ColorModel colorModel,
+ java.awt.Rectangle deviceBounds,
+ java.awt.geom.Rectangle2D userBounds,
+ java.awt.geom.AffineTransform xform,
+ java.awt.RenderingHints hints) {
+ precomputeGradientColors();
+
+ java.awt.geom.AffineTransform canvasMatrix;
+ try {
+ canvasMatrix = xform.createInverse();
+ } catch (java.awt.geom.NoninvertibleTransformException e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+ "Unable to inverse matrix in RadialGradient", e, null /*data*/);
+ canvasMatrix = new java.awt.geom.AffineTransform();
+ }
+
+ java.awt.geom.AffineTransform localMatrix = getLocalMatrix();
+ try {
+ localMatrix = localMatrix.createInverse();
+ } catch (java.awt.geom.NoninvertibleTransformException e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+ "Unable to inverse matrix in RadialGradient", e, null /*data*/);
+ localMatrix = new java.awt.geom.AffineTransform();
+ }
+
+ return new RadialGradientPaintContext(canvasMatrix, localMatrix, colorModel);
+ }
+
+ private class RadialGradientPaintContext implements java.awt.PaintContext {
+
+ private final java.awt.geom.AffineTransform mCanvasMatrix;
+ private final java.awt.geom.AffineTransform mLocalMatrix;
+ private final java.awt.image.ColorModel mColorModel;
+
+ public RadialGradientPaintContext(
+ java.awt.geom.AffineTransform canvasMatrix,
+ java.awt.geom.AffineTransform localMatrix,
+ java.awt.image.ColorModel colorModel) {
+ mCanvasMatrix = canvasMatrix;
+ mLocalMatrix = localMatrix;
+ mColorModel = colorModel;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public java.awt.image.ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ @Override
+ public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+ java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h,
+ java.awt.image.BufferedImage.TYPE_INT_ARGB);
+
+ int[] data = new int[w*h];
+
+ // compute distance from each point to the center, and figure out the distance from
+ // it.
+ int index = 0;
+ float[] pt1 = new float[2];
+ float[] pt2 = new float[2];
+ for (int iy = 0 ; iy < h ; iy++) {
+ for (int ix = 0 ; ix < w ; ix++) {
+ // handle the canvas transform
+ pt1[0] = x + ix;
+ pt1[1] = y + iy;
+ mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
+
+ // handle the local matrix
+ pt1[0] = pt2[0] - mX;
+ pt1[1] = pt2[1] - mY;
+ mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
+
+ float _x = pt2[0];
+ float _y = pt2[1];
+ float distance = (float) Math.sqrt(_x * _x + _y * _y);
+
+ data[index++] = getGradientColor(distance / mRadius);
+ }
+ }
+
+ image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
+
+ return image.getRaster();
+ }
+
+ }
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java
new file mode 100644
index 0000000..2812b6b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Rasterizer_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Rasterizer
+ *
+ * Through the layoutlib_create tool, the original native methods of Rasterizer have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Rasterizer class.
+ *
+ * This also serve as a base class for all Rasterizer delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class Rasterizer_Delegate {
+
+ // ---- delegate manager ----
+ protected static final DelegateManager<Rasterizer_Delegate> sManager =
+ new DelegateManager<Rasterizer_Delegate>(Rasterizer_Delegate.class);
+
+ // ---- delegate helper data ----
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ public static Rasterizer_Delegate getDelegate(int nativeShader) {
+ return sManager.getDelegate(nativeShader);
+ }
+
+ public abstract boolean isSupported();
+ public abstract String getSupportMessage();
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static void finalizer(int native_instance) {
+ sManager.removeJavaReferenceFor(native_instance);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java
new file mode 100644
index 0000000..cb31b8f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.os.Parcel;
+
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Region
+ *
+ * Through the layoutlib_create tool, the original native methods of Region have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Region class.
+ *
+ * This also serve as a base class for all Region delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public class Region_Delegate {
+
+ // ---- delegate manager ----
+ protected static final DelegateManager<Region_Delegate> sManager =
+ new DelegateManager<Region_Delegate>(Region_Delegate.class);
+
+ // ---- delegate helper data ----
+
+ // ---- delegate data ----
+ private Area mArea = new Area();
+
+ // ---- Public Helper methods ----
+
+ public static Region_Delegate getDelegate(int nativeShader) {
+ return sManager.getDelegate(nativeShader);
+ }
+
+ public Area getJavaArea() {
+ return mArea;
+ }
+
+ /**
+ * Combines two {@link Shape} into another one (actually an {@link Area}), according
+ * to the given {@link Region.Op}.
+ *
+ * If the Op is not one that combines two shapes, then this return null
+ *
+ * @param shape1 the firt shape to combine which can be null if there's no original clip.
+ * @param shape2 the 2nd shape to combine
+ * @param regionOp the operande for the combine
+ * @return a new area or null.
+ */
+ public static Area combineShapes(Shape shape1, Shape shape2, int regionOp) {
+ if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
+ // if shape1 is null (empty), then the result is null.
+ if (shape1 == null) {
+ return null;
+ }
+
+ // result is always a new area.
+ Area result = new Area(shape1);
+ result.subtract(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+ return result;
+
+ } else if (regionOp == Region.Op.INTERSECT.nativeInt) {
+ // if shape1 is null, then the result is simply shape2.
+ if (shape1 == null) {
+ return new Area(shape2);
+ }
+
+ // result is always a new area.
+ Area result = new Area(shape1);
+ result.intersect(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+ return result;
+
+ } else if (regionOp == Region.Op.UNION.nativeInt) {
+ // if shape1 is null, then the result is simply shape2.
+ if (shape1 == null) {
+ return new Area(shape2);
+ }
+
+ // result is always a new area.
+ Area result = new Area(shape1);
+ result.add(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+ return result;
+
+ } else if (regionOp == Region.Op.XOR.nativeInt) {
+ // if shape1 is null, then the result is simply shape2
+ if (shape1 == null) {
+ return new Area(shape2);
+ }
+
+ // result is always a new area.
+ Area result = new Area(shape1);
+ result.exclusiveOr(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
+ return result;
+
+ } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) {
+ // result is always a new area.
+ Area result = new Area(shape2);
+
+ if (shape1 != null) {
+ result.subtract(shape1 instanceof Area ? (Area) shape1 : new Area(shape1));
+ }
+
+ return result;
+ }
+
+ return null;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static boolean isEmpty(Region thisRegion) {
+ Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+ if (regionDelegate == null) {
+ return true;
+ }
+
+ return regionDelegate.mArea.isEmpty();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean isRect(Region thisRegion) {
+ Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+ if (regionDelegate == null) {
+ return true;
+ }
+
+ return regionDelegate.mArea.isRectangular();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean isComplex(Region thisRegion) {
+ Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+ if (regionDelegate == null) {
+ return true;
+ }
+
+ return regionDelegate.mArea.isSingular() == false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean contains(Region thisRegion, int x, int y) {
+ Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+ if (regionDelegate == null) {
+ return false;
+ }
+
+ return regionDelegate.mArea.contains(x, y);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean quickContains(Region thisRegion,
+ int left, int top, int right, int bottom) {
+ Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+ if (regionDelegate == null) {
+ return false;
+ }
+
+ return regionDelegate.mArea.isRectangular() &&
+ regionDelegate.mArea.contains(left, top, right - left, bottom - top);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean quickReject(Region thisRegion,
+ int left, int top, int right, int bottom) {
+ Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+ if (regionDelegate == null) {
+ return false;
+ }
+
+ return regionDelegate.mArea.isEmpty() ||
+ regionDelegate.mArea.intersects(left, top, right - left, bottom - top) == false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean quickReject(Region thisRegion, Region rgn) {
+ Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+ if (regionDelegate == null) {
+ return false;
+ }
+
+ Region_Delegate targetRegionDelegate = sManager.getDelegate(rgn.mNativeRegion);
+ if (targetRegionDelegate == null) {
+ return false;
+ }
+
+ return regionDelegate.mArea.isEmpty() ||
+ regionDelegate.mArea.getBounds().intersects(
+ targetRegionDelegate.mArea.getBounds()) == false;
+
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void translate(Region thisRegion, int dx, int dy, Region dst) {
+ Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+ if (regionDelegate == null) {
+ return;
+ }
+
+ Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion);
+ if (targetRegionDelegate == null) {
+ return;
+ }
+
+ if (regionDelegate.mArea.isEmpty()) {
+ targetRegionDelegate.mArea = new Area();
+ } else {
+ targetRegionDelegate.mArea = new Area(regionDelegate.mArea);
+ AffineTransform mtx = new AffineTransform();
+ mtx.translate(dx, dy);
+ targetRegionDelegate.mArea.transform(mtx);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void scale(Region thisRegion, float scale, Region dst) {
+ Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
+ if (regionDelegate == null) {
+ return;
+ }
+
+ Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion);
+ if (targetRegionDelegate == null) {
+ return;
+ }
+
+ if (regionDelegate.mArea.isEmpty()) {
+ targetRegionDelegate.mArea = new Area();
+ } else {
+ targetRegionDelegate.mArea = new Area(regionDelegate.mArea);
+ AffineTransform mtx = new AffineTransform();
+ mtx.scale(scale, scale);
+ targetRegionDelegate.mArea.transform(mtx);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeConstructor() {
+ Region_Delegate newDelegate = new Region_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDestructor(int native_region) {
+ sManager.removeJavaReferenceFor(native_region);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeSetRegion(int native_dst, int native_src) {
+ Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+ if (dstRegion == null) {
+ return true;
+ }
+
+ Region_Delegate srcRegion = sManager.getDelegate(native_src);
+ if (srcRegion == null) {
+ return true;
+ }
+
+ dstRegion.mArea.reset();
+ dstRegion.mArea.add(srcRegion.mArea);
+
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeSetRect(int native_dst,
+ int left, int top, int right, int bottom) {
+ Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+ if (dstRegion == null) {
+ return true;
+ }
+
+ dstRegion.mArea = new Area(new Rectangle2D.Float(left, top, right - left, bottom - top));
+ return dstRegion.mArea.getBounds().isEmpty() == false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeSetPath(int native_dst, int native_path, int native_clip) {
+ Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+ if (dstRegion == null) {
+ return true;
+ }
+
+ Path_Delegate path = Path_Delegate.getDelegate(native_path);
+ if (path == null) {
+ return true;
+ }
+
+ dstRegion.mArea = new Area(path.getJavaShape());
+
+ Region_Delegate clip = sManager.getDelegate(native_clip);
+ if (clip != null) {
+ dstRegion.mArea.subtract(clip.getJavaArea());
+ }
+
+ return dstRegion.mArea.getBounds().isEmpty() == false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeGetBounds(int native_region, Rect rect) {
+ Region_Delegate region = sManager.getDelegate(native_region);
+ if (region == null) {
+ return true;
+ }
+
+ Rectangle bounds = region.mArea.getBounds();
+ if (bounds.isEmpty()) {
+ rect.left = rect.top = rect.right = rect.bottom = 0;
+ return false;
+ }
+
+ rect.left = bounds.x;
+ rect.top = bounds.y;
+ rect.right = bounds.x + bounds.width;
+ rect.bottom = bounds.y + bounds.height;
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeGetBoundaryPath(int native_region, int native_path) {
+ Region_Delegate region = sManager.getDelegate(native_region);
+ if (region == null) {
+ return false;
+ }
+
+ Path_Delegate path = Path_Delegate.getDelegate(native_path);
+ if (path == null) {
+ return false;
+ }
+
+ if (region.mArea.isEmpty()) {
+ path.reset();
+ return false;
+ }
+
+ path.setPathIterator(region.mArea.getPathIterator(new AffineTransform()));
+ return true;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeOp(int native_dst,
+ int left, int top, int right, int bottom, int op) {
+ Region_Delegate region = sManager.getDelegate(native_dst);
+ if (region == null) {
+ return false;
+ }
+
+ region.mArea = combineShapes(region.mArea,
+ new Rectangle2D.Float(left, top, right - left, bottom - top), op);
+
+ assert region.mArea != null;
+ if (region.mArea != null) {
+ region.mArea = new Area();
+ }
+
+ return region.mArea.getBounds().isEmpty() == false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeOp(int native_dst, Rect rect, int native_region, int op) {
+ Region_Delegate region = sManager.getDelegate(native_dst);
+ if (region == null) {
+ return false;
+ }
+
+ region.mArea = combineShapes(region.mArea,
+ new Rectangle2D.Float(rect.left, rect.top, rect.width(), rect.height()), op);
+
+ assert region.mArea != null;
+ if (region.mArea != null) {
+ region.mArea = new Area();
+ }
+
+ return region.mArea.getBounds().isEmpty() == false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeOp(int native_dst,
+ int native_region1, int native_region2, int op) {
+ Region_Delegate dstRegion = sManager.getDelegate(native_dst);
+ if (dstRegion == null) {
+ return true;
+ }
+
+ Region_Delegate region1 = sManager.getDelegate(native_region1);
+ if (region1 == null) {
+ return false;
+ }
+
+ Region_Delegate region2 = sManager.getDelegate(native_region2);
+ if (region2 == null) {
+ return false;
+ }
+
+ dstRegion.mArea = combineShapes(region1.mArea, region2.mArea, op);
+
+ assert dstRegion.mArea != null;
+ if (dstRegion.mArea != null) {
+ dstRegion.mArea = new Area();
+ }
+
+ return dstRegion.mArea.getBounds().isEmpty() == false;
+
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreateFromParcel(Parcel p) {
+ // This is only called by Region.CREATOR (Parcelable.Creator<Region>), which is only
+ // used during aidl call so really this should not be called.
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "AIDL is not suppored, and therefore Regions cannot be created from parcels.",
+ null /*data*/);
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeWriteToParcel(int native_region,
+ Parcel p) {
+ // This is only called when sending a region through aidl, so really this should not
+ // be called.
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "AIDL is not suppored, and therefore Regions cannot be written to parcels.",
+ null /*data*/);
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeEquals(int native_r1, int native_r2) {
+ Region_Delegate region1 = sManager.getDelegate(native_r1);
+ if (region1 == null) {
+ return false;
+ }
+
+ Region_Delegate region2 = sManager.getDelegate(native_r2);
+ if (region2 == null) {
+ return false;
+ }
+
+ return region1.mArea.equals(region2.mArea);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String nativeToString(int native_region) {
+ Region_Delegate region = sManager.getDelegate(native_region);
+ if (region == null) {
+ return "not found";
+ }
+
+ return region.mArea.toString();
+ }
+
+ // ---- Private delegate/helper methods ----
+
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java
new file mode 100644
index 0000000..368c0384
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Shader.TileMode;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Shader
+ *
+ * Through the layoutlib_create tool, the original native methods of Shader have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Shader class.
+ *
+ * This also serve as a base class for all Shader delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class Shader_Delegate {
+
+ // ---- delegate manager ----
+ protected static final DelegateManager<Shader_Delegate> sManager =
+ new DelegateManager<Shader_Delegate>(Shader_Delegate.class);
+
+ // ---- delegate helper data ----
+
+ // ---- delegate data ----
+ private Matrix_Delegate mLocalMatrix = null;
+
+ // ---- Public Helper methods ----
+
+ public static Shader_Delegate getDelegate(int nativeShader) {
+ return sManager.getDelegate(nativeShader);
+ }
+
+ /**
+ * Returns the {@link TileMode} matching the given int.
+ * @param tileMode the tile mode int value
+ * @return the TileMode enum.
+ */
+ public static TileMode getTileMode(int tileMode) {
+ for (TileMode tm : TileMode.values()) {
+ if (tm.nativeInt == tileMode) {
+ return tm;
+ }
+ }
+
+ assert false;
+ return TileMode.CLAMP;
+ }
+
+ public abstract java.awt.Paint getJavaPaint();
+ public abstract boolean isSupported();
+ public abstract String getSupportMessage();
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDestructor(int native_shader, int native_skiaShader) {
+ sManager.removeJavaReferenceFor(native_shader);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeSetLocalMatrix(int native_shader, int native_skiaShader,
+ int matrix_instance) {
+ // get the delegate from the native int.
+ Shader_Delegate shaderDelegate = sManager.getDelegate(native_shader);
+ if (shaderDelegate == null) {
+ return;
+ }
+
+ shaderDelegate.mLocalMatrix = Matrix_Delegate.getDelegate(matrix_instance);
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ protected java.awt.geom.AffineTransform getLocalMatrix() {
+ if (mLocalMatrix != null) {
+ return mLocalMatrix.getAffineTransform();
+ }
+
+ return new java.awt.geom.AffineTransform();
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java
new file mode 100644
index 0000000..410df0c
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Stroke;
+
+/**
+ * Delegate implementing the native methods of android.graphics.SumPathEffect
+ *
+ * Through the layoutlib_create tool, the original native methods of SumPathEffect have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original SumPathEffect class.
+ *
+ * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
+ *
+ * @see PathEffect_Delegate
+ *
+ */
+public class SumPathEffect_Delegate extends PathEffect_Delegate {
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public Stroke getStroke(Paint_Delegate paint) {
+ // FIXME
+ return null;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+
+ @Override
+ public String getSupportMessage() {
+ return "Sum Path Effects are not supported in Layout Preview mode.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate(int first, int second) {
+ SumPathEffect_Delegate newDelegate = new SumPathEffect_Delegate();
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ // ---- Private delegate/helper methods ----
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java
new file mode 100644
index 0000000..13ae12e
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.SweepGradient
+ *
+ * Through the layoutlib_create tool, the original native methods of SweepGradient have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original SweepGradient class.
+ *
+ * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
+ * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
+ *
+ * @see Shader_Delegate
+ *
+ */
+public class SweepGradient_Delegate extends Gradient_Delegate {
+
+ // ---- delegate data ----
+ private java.awt.Paint mJavaPaint;
+
+ // ---- Public Helper methods ----
+
+ @Override
+ public java.awt.Paint getJavaPaint() {
+ return mJavaPaint;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate1(float x, float y, int colors[], float positions[]) {
+ SweepGradient_Delegate newDelegate = new SweepGradient_Delegate(x, y, colors, positions);
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeCreate2(float x, float y, int color0, int color1) {
+ return nativeCreate1(x, y, new int[] { color0, color1 }, null /*positions*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativePostCreate1(int native_shader, float cx, float cy,
+ int[] colors, float[] positions) {
+ // nothing to be done here.
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativePostCreate2(int native_shader, float cx, float cy,
+ int color0, int color1) {
+ // nothing to be done here.
+ return 0;
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ /**
+ * A subclass of Shader that draws a sweep gradient around a center point.
+ *
+ * @param cx The x-coordinate of the center
+ * @param cy The y-coordinate of the center
+ * @param colors The colors to be distributed between around the center.
+ * There must be at least 2 colors in the array.
+ * @param positions May be NULL. The relative position of
+ * each corresponding color in the colors array, beginning
+ * with 0 and ending with 1.0. If the values are not
+ * monotonic, the drawing may produce unexpected results.
+ * If positions is NULL, then the colors are automatically
+ * spaced evenly.
+ */
+ private SweepGradient_Delegate(float cx, float cy,
+ int colors[], float positions[]) {
+ super(colors, positions);
+ mJavaPaint = new SweepGradientPaint(cx, cy, mColors, mPositions);
+ }
+
+ private class SweepGradientPaint extends GradientPaint {
+
+ private final float mCx;
+ private final float mCy;
+
+ public SweepGradientPaint(float cx, float cy, int[] colors,
+ float[] positions) {
+ super(colors, positions, null /*tileMode*/);
+ mCx = cx;
+ mCy = cy;
+ }
+
+ @Override
+ public java.awt.PaintContext createContext(
+ java.awt.image.ColorModel colorModel,
+ java.awt.Rectangle deviceBounds,
+ java.awt.geom.Rectangle2D userBounds,
+ java.awt.geom.AffineTransform xform,
+ java.awt.RenderingHints hints) {
+ precomputeGradientColors();
+
+ java.awt.geom.AffineTransform canvasMatrix;
+ try {
+ canvasMatrix = xform.createInverse();
+ } catch (java.awt.geom.NoninvertibleTransformException e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+ "Unable to inverse matrix in SweepGradient", e, null /*data*/);
+ canvasMatrix = new java.awt.geom.AffineTransform();
+ }
+
+ java.awt.geom.AffineTransform localMatrix = getLocalMatrix();
+ try {
+ localMatrix = localMatrix.createInverse();
+ } catch (java.awt.geom.NoninvertibleTransformException e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
+ "Unable to inverse matrix in SweepGradient", e, null /*data*/);
+ localMatrix = new java.awt.geom.AffineTransform();
+ }
+
+ return new SweepGradientPaintContext(canvasMatrix, localMatrix, colorModel);
+ }
+
+ private class SweepGradientPaintContext implements java.awt.PaintContext {
+
+ private final java.awt.geom.AffineTransform mCanvasMatrix;
+ private final java.awt.geom.AffineTransform mLocalMatrix;
+ private final java.awt.image.ColorModel mColorModel;
+
+ public SweepGradientPaintContext(
+ java.awt.geom.AffineTransform canvasMatrix,
+ java.awt.geom.AffineTransform localMatrix,
+ java.awt.image.ColorModel colorModel) {
+ mCanvasMatrix = canvasMatrix;
+ mLocalMatrix = localMatrix;
+ mColorModel = colorModel;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public java.awt.image.ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ @Override
+ public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+ java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h,
+ java.awt.image.BufferedImage.TYPE_INT_ARGB);
+
+ int[] data = new int[w*h];
+
+ // compute angle from each point to the center, and figure out the distance from
+ // it.
+ int index = 0;
+ float[] pt1 = new float[2];
+ float[] pt2 = new float[2];
+ for (int iy = 0 ; iy < h ; iy++) {
+ for (int ix = 0 ; ix < w ; ix++) {
+ // handle the canvas transform
+ pt1[0] = x + ix;
+ pt1[1] = y + iy;
+ mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
+
+ // handle the local matrix
+ pt1[0] = pt2[0] - mCx;
+ pt1[1] = pt2[1] - mCy;
+ mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
+
+ float dx = pt2[0];
+ float dy = pt2[1];
+
+ float angle;
+ if (dx == 0) {
+ angle = (float) (dy < 0 ? 3 * Math.PI / 2 : Math.PI / 2);
+ } else if (dy == 0) {
+ angle = (float) (dx < 0 ? Math.PI : 0);
+ } else {
+ angle = (float) Math.atan(dy / dx);
+ if (dx > 0) {
+ if (dy < 0) {
+ angle += Math.PI * 2;
+ }
+ } else {
+ angle += Math.PI;
+ }
+ }
+
+ // convert to 0-1. value and get color
+ data[index++] = getGradientColor((float) (angle / (2 * Math.PI)));
+ }
+ }
+
+ image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
+
+ return image.getRaster();
+ }
+
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
new file mode 100644
index 0000000..adad2ac
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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 android.graphics;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class Typeface_Accessor {
+
+ public static void resetDefaults() {
+ Typeface.sDefaults = null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
new file mode 100644
index 0000000..8701cc8
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.FontLoader;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.res.AssetManager;
+
+import java.awt.Font;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Typeface
+ *
+ * Through the layoutlib_create tool, the original native methods of Typeface have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Typeface class.
+ *
+ * @see DelegateManager
+ *
+ */
+public final class Typeface_Delegate {
+
+ private static final String SYSTEM_FONTS = "/system/fonts/";
+
+ // ---- delegate manager ----
+ private static final DelegateManager<Typeface_Delegate> sManager =
+ new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class);
+
+ // ---- delegate helper data ----
+ private static final String DEFAULT_FAMILY = "sans-serif";
+
+ private static FontLoader sFontLoader;
+ private static final List<Typeface_Delegate> sPostInitDelegate =
+ new ArrayList<Typeface_Delegate>();
+
+ // ---- delegate data ----
+
+ private final String mFamily;
+ private int mStyle;
+ private List<Font> mFonts;
+
+
+ // ---- Public Helper methods ----
+
+ public static synchronized void init(FontLoader fontLoader) {
+ sFontLoader = fontLoader;
+
+ for (Typeface_Delegate delegate : sPostInitDelegate) {
+ delegate.init();
+ }
+ sPostInitDelegate.clear();
+ }
+
+ public static Typeface_Delegate getDelegate(int nativeTypeface) {
+ return sManager.getDelegate(nativeTypeface);
+ }
+
+ public static List<Font> getFonts(Typeface typeface) {
+ return getFonts(typeface.native_instance);
+ }
+
+ public static List<Font> getFonts(int native_int) {
+ Typeface_Delegate delegate = sManager.getDelegate(native_int);
+ if (delegate == null) {
+ return null;
+ }
+
+ return delegate.getFonts();
+ }
+
+ public List<Font> getFonts() {
+ return mFonts;
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static synchronized int nativeCreate(String familyName, int style) {
+ if (familyName == null) {
+ familyName = DEFAULT_FAMILY;
+ }
+
+ Typeface_Delegate newDelegate = new Typeface_Delegate(familyName, style);
+ if (sFontLoader != null) {
+ newDelegate.init();
+ } else {
+ // font loader has not been initialized yet, add the delegate to a list of delegates
+ // to init when the font loader is initialized.
+ // There won't be any rendering before this happens anyway.
+ sPostInitDelegate.add(newDelegate);
+ }
+
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static synchronized int nativeCreateFromTypeface(int native_instance, int style) {
+ Typeface_Delegate delegate = sManager.getDelegate(native_instance);
+ if (delegate == null) {
+ return 0;
+ }
+
+ Typeface_Delegate newDelegate = new Typeface_Delegate(delegate.mFamily, style);
+ if (sFontLoader != null) {
+ newDelegate.init();
+ } else {
+ // font loader has not been initialized yet, add the delegate to a list of delegates
+ // to init when the font loader is initialized.
+ // There won't be any rendering before this happens anyway.
+ sPostInitDelegate.add(newDelegate);
+ }
+
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static synchronized int nativeCreateFromAsset(AssetManager mgr, String path) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Typeface.createFromAsset() is not supported.", null /*throwable*/, null /*data*/);
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static synchronized int nativeCreateFromFile(String path) {
+ if (path.startsWith(SYSTEM_FONTS) ) {
+ String relativePath = path.substring(SYSTEM_FONTS.length());
+ File f = new File(sFontLoader.getOsFontsLocation(), relativePath);
+
+ try {
+ Font font = Font.createFont(Font.TRUETYPE_FONT, f);
+ if (font != null) {
+ Typeface_Delegate newDelegate = new Typeface_Delegate(font);
+ return sManager.addNewDelegate(newDelegate);
+ }
+ } catch (Exception e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
+ String.format("Unable to load font %1$s", relativePath),
+ null /*throwable*/, null /*data*/);
+ }
+ } else {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Typeface.createFromFile() can only work with platform fonts located in " +
+ SYSTEM_FONTS,
+ null /*throwable*/, null /*data*/);
+ }
+
+
+ // return a copy of the base font
+ return nativeCreate(null, 0);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeUnref(int native_instance) {
+ sManager.removeJavaReferenceFor(native_instance);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeGetStyle(int native_instance) {
+ Typeface_Delegate delegate = sManager.getDelegate(native_instance);
+ if (delegate == null) {
+ return 0;
+ }
+
+ return delegate.mStyle;
+ }
+
+ // ---- Private delegate/helper methods ----
+
+ private Typeface_Delegate(String family, int style) {
+ mFamily = family;
+ mStyle = style;
+ }
+
+ private Typeface_Delegate(Font font) {
+ mFamily = font.getFamily();
+ mStyle = Typeface.NORMAL;
+
+ mFonts = sFontLoader.getFallbackFonts(mStyle);
+
+ // insert the font glyph first.
+ mFonts.add(0, font);
+ }
+
+ private void init() {
+ mFonts = sFontLoader.getFont(mFamily, mStyle);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java
new file mode 100644
index 0000000..962d69c
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 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 android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.awt.Composite;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Xfermode
+ *
+ * Through the layoutlib_create tool, the original native methods of Xfermode have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original Xfermode class.
+ *
+ * This also serve as a base class for all Xfermode delegate classes.
+ *
+ * @see DelegateManager
+ *
+ */
+public abstract class Xfermode_Delegate {
+
+ // ---- delegate manager ----
+ protected static final DelegateManager<Xfermode_Delegate> sManager =
+ new DelegateManager<Xfermode_Delegate>(Xfermode_Delegate.class);
+
+ // ---- delegate helper data ----
+
+ // ---- delegate data ----
+
+ // ---- Public Helper methods ----
+
+ public static Xfermode_Delegate getDelegate(int native_instance) {
+ return sManager.getDelegate(native_instance);
+ }
+
+ public abstract Composite getComposite(int alpha);
+ public abstract boolean isSupported();
+ public abstract String getSupportMessage();
+
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static void finalizer(int native_instance) {
+ sManager.removeJavaReferenceFor(native_instance);
+ }
+
+ // ---- Private delegate/helper methods ----
+
+}
diff --git a/tools/layoutlib/bridge/src/android/os/Build_Delegate.java b/tools/layoutlib/bridge/src/android/os/Build_Delegate.java
new file mode 100644
index 0000000..ff82a5e
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/Build_Delegate.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2011 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 android.os;
+
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.util.Map;
+
+/**
+ * Delegate implementing the native methods of android.os.Build
+ *
+ * Through the layoutlib_create tool, the original native methods of Build have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ */
+public class Build_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static String getString(String property) {
+ Map<String, String> properties = Bridge.getPlatformProperties();
+ String value = properties.get(property);
+ if (value != null) {
+ return value;
+ }
+
+ return Build.UNKNOWN;
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java b/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java
new file mode 100644
index 0000000..afbe97c0
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 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 android.os;
+
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Delegate overriding selected methods of android.os.HandlerThread
+ *
+ * Through the layoutlib_create tool, selected methods of Handler have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class HandlerThread_Delegate {
+
+ private static Map<BridgeContext, List<HandlerThread>> sThreads =
+ new HashMap<BridgeContext, List<HandlerThread>>();
+
+ public static void cleanUp(BridgeContext context) {
+ List<HandlerThread> list = sThreads.get(context);
+ if (list != null) {
+ for (HandlerThread thread : list) {
+ thread.quit();
+ }
+
+ list.clear();
+ sThreads.remove(context);
+ }
+ }
+
+ // -------- Delegate methods
+
+ @LayoutlibDelegate
+ /*package*/ static void run(HandlerThread theThread) {
+ // record the thread so that it can be quit() on clean up.
+ BridgeContext context = RenderAction.getCurrentContext();
+ List<HandlerThread> list = sThreads.get(context);
+ if (list == null) {
+ list = new ArrayList<HandlerThread>();
+ sThreads.put(context, list);
+ }
+
+ list.add(theThread);
+
+ // ---- START DEFAULT IMPLEMENTATION.
+
+ theThread.mTid = Process.myTid();
+ Looper.prepare();
+ synchronized (theThread) {
+ theThread.mLooper = Looper.myLooper();
+ theThread.notifyAll();
+ }
+ Process.setThreadPriority(theThread.mPriority);
+ theThread.onLooperPrepared();
+ Looper.loop();
+ theThread.mTid = -1;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java
new file mode 100644
index 0000000..2152c8a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 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 android.os;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+
+/**
+ * Delegate overriding selected methods of android.os.Handler
+ *
+ * Through the layoutlib_create tool, selected methods of Handler have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class Handler_Delegate {
+
+ // -------- Delegate methods
+
+ @LayoutlibDelegate
+ /*package*/ static boolean sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
+ // get the callback
+ IHandlerCallback callback = sCallbacks.get();
+ if (callback != null) {
+ callback.sendMessageAtTime(handler, msg, uptimeMillis);
+ }
+ return true;
+ }
+
+ // -------- Delegate implementation
+
+ public interface IHandlerCallback {
+ void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis);
+ }
+
+ private final static ThreadLocal<IHandlerCallback> sCallbacks =
+ new ThreadLocal<IHandlerCallback>();
+
+ public static void setCallback(IHandlerCallback callback) {
+ sCallbacks.set(callback);
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/os/Looper_Accessor.java b/tools/layoutlib/bridge/src/android/os/Looper_Accessor.java
new file mode 100644
index 0000000..09f3e47
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/Looper_Accessor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 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 android.os;
+
+import java.lang.reflect.Field;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class Looper_Accessor {
+
+ public static void cleanupThread() {
+ // clean up the looper
+ Looper.sThreadLocal.remove();
+ try {
+ Field sMainLooper = Looper.class.getDeclaredField("sMainLooper");
+ sMainLooper.setAccessible(true);
+ sMainLooper.set(null, null);
+ } catch (SecurityException e) {
+ catchReflectionException();
+ } catch (IllegalArgumentException e) {
+ catchReflectionException();
+ } catch (NoSuchFieldException e) {
+ catchReflectionException();
+ } catch (IllegalAccessException e) {
+ catchReflectionException();
+ }
+
+ }
+
+ private static void catchReflectionException() {
+ assert(false);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/os/ServiceManager.java b/tools/layoutlib/bridge/src/android/os/ServiceManager.java
new file mode 100644
index 0000000..6a68ee2
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/ServiceManager.java
@@ -0,0 +1,72 @@
+/*
+ * 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 android.os;
+
+import java.util.Map;
+
+public final class ServiceManager {
+
+ /**
+ * Returns a reference to a service with the given name.
+ *
+ * @param name the name of the service to get
+ * @return a reference to the service, or <code>null</code> if the service doesn't exist
+ */
+ public static IBinder getService(String name) {
+ return null;
+ }
+
+ /**
+ * Place a new @a service called @a name into the service
+ * manager.
+ *
+ * @param name the name of the new service
+ * @param service the service object
+ */
+ public static void addService(String name, IBinder service) {
+ // pass
+ }
+
+ /**
+ * Retrieve an existing service called @a name from the
+ * service manager. Non-blocking.
+ */
+ public static IBinder checkService(String name) {
+ return null;
+ }
+
+ /**
+ * Return a list of all currently running services.
+ */
+ public static String[] listServices() throws RemoteException {
+ // actual implementation returns null sometimes, so it's ok
+ // to return null instead of an empty list.
+ return null;
+ }
+
+ /**
+ * This is only intended to be called when the process is first being brought
+ * up and bound by the activity manager. There is only one thread in the process
+ * at that time, so no locking is done.
+ *
+ * @param cache the cache of service references
+ * @hide
+ */
+ public static void initServiceCache(Map<String, IBinder> cache) {
+ // pass
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
new file mode 100644
index 0000000..fd594f7
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2010 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 android.os;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.os.SystemClock
+ *
+ * Through the layoutlib_create tool, the original native methods of SystemClock have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ */
+public class SystemClock_Delegate {
+ private static long sBootTime = System.currentTimeMillis();
+ private static long sBootTimeNano = System.nanoTime();
+
+ @LayoutlibDelegate
+ /*package*/ static boolean setCurrentTimeMillis(long millis) {
+ return true;
+ }
+
+ /**
+ * Returns milliseconds since boot, not counting time spent in deep sleep.
+ * <b>Note:</b> This value may get reset occasionally (before it would
+ * otherwise wrap around).
+ *
+ * @return milliseconds of non-sleep uptime since boot.
+ */
+ @LayoutlibDelegate
+ /*package*/ static long uptimeMillis() {
+ return System.currentTimeMillis() - sBootTime;
+ }
+
+ /**
+ * Returns milliseconds since boot, including time spent in sleep.
+ *
+ * @return elapsed milliseconds since boot.
+ */
+ @LayoutlibDelegate
+ /*package*/ static long elapsedRealtime() {
+ return System.currentTimeMillis() - sBootTime;
+ }
+
+ /**
+ * Returns nanoseconds since boot, including time spent in sleep.
+ *
+ * @return elapsed nanoseconds since boot.
+ */
+ @LayoutlibDelegate
+ /*package*/ static long elapsedRealtimeNanos() {
+ return System.nanoTime() - sBootTimeNano;
+ }
+
+ /**
+ * Returns milliseconds running in the current thread.
+ *
+ * @return elapsed milliseconds in the thread
+ */
+ @LayoutlibDelegate
+ /*package*/ static long currentThreadTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+ /**
+ * Returns microseconds running in the current thread.
+ *
+ * @return elapsed microseconds in the thread
+ *
+ * @hide
+ */
+ @LayoutlibDelegate
+ /*package*/ static long currentThreadTimeMicro() {
+ return System.currentTimeMillis() * 1000;
+ }
+
+ /**
+ * Returns current wall time in microseconds.
+ *
+ * @return elapsed microseconds in wall time
+ *
+ * @hide
+ */
+ @LayoutlibDelegate
+ /*package*/ static long currentTimeMicro() {
+ return elapsedRealtime() * 1000;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
new file mode 100644
index 0000000..973fa0e
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2011 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 android.text;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.ibm.icu.text.Bidi;
+
+
+/**
+ * Delegate used to provide new implementation for the native methods of {@link AndroidBidi}
+ *
+ * Through the layoutlib_create tool, the original methods of AndroidBidi have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class AndroidBidi_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static int runBidi(int dir, char[] chars, byte[] charInfo, int count,
+ boolean haveInfo) {
+
+ switch (dir) {
+ case 0: // Layout.DIR_REQUEST_LTR
+ case 1: // Layout.DIR_REQUEST_RTL
+ break; // No change.
+ case -1:
+ dir = Bidi.LEVEL_DEFAULT_LTR;
+ break;
+ case -2:
+ dir = Bidi.LEVEL_DEFAULT_RTL;
+ break;
+ default:
+ // Invalid code. Log error, assume LEVEL_DEFAULT_LTR and continue.
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Invalid direction flag", null);
+ dir = Bidi.LEVEL_DEFAULT_LTR;
+ }
+ Bidi bidi = new Bidi(chars, 0, null, 0, count, dir);
+ if (charInfo != null) {
+ for (int i = 0; i < count; ++i)
+ charInfo[i] = bidi.getLevelAt(i);
+ }
+ return bidi.getParaLevel();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java
new file mode 100644
index 0000000..8cd1a69
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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 android.text.format;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+
+
+/**
+ * Delegate used to provide new implementation for the native methods of {@link DateFormat}
+ *
+ * Through the layoutlib_create tool, the original methods of DateFormat have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class DateFormat_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static boolean is24HourFormat(Context context) {
+ return false;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java
new file mode 100644
index 0000000..15cd687
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 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 android.text.format;
+
+import java.util.Calendar;
+import java.util.UnknownFormatConversionException;
+import java.util.regex.Pattern;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation for native methods of {@link Time}
+ *
+ * Through the layoutlib_create tool, some native methods of Time have been replaced by calls to
+ * methods of the same name in this delegate class.
+ */
+public class Time_Delegate {
+
+ // Regex to match odd number of '%'.
+ private static final Pattern p = Pattern.compile("(?<!%)(%%)*%(?!%)");
+
+ @LayoutlibDelegate
+ /*package*/ static String format1(Time thisTime, String format) {
+
+ try {
+ // Change the format by adding changing '%' to "%1$t". This is required to tell the
+ // formatter which argument to use from the argument list. '%%' is left as is. In the
+ // replacement string, $0 refers to matched pattern. \\1 means '1', written this way to
+ // separate it from 0. \\$ means '$', written this way to suppress the special meaning
+ // of $.
+ return String.format(
+ p.matcher(format).replaceAll("$0\\1\\$t"),
+ timeToCalendar(thisTime, Calendar.getInstance()));
+ } catch (UnknownFormatConversionException e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_STRFTIME, "Unrecognized format", e, format);
+ return format;
+ }
+ }
+
+ private static Calendar timeToCalendar(Time time, Calendar calendar) {
+ calendar.set(time.year, time.month, time.monthDay, time.hour, time.minute, time.second);
+ return calendar;
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java
new file mode 100644
index 0000000..6ac5b02
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java
@@ -0,0 +1,283 @@
+/*
+ * 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.
+ */
+
+package android.util;
+
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.internal.util.XmlUtils;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.resources.ResourceType;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A correct implementation of the {@link AttributeSet} interface on top of a XmlPullParser
+ */
+public class BridgeXmlPullAttributes extends XmlPullAttributes {
+
+ private final BridgeContext mContext;
+ private final boolean mPlatformFile;
+
+ public BridgeXmlPullAttributes(XmlPullParser parser, BridgeContext context,
+ boolean platformFile) {
+ super(parser);
+ mContext = context;
+ mPlatformFile = platformFile;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see android.util.XmlPullAttributes#getAttributeNameResource(int)
+ *
+ * This methods must return com.android.internal.R.attr.<name> matching
+ * the name of the attribute.
+ * It returns 0 if it doesn't find anything.
+ */
+ @Override
+ public int getAttributeNameResource(int index) {
+ // get the attribute name.
+ String name = getAttributeName(index);
+
+ // get the attribute namespace
+ String ns = mParser.getAttributeNamespace(index);
+
+ if (BridgeConstants.NS_RESOURCES.equals(ns)) {
+ Integer v = Bridge.getResourceId(ResourceType.ATTR, name);
+ if (v != null) {
+ return v.intValue();
+ }
+
+ return 0;
+ }
+
+ // this is not an attribute in the android namespace, we query the customviewloader, if
+ // the namespaces match.
+ if (mContext.getProjectCallback().getNamespace().equals(ns)) {
+ Integer v = mContext.getProjectCallback().getResourceId(ResourceType.ATTR, name);
+ if (v != null) {
+ return v.intValue();
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public int getAttributeListValue(String namespace, String attribute,
+ String[] options, int defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToList(value, options, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(String namespace, String attribute,
+ boolean defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToBoolean(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+
+ return resolveResourceValue(value, defaultValue);
+ }
+
+ @Override
+ public int getAttributeIntValue(String namespace, String attribute,
+ int defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(String namespace, String attribute,
+ int defaultValue) {
+ String value = getAttributeValue(namespace, attribute);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public float getAttributeFloatValue(String namespace, String attribute,
+ float defaultValue) {
+ String s = getAttributeValue(namespace, attribute);
+ if (s != null) {
+ ResourceValue r = getResourceValue(s);
+
+ if (r != null) {
+ s = r.getValue();
+ }
+
+ return Float.parseFloat(s);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeListValue(int index,
+ String[] options, int defaultValue) {
+ return XmlUtils.convertValueToList(
+ getAttributeValue(index), options, defaultValue);
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+ String value = getAttributeValue(index);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToBoolean(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeResourceValue(int index, int defaultValue) {
+ String value = getAttributeValue(index);
+
+ return resolveResourceValue(value, defaultValue);
+ }
+
+ @Override
+ public int getAttributeIntValue(int index, int defaultValue) {
+ String value = getAttributeValue(index);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+ String value = getAttributeValue(index);
+ if (value != null) {
+ ResourceValue r = getResourceValue(value);
+
+ if (r != null) {
+ value = r.getValue();
+ }
+
+ return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public float getAttributeFloatValue(int index, float defaultValue) {
+ String s = getAttributeValue(index);
+ if (s != null) {
+ ResourceValue r = getResourceValue(s);
+
+ if (r != null) {
+ s = r.getValue();
+ }
+
+ return Float.parseFloat(s);
+ }
+
+ return defaultValue;
+ }
+
+ // -- private helper methods
+
+ /**
+ * Returns a resolved {@link ResourceValue} from a given value.
+ */
+ private ResourceValue getResourceValue(String value) {
+ // now look for this particular value
+ RenderResources resources = mContext.getRenderResources();
+ return resources.resolveResValue(resources.findResValue(value, mPlatformFile));
+ }
+
+ /**
+ * Resolves and return a value to its associated integer.
+ */
+ private int resolveResourceValue(String value, int defaultValue) {
+ ResourceValue resource = getResourceValue(value);
+ if (resource != null) {
+ Integer id = null;
+ if (mPlatformFile || resource.isFramework()) {
+ id = Bridge.getResourceId(resource.getResourceType(), resource.getName());
+ } else {
+ id = mContext.getProjectCallback().getResourceId(
+ resource.getResourceType(), resource.getName());
+ }
+
+ if (id != null) {
+ return id;
+ }
+ }
+
+ return defaultValue;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java b/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java
new file mode 100644
index 0000000..8b4c60b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/util/FloatMath_Delegate.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2007 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 android.util;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.util.FloatMath
+ *
+ * Through the layoutlib_create tool, the original native methods of FloatMath have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ */
+/*package*/ final class FloatMath_Delegate {
+
+ /** Prevents instantiation. */
+ private FloatMath_Delegate() {}
+
+ /**
+ * Returns the float conversion of the most positive (i.e. closest to
+ * positive infinity) integer value which is less than the argument.
+ *
+ * @param value to be converted
+ * @return the floor of value
+ */
+ @LayoutlibDelegate
+ /*package*/ static float floor(float value) {
+ return (float)Math.floor(value);
+ }
+
+ /**
+ * Returns the float conversion of the most negative (i.e. closest to
+ * negative infinity) integer value which is greater than the argument.
+ *
+ * @param value to be converted
+ * @return the ceiling of value
+ */
+ @LayoutlibDelegate
+ /*package*/ static float ceil(float value) {
+ return (float)Math.ceil(value);
+ }
+
+ /**
+ * Returns the closest float approximation of the sine of the argument.
+ *
+ * @param angle to compute the cosine of, in radians
+ * @return the sine of angle
+ */
+ @LayoutlibDelegate
+ /*package*/ static float sin(float angle) {
+ return (float)Math.sin(angle);
+ }
+
+ /**
+ * Returns the closest float approximation of the cosine of the argument.
+ *
+ * @param angle to compute the cosine of, in radians
+ * @return the cosine of angle
+ */
+ @LayoutlibDelegate
+ /*package*/ static float cos(float angle) {
+ return (float)Math.cos(angle);
+ }
+
+ /**
+ * Returns the closest float approximation of the square root of the
+ * argument.
+ *
+ * @param value to compute sqrt of
+ * @return the square root of value
+ */
+ @LayoutlibDelegate
+ /*package*/ static float sqrt(float value) {
+ return (float)Math.sqrt(value);
+ }
+
+ /**
+ * Returns the closest float approximation of the raising "e" to the power
+ * of the argument.
+ *
+ * @param value to compute the exponential of
+ * @return the exponential of value
+ */
+ @LayoutlibDelegate
+ /*package*/ static float exp(float value) {
+ return (float)Math.exp(value);
+ }
+
+ /**
+ * Returns the closest float approximation of the result of raising {@code
+ * x} to the power of {@code y}.
+ *
+ * @param x the base of the operation.
+ * @param y the exponent of the operation.
+ * @return {@code x} to the power of {@code y}.
+ */
+ @LayoutlibDelegate
+ /*package*/ static float pow(float x, float y) {
+ return (float)Math.pow(x, y);
+ }
+
+ /**
+ * Returns {@code sqrt(}<i>{@code x}</i><sup>{@code 2}</sup>{@code +} <i>
+ * {@code y}</i><sup>{@code 2}</sup>{@code )}.
+ *
+ * @param x a float number
+ * @param y a float number
+ * @return the hypotenuse
+ */
+ @LayoutlibDelegate
+ /*package*/ static float hypot(float x, float y) {
+ return (float)Math.sqrt(x*x + y*y);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/util/Log_Delegate.java b/tools/layoutlib/bridge/src/android/util/Log_Delegate.java
new file mode 100644
index 0000000..7f432ab
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/util/Log_Delegate.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011 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 android.util;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+class Log_Delegate {
+ // to replicate prefix visible when using 'adb logcat'
+ private static char priorityChar(int priority) {
+ switch (priority) {
+ case Log.VERBOSE:
+ return 'V';
+ case Log.DEBUG:
+ return 'D';
+ case Log.INFO:
+ return 'I';
+ case Log.WARN:
+ return 'W';
+ case Log.ERROR:
+ return 'E';
+ case Log.ASSERT:
+ return 'A';
+ default:
+ return '?';
+ }
+ }
+
+ @LayoutlibDelegate
+ static int println_native(int bufID, int priority, String tag, String msgs) {
+ String prefix = priorityChar(priority) + "/" + tag + ": ";
+ for (String msg: msgs.split("\n")) {
+ System.out.println(prefix + msg);
+ }
+ return 0;
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/util/LruCache.java b/tools/layoutlib/bridge/src/android/util/LruCache.java
new file mode 100644
index 0000000..5208606
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/util/LruCache.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2011 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 android.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * BEGIN LAYOUTLIB CHANGE
+ * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
+ * END LAYOUTLIB CHANGE
+ *
+ * A cache that holds strong references to a limited number of values. Each time
+ * a value is accessed, it is moved to the head of a queue. When a value is
+ * added to a full cache, the value at the end of that queue is evicted and may
+ * become eligible for garbage collection.
+ *
+ * <p>If your cached values hold resources that need to be explicitly released,
+ * override {@link #entryRemoved}.
+ *
+ * <p>If a cache miss should be computed on demand for the corresponding keys,
+ * override {@link #create}. This simplifies the calling code, allowing it to
+ * assume a value will always be returned, even when there's a cache miss.
+ *
+ * <p>By default, the cache size is measured in the number of entries. Override
+ * {@link #sizeOf} to size the cache in different units. For example, this cache
+ * is limited to 4MiB of bitmaps:
+ * <pre> {@code
+ * int cacheSize = 4 * 1024 * 1024; // 4MiB
+ * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
+ * protected int sizeOf(String key, Bitmap value) {
+ * return value.getByteCount();
+ * }
+ * }}</pre>
+ *
+ * <p>This class is thread-safe. Perform multiple cache operations atomically by
+ * synchronizing on the cache: <pre> {@code
+ * synchronized (cache) {
+ * if (cache.get(key) == null) {
+ * cache.put(key, value);
+ * }
+ * }}</pre>
+ *
+ * <p>This class does not allow null to be used as a key or value. A return
+ * value of null from {@link #get}, {@link #put} or {@link #remove} is
+ * unambiguous: the key was not in the cache.
+ *
+ * <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
+ * of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
+ * Support Package</a> for earlier releases.
+ */
+public class LruCache<K, V> {
+ private final LinkedHashMap<K, V> map;
+
+ /** Size of this cache in units. Not necessarily the number of elements. */
+ private int size;
+ private int maxSize;
+
+ private int putCount;
+ private int createCount;
+ private int evictionCount;
+ private int hitCount;
+ private int missCount;
+
+ /**
+ * @param maxSize for caches that do not override {@link #sizeOf}, this is
+ * the maximum number of entries in the cache. For all other caches,
+ * this is the maximum sum of the sizes of the entries in this cache.
+ */
+ public LruCache(int maxSize) {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+ this.maxSize = maxSize;
+ this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
+ }
+
+ /**
+ * Sets the size of the cache.
+ * @param maxSize The new maximum size.
+ *
+ * @hide
+ */
+ public void resize(int maxSize) {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+
+ synchronized (this) {
+ this.maxSize = maxSize;
+ }
+ trimToSize(maxSize);
+ }
+
+ /**
+ * Returns the value for {@code key} if it exists in the cache or can be
+ * created by {@code #create}. If a value was returned, it is moved to the
+ * head of the queue. This returns null if a value is not cached and cannot
+ * be created.
+ */
+ public final V get(K key) {
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+
+ V mapValue;
+ synchronized (this) {
+ mapValue = map.get(key);
+ if (mapValue != null) {
+ hitCount++;
+ return mapValue;
+ }
+ missCount++;
+ }
+
+ /*
+ * Attempt to create a value. This may take a long time, and the map
+ * may be different when create() returns. If a conflicting value was
+ * added to the map while create() was working, we leave that value in
+ * the map and release the created value.
+ */
+
+ V createdValue = create(key);
+ if (createdValue == null) {
+ return null;
+ }
+
+ synchronized (this) {
+ createCount++;
+ mapValue = map.put(key, createdValue);
+
+ if (mapValue != null) {
+ // There was a conflict so undo that last put
+ map.put(key, mapValue);
+ } else {
+ size += safeSizeOf(key, createdValue);
+ }
+ }
+
+ if (mapValue != null) {
+ entryRemoved(false, key, createdValue, mapValue);
+ return mapValue;
+ } else {
+ trimToSize(maxSize);
+ return createdValue;
+ }
+ }
+
+ /**
+ * Caches {@code value} for {@code key}. The value is moved to the head of
+ * the queue.
+ *
+ * @return the previous value mapped by {@code key}.
+ */
+ public final V put(K key, V value) {
+ if (key == null || value == null) {
+ throw new NullPointerException("key == null || value == null");
+ }
+
+ V previous;
+ synchronized (this) {
+ putCount++;
+ size += safeSizeOf(key, value);
+ previous = map.put(key, value);
+ if (previous != null) {
+ size -= safeSizeOf(key, previous);
+ }
+ }
+
+ if (previous != null) {
+ entryRemoved(false, key, previous, value);
+ }
+
+ trimToSize(maxSize);
+ return previous;
+ }
+
+ /**
+ * @param maxSize the maximum size of the cache before returning. May be -1
+ * to evict even 0-sized elements.
+ */
+ private void trimToSize(int maxSize) {
+ while (true) {
+ K key;
+ V value;
+ synchronized (this) {
+ if (size < 0 || (map.isEmpty() && size != 0)) {
+ throw new IllegalStateException(getClass().getName()
+ + ".sizeOf() is reporting inconsistent results!");
+ }
+
+ if (size <= maxSize) {
+ break;
+ }
+
+ // BEGIN LAYOUTLIB CHANGE
+ // get the last item in the linked list.
+ // This is not efficient, the goal here is to minimize the changes
+ // compared to the platform version.
+ Map.Entry<K, V> toEvict = null;
+ for (Map.Entry<K, V> entry : map.entrySet()) {
+ toEvict = entry;
+ }
+ // END LAYOUTLIB CHANGE
+
+ if (toEvict == null) {
+ break;
+ }
+
+ key = toEvict.getKey();
+ value = toEvict.getValue();
+ map.remove(key);
+ size -= safeSizeOf(key, value);
+ evictionCount++;
+ }
+
+ entryRemoved(true, key, value, null);
+ }
+ }
+
+ /**
+ * Removes the entry for {@code key} if it exists.
+ *
+ * @return the previous value mapped by {@code key}.
+ */
+ public final V remove(K key) {
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+
+ V previous;
+ synchronized (this) {
+ previous = map.remove(key);
+ if (previous != null) {
+ size -= safeSizeOf(key, previous);
+ }
+ }
+
+ if (previous != null) {
+ entryRemoved(false, key, previous, null);
+ }
+
+ return previous;
+ }
+
+ /**
+ * Called for entries that have been evicted or removed. This method is
+ * invoked when a value is evicted to make space, removed by a call to
+ * {@link #remove}, or replaced by a call to {@link #put}. The default
+ * implementation does nothing.
+ *
+ * <p>The method is called without synchronization: other threads may
+ * access the cache while this method is executing.
+ *
+ * @param evicted true if the entry is being removed to make space, false
+ * if the removal was caused by a {@link #put} or {@link #remove}.
+ * @param newValue the new value for {@code key}, if it exists. If non-null,
+ * this removal was caused by a {@link #put}. Otherwise it was caused by
+ * an eviction or a {@link #remove}.
+ */
+ protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
+
+ /**
+ * Called after a cache miss to compute a value for the corresponding key.
+ * Returns the computed value or null if no value can be computed. The
+ * default implementation returns null.
+ *
+ * <p>The method is called without synchronization: other threads may
+ * access the cache while this method is executing.
+ *
+ * <p>If a value for {@code key} exists in the cache when this method
+ * returns, the created value will be released with {@link #entryRemoved}
+ * and discarded. This can occur when multiple threads request the same key
+ * at the same time (causing multiple values to be created), or when one
+ * thread calls {@link #put} while another is creating a value for the same
+ * key.
+ */
+ protected V create(K key) {
+ return null;
+ }
+
+ private int safeSizeOf(K key, V value) {
+ int result = sizeOf(key, value);
+ if (result < 0) {
+ throw new IllegalStateException("Negative size: " + key + "=" + value);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the size of the entry for {@code key} and {@code value} in
+ * user-defined units. The default implementation returns 1 so that size
+ * is the number of entries and max size is the maximum number of entries.
+ *
+ * <p>An entry's size must not change while it is in the cache.
+ */
+ protected int sizeOf(K key, V value) {
+ return 1;
+ }
+
+ /**
+ * Clear the cache, calling {@link #entryRemoved} on each removed entry.
+ */
+ public final void evictAll() {
+ trimToSize(-1); // -1 will evict 0-sized elements
+ }
+
+ /**
+ * For caches that do not override {@link #sizeOf}, this returns the number
+ * of entries in the cache. For all other caches, this returns the sum of
+ * the sizes of the entries in this cache.
+ */
+ public synchronized final int size() {
+ return size;
+ }
+
+ /**
+ * For caches that do not override {@link #sizeOf}, this returns the maximum
+ * number of entries in the cache. For all other caches, this returns the
+ * maximum sum of the sizes of the entries in this cache.
+ */
+ public synchronized final int maxSize() {
+ return maxSize;
+ }
+
+ /**
+ * Returns the number of times {@link #get} returned a value that was
+ * already present in the cache.
+ */
+ public synchronized final int hitCount() {
+ return hitCount;
+ }
+
+ /**
+ * Returns the number of times {@link #get} returned null or required a new
+ * value to be created.
+ */
+ public synchronized final int missCount() {
+ return missCount;
+ }
+
+ /**
+ * Returns the number of times {@link #create(Object)} returned a value.
+ */
+ public synchronized final int createCount() {
+ return createCount;
+ }
+
+ /**
+ * Returns the number of times {@link #put} was called.
+ */
+ public synchronized final int putCount() {
+ return putCount;
+ }
+
+ /**
+ * Returns the number of values that have been evicted.
+ */
+ public synchronized final int evictionCount() {
+ return evictionCount;
+ }
+
+ /**
+ * Returns a copy of the current contents of the cache, ordered from least
+ * recently accessed to most recently accessed.
+ */
+ public synchronized final Map<K, V> snapshot() {
+ return new LinkedHashMap<K, V>(map);
+ }
+
+ @Override public synchronized final String toString() {
+ int accesses = hitCount + missCount;
+ int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
+ return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
+ maxSize, hitCount, missCount, hitPercent);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
new file mode 100644
index 0000000..4901f72
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2011 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 android.view;
+
+import com.android.layoutlib.bridge.android.BridgeWindow;
+import com.android.layoutlib.bridge.android.BridgeWindowSession;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.View.AttachInfo;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class AttachInfo_Accessor {
+
+ public static void setAttachInfo(View view) {
+ Context context = view.getContext();
+ WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ ViewRootImpl root = new ViewRootImpl(context, display);
+ AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(),
+ display, root, new Handler(), null);
+ info.mHasWindowFocus = true;
+ info.mWindowVisibility = View.VISIBLE;
+ info.mInTouchMode = false; // this is so that we can display selections.
+ info.mHardwareAccelerated = false;
+ view.dispatchAttachedToWindow(info, 0);
+ }
+
+ public static void dispatchOnPreDraw(View view) {
+ view.mAttachInfo.mTreeObserver.dispatchOnPreDraw();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
new file mode 100644
index 0000000..cdbe200
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -0,0 +1,273 @@
+/*
+ * 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.
+ */
+
+package android.view;
+
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.MergeCookie;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.InflateException;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.io.File;
+
+/**
+ * Custom implementation of {@link LayoutInflater} to handle custom views.
+ */
+public final class BridgeInflater extends LayoutInflater {
+
+ private final IProjectCallback mProjectCallback;
+ private boolean mIsInMerge = false;
+ private ResourceReference mResourceReference;
+
+ /**
+ * List of class prefixes which are tried first by default.
+ * <p/>
+ * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater.
+ */
+ private static final String[] sClassPrefixList = {
+ "android.widget.",
+ "android.webkit."
+ };
+
+ protected BridgeInflater(LayoutInflater original, Context newContext) {
+ super(original, newContext);
+ mProjectCallback = null;
+ }
+
+ /**
+ * Instantiate a new BridgeInflater with an {@link IProjectCallback} object.
+ *
+ * @param context The Android application context.
+ * @param projectCallback the {@link IProjectCallback} object.
+ */
+ public BridgeInflater(Context context, IProjectCallback projectCallback) {
+ super(context);
+ mProjectCallback = projectCallback;
+ mConstructorArgs[0] = context;
+ }
+
+ @Override
+ public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+ View view = null;
+
+ try {
+ // First try to find a class using the default Android prefixes
+ for (String prefix : sClassPrefixList) {
+ try {
+ view = createView(name, prefix, attrs);
+ if (view != null) {
+ break;
+ }
+ } catch (ClassNotFoundException e) {
+ // Ignore. We'll try again using the base class below.
+ }
+ }
+
+ // Next try using the parent loader. This will most likely only work for
+ // fully-qualified class names.
+ try {
+ if (view == null) {
+ view = super.onCreateView(name, attrs);
+ }
+ } catch (ClassNotFoundException e) {
+ // Ignore. We'll try again using the custom view loader below.
+ }
+
+ // Finally try again using the custom view loader
+ try {
+ if (view == null) {
+ view = loadCustomView(name, attrs);
+ }
+ } catch (ClassNotFoundException e) {
+ // If the class was not found, we throw the exception directly, because this
+ // method is already expected to throw it.
+ throw e;
+ }
+ } catch (Exception e) {
+ // Wrap the real exception in a ClassNotFoundException, so that the calling method
+ // can deal with it.
+ ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e);
+ throw exception;
+ }
+
+ setupViewInContext(view, attrs);
+
+ return view;
+ }
+
+ @Override
+ public View createViewFromTag(View parent, String name, AttributeSet attrs,
+ boolean inheritContext) {
+ View view = null;
+ try {
+ view = super.createViewFromTag(parent, name, attrs, inheritContext);
+ } catch (InflateException e) {
+ // try to load the class from using the custom view loader
+ try {
+ view = loadCustomView(name, attrs);
+ } catch (Exception e2) {
+ // Wrap the real exception in an InflateException so that the calling
+ // method can deal with it.
+ InflateException exception = new InflateException();
+ if (e2.getClass().equals(ClassNotFoundException.class) == false) {
+ exception.initCause(e2);
+ } else {
+ exception.initCause(e);
+ }
+ throw exception;
+ }
+ }
+
+ setupViewInContext(view, attrs);
+
+ return view;
+ }
+
+ @Override
+ public View inflate(int resource, ViewGroup root) {
+ Context context = getContext();
+ if (context instanceof BridgeContext) {
+ BridgeContext bridgeContext = (BridgeContext)context;
+
+ ResourceValue value = null;
+
+ Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
+ if (layoutInfo != null) {
+ value = bridgeContext.getRenderResources().getFrameworkResource(
+ ResourceType.LAYOUT, layoutInfo.getSecond());
+ } else {
+ layoutInfo = mProjectCallback.resolveResourceId(resource);
+
+ if (layoutInfo != null) {
+ value = bridgeContext.getRenderResources().getProjectResource(
+ ResourceType.LAYOUT, layoutInfo.getSecond());
+ }
+ }
+
+ if (value != null) {
+ File f = new File(value.getValue());
+ if (f.isFile()) {
+ try {
+ XmlPullParser parser = ParserFactory.create(f);
+
+ BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
+ parser, bridgeContext, false);
+
+ return inflate(bridgeParser, root);
+ } catch (Exception e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed to parse file " + f.getAbsolutePath(), e, null /*data*/);
+
+ return null;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private View loadCustomView(String name, AttributeSet attrs) throws ClassNotFoundException,
+ Exception{
+ if (mProjectCallback != null) {
+ // first get the classname in case it's not the node name
+ if (name.equals("view")) {
+ name = attrs.getAttributeValue(null, "class");
+ }
+
+ mConstructorArgs[1] = attrs;
+
+ Object customView = mProjectCallback.loadView(name, mConstructorSignature,
+ mConstructorArgs);
+
+ if (customView instanceof View) {
+ return (View)customView;
+ }
+ }
+
+ return null;
+ }
+
+ private void setupViewInContext(View view, AttributeSet attrs) {
+ if (getContext() instanceof BridgeContext) {
+ BridgeContext bc = (BridgeContext) getContext();
+ if (attrs instanceof BridgeXmlBlockParser) {
+ BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs;
+
+ // get the view key
+ Object viewKey = parser.getViewCookie();
+
+ if (viewKey == null) {
+ int currentDepth = parser.getDepth();
+
+ // test whether we are in an included file or in a adapter binding view.
+ BridgeXmlBlockParser previousParser = bc.getPreviousParser();
+ if (previousParser != null) {
+ // looks like we inside an embedded layout.
+ // only apply the cookie of the calling node (<include>) if we are at the
+ // top level of the embedded layout. If there is a merge tag, then
+ // skip it and look for the 2nd level
+ int testDepth = mIsInMerge ? 2 : 1;
+ if (currentDepth == testDepth) {
+ viewKey = previousParser.getViewCookie();
+ // if we are in a merge, wrap the cookie in a MergeCookie.
+ if (viewKey != null && mIsInMerge) {
+ viewKey = new MergeCookie(viewKey);
+ }
+ }
+ } else if (mResourceReference != null && currentDepth == 1) {
+ // else if there's a resource reference, this means we are in an adapter
+ // binding case. Set the resource ref as the view cookie only for the top
+ // level view.
+ viewKey = mResourceReference;
+ }
+ }
+
+ if (viewKey != null) {
+ bc.addViewKey(view, viewKey);
+ }
+ }
+ }
+ }
+
+ public void setIsInMerge(boolean isInMerge) {
+ mIsInMerge = isInMerge;
+ }
+
+ public void setResourceReference(ResourceReference reference) {
+ mResourceReference = reference;
+ }
+
+ @Override
+ public LayoutInflater cloneInContext(Context newContext) {
+ return new BridgeInflater(this, newContext);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
new file mode 100644
index 0000000..f75ee50
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 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 android.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link Choreographer}
+ *
+ * Through the layoutlib_create tool, the original methods of Choreographer have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+public class Choreographer_Delegate {
+
+ @LayoutlibDelegate
+ public static float getRefreshRate() {
+ return 60.f;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/Display_Delegate.java b/tools/layoutlib/bridge/src/android/view/Display_Delegate.java
new file mode 100644
index 0000000..53dc821
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/Display_Delegate.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 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 android.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link Display}
+ *
+ * Through the layoutlib_create tool, the original methods of Display have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class Display_Delegate {
+
+ @LayoutlibDelegate
+ static void updateDisplayInfoLocked(Display theDisplay) {
+ // do nothing
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
new file mode 100644
index 0000000..dd2cbc1
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2011 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 android.view;
+
+import android.graphics.Point;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.IApplicationToken;
+import android.view.IInputFilter;
+import android.view.IOnKeyguardExitResult;
+import android.view.IRotationWatcher;
+import android.view.IWindowManager;
+import android.view.IWindowSession;
+
+import java.util.List;
+
+/**
+ * Basic implementation of {@link IWindowManager} so that {@link Display} (and
+ * {@link Display_Delegate}) can return a valid instance.
+ */
+public class IWindowManagerImpl implements IWindowManager {
+
+ private final Configuration mConfig;
+ private final DisplayMetrics mMetrics;
+ private final int mRotation;
+ private final boolean mHasNavigationBar;
+
+ public IWindowManagerImpl(Configuration config, DisplayMetrics metrics, int rotation,
+ boolean hasNavigationBar) {
+ mConfig = config;
+ mMetrics = metrics;
+ mRotation = rotation;
+ mHasNavigationBar = hasNavigationBar;
+ }
+
+ // custom API.
+
+ public DisplayMetrics getMetrics() {
+ return mMetrics;
+ }
+
+ // ---- implementation of IWindowManager that we care about ----
+
+ @Override
+ public int getRotation() throws RemoteException {
+ return mRotation;
+ }
+
+ @Override
+ public boolean hasNavigationBar() {
+ return mHasNavigationBar;
+ }
+
+ // ---- unused implementation of IWindowManager ----
+
+ @Override
+ public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4,
+ boolean arg5, boolean arg6, int arg7, int arg8)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void addWindowToken(IBinder arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void clearForcedDisplaySize(int displayId) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void clearForcedDisplayDensity(int displayId) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setOverscan(int displayId, int left, int top, int right, int bottom)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void closeSystemDialogs(String arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void startFreezingScreen(int exitAnim, int enterAnim) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void stopFreezingScreen() {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void disableKeyguard(IBinder arg0, String arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void executeAppTransition() throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void exitKeyguardSecurely(IOnKeyguardExitResult arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void freezeRotation(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public float getAnimationScale(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public float[] getAnimationScales() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getAppOrientation(IApplicationToken arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public int getPendingAppTransition() throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public boolean inKeyguardRestrictedInputMode() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean inputMethodClientHasFocus(IInputMethodClient arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardLocked() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean isKeyguardSecure() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean isViewServerRunning() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public IWindowSession openSession(IInputMethodClient arg0, IInputContext arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void overridePendingAppTransition(String arg0, int arg1, int arg2,
+ IRemoteCallback startedCallback) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
+ int startHeight) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY,
+ IRemoteCallback startedCallback, boolean scaleUp) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void pauseKeyDispatching(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void prepareAppTransition(int arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void reenableKeyguard(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void removeAppToken(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void removeWindowToken(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void resumeKeyDispatching(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Bitmap screenshotApplications(IBinder arg0, int displayId, int arg1,
+ int arg2, boolean arg3) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void setAnimationScale(int arg0, float arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAnimationScales(float[] arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAppGroupId(IBinder arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAppOrientation(IApplicationToken arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setAppStartingWindow(IBinder arg0, String arg1, int arg2, CompatibilityInfo arg3,
+ CharSequence arg4, int arg5, int arg6, int arg7, int arg8, IBinder arg9, boolean arg10)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setAppVisibility(IBinder arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAppWillBeHidden(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setEventDispatching(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setFocusedApp(IBinder arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void getInitialDisplaySize(int displayId, Point size) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void getBaseDisplaySize(int displayId, Point size) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setForcedDisplaySize(int displayId, int arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public int getInitialDisplayDensity(int displayId) {
+ return -1;
+ }
+
+ @Override
+ public int getBaseDisplayDensity(int displayId) {
+ return -1;
+ }
+
+ @Override
+ public void setForcedDisplayDensity(int displayId, int density) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setInTouchMode(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setNewConfiguration(Configuration arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void updateRotation(boolean arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setStrictModeVisualIndicatorPreference(String arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void showStrictModeViolation(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void startAppFreezingScreen(IBinder arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean startViewServer(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void statusBarVisibilityChanged(int arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void stopAppFreezingScreen(IBinder arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean stopViewServer() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void thawRotation() throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int watchRotation(IRotationWatcher arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public void removeRotationWatcher(IRotationWatcher arg0) throws RemoteException {
+ }
+
+ @Override
+ public boolean waitForWindowDrawn(IBinder token, IRemoteCallback callback) {
+ return false;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int getPreferredOptionsPanelGravity() throws RemoteException {
+ return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ }
+
+ @Override
+ public void dismissKeyguard() {
+ }
+
+ @Override
+ public void lockNow(Bundle options) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean isSafeModeEnabled() {
+ return false;
+ }
+
+ @Override
+ public IBinder getFocusedWindowToken() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void setInputFilter(IInputFilter filter) throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void getWindowFrame(IBinder token, Rect outFrame) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setMagnificationCallbacks(IMagnificationCallbacks callbacks) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void setMagnificationSpec(MagnificationSpec spec) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean isRotationFrozen() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void setTouchExplorationEnabled(boolean enabled) {
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
new file mode 100644
index 0000000..7a73fae
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2011 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 android.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import java.io.IOException;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link LayoutInflater}
+ *
+ * Through the layoutlib_create tool, the original methods of LayoutInflater have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class LayoutInflater_Delegate {
+
+ private static final String TAG_MERGE = "merge";
+
+ public static boolean sIsInInclude = false;
+
+ /**
+ * Recursive method used to descend down the xml hierarchy and instantiate
+ * views, instantiate their children, and then call onFinishInflate().
+ *
+ * This implementation just records the merge status before calling the default implementation.
+ */
+ @LayoutlibDelegate
+ /* package */ static void rInflate(LayoutInflater thisInflater, XmlPullParser parser,
+ View parent, final AttributeSet attrs, boolean finishInflate, boolean inheritContext)
+ throws XmlPullParserException, IOException {
+
+ if (finishInflate == false) {
+ // this is a merge rInflate!
+ if (thisInflater instanceof BridgeInflater) {
+ ((BridgeInflater) thisInflater).setIsInMerge(true);
+ }
+ }
+
+ // ---- START DEFAULT IMPLEMENTATION.
+
+ thisInflater.rInflate_Original(parser, parent, attrs, finishInflate, inheritContext);
+
+ // ---- END DEFAULT IMPLEMENTATION.
+
+ if (finishInflate == false) {
+ // this is a merge rInflate!
+ if (thisInflater instanceof BridgeInflater) {
+ ((BridgeInflater) thisInflater).setIsInMerge(false);
+ }
+ }
+ }
+
+ @LayoutlibDelegate
+ public static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser, View parent,
+ AttributeSet attrs, boolean inheritContext) throws XmlPullParserException, IOException {
+
+ int type;
+
+ if (parent instanceof ViewGroup) {
+ final int layout = attrs.getAttributeResourceValue(null, "layout", 0);
+ if (layout == 0) {
+ final String value = attrs.getAttributeValue(null, "layout");
+ if (value == null) {
+ throw new InflateException("You must specifiy a layout in the"
+ + " include tag: <include layout=\"@layout/layoutID\" />");
+ } else {
+ throw new InflateException("You must specifiy a valid layout "
+ + "reference. The layout ID " + value + " is not valid.");
+ }
+ } else {
+ final XmlResourceParser childParser =
+ thisInflater.getContext().getResources().getLayout(layout);
+
+ try {
+ final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
+
+ while ((type = childParser.next()) != XmlPullParser.START_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ // Empty.
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new InflateException(childParser.getPositionDescription() +
+ ": No start tag found!");
+ }
+
+ final String childName = childParser.getName();
+
+ if (TAG_MERGE.equals(childName)) {
+ // Inflate all children.
+ thisInflater.rInflate(childParser, parent, childAttrs, false,
+ inheritContext);
+ } else {
+ final View view = thisInflater.createViewFromTag(parent, childName,
+ childAttrs, inheritContext);
+ final ViewGroup group = (ViewGroup) parent;
+
+ // We try to load the layout params set in the <include /> tag. If
+ // they don't exist, we will rely on the layout params set in the
+ // included XML file.
+ // During a layoutparams generation, a runtime exception is thrown
+ // if either layout_width or layout_height is missing. We catch
+ // this exception and set localParams accordingly: true means we
+ // successfully loaded layout params from the <include /> tag,
+ // false means we need to rely on the included layout params.
+ ViewGroup.LayoutParams params = null;
+ try {
+ // ---- START CHANGES
+ sIsInInclude = true;
+ // ---- END CHANGES
+
+ params = group.generateLayoutParams(attrs);
+
+ } catch (RuntimeException e) {
+ // ---- START CHANGES
+ sIsInInclude = false;
+ // ---- END CHANGES
+
+ params = group.generateLayoutParams(childAttrs);
+ } finally {
+ // ---- START CHANGES
+ sIsInInclude = false;
+ // ---- END CHANGES
+
+ if (params != null) {
+ view.setLayoutParams(params);
+ }
+ }
+
+ // Inflate all children.
+ thisInflater.rInflate(childParser, view, childAttrs, true, true);
+
+ // Attempt to override the included layout's android:id with the
+ // one set on the <include /> tag itself.
+ TypedArray a = thisInflater.mContext.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.View, 0, 0);
+ int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
+ // While we're at it, let's try to override android:visibility.
+ int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
+ a.recycle();
+
+ if (id != View.NO_ID) {
+ view.setId(id);
+ }
+
+ switch (visibility) {
+ case 0:
+ view.setVisibility(View.VISIBLE);
+ break;
+ case 1:
+ view.setVisibility(View.INVISIBLE);
+ break;
+ case 2:
+ view.setVisibility(View.GONE);
+ break;
+ }
+
+ group.addView(view);
+ }
+ } finally {
+ childParser.close();
+ }
+ }
+ } else {
+ throw new InflateException("<include /> can only be used inside of a ViewGroup");
+ }
+
+ final int currentDepth = parser.getDepth();
+ while (((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
+ // Empty
+ }
+ }
+
+
+}
diff --git a/tools/layoutlib/bridge/src/android/view/SurfaceView.java b/tools/layoutlib/bridge/src/android/view/SurfaceView.java
new file mode 100644
index 0000000..6aa4b3b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/SurfaceView.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2006 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 android.view;
+
+import com.android.layoutlib.bridge.MockView;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+/**
+ * Mock version of the SurfaceView.
+ * Only non override public methods from the real SurfaceView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ *
+ * TODO: generate automatically.
+ *
+ */
+public class SurfaceView extends MockView {
+
+ public SurfaceView(Context context) {
+ this(context, null);
+ }
+
+ public SurfaceView(Context context, AttributeSet attrs) {
+ this(context, attrs , 0);
+ }
+
+ public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public SurfaceHolder getHolder() {
+ return mSurfaceHolder;
+ }
+
+ private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+
+ @Override
+ public boolean isCreating() {
+ return false;
+ }
+
+ @Override
+ public void addCallback(Callback callback) {
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ }
+
+ @Override
+ public void setFixedSize(int width, int height) {
+ }
+
+ @Override
+ public void setSizeFromLayout() {
+ }
+
+ @Override
+ public void setFormat(int format) {
+ }
+
+ @Override
+ public void setType(int type) {
+ }
+
+ @Override
+ public void setKeepScreenOn(boolean screenOn) {
+ }
+
+ @Override
+ public Canvas lockCanvas() {
+ return null;
+ }
+
+ @Override
+ public Canvas lockCanvas(Rect dirty) {
+ return null;
+ }
+
+ @Override
+ public void unlockCanvasAndPost(Canvas canvas) {
+ }
+
+ @Override
+ public Surface getSurface() {
+ return null;
+ }
+
+ @Override
+ public Rect getSurfaceFrame() {
+ return null;
+ }
+ };
+}
+
diff --git a/tools/layoutlib/bridge/src/android/view/ViewConfiguration_Accessor.java b/tools/layoutlib/bridge/src/android/view/ViewConfiguration_Accessor.java
new file mode 100644
index 0000000..c3533e0
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ViewConfiguration_Accessor.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 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 android.view;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class ViewConfiguration_Accessor {
+
+ public static void clearConfigurations() {
+ // clear the stored ViewConfiguration since the map is per density and not per context.
+ ViewConfiguration.sConfigurations.clear();
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java
new file mode 100644
index 0000000..14b84ef
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 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 android.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link ViewRootImpl}
+ *
+ * Through the layoutlib_create tool, the original methods of ViewRootImpl have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class ViewRootImpl_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static boolean isInTouchMode() {
+ return false; // this allows displaying selection.
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/View_Delegate.java b/tools/layoutlib/bridge/src/android/view/View_Delegate.java
new file mode 100644
index 0000000..8215f7c
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/View_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 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 android.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link View}
+ *
+ * Through the layoutlib_create tool, the original methods of View have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class View_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static boolean isInEditMode(View thisView) {
+ return true;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/WindowManagerGlobal_Delegate.java b/tools/layoutlib/bridge/src/android/view/WindowManagerGlobal_Delegate.java
new file mode 100644
index 0000000..2606e55
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/WindowManagerGlobal_Delegate.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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 android.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of
+ * {@link WindowManagerGlobal}
+ *
+ * Through the layoutlib_create tool, the original methods of WindowManagerGlobal have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+public class WindowManagerGlobal_Delegate {
+
+ private static IWindowManager sService;
+
+ @LayoutlibDelegate
+ public static IWindowManager getWindowManagerService() {
+ return sService;
+ }
+
+ // ---- internal implementation stuff ----
+
+ public static void setWindowManagerService(IWindowManager service) {
+ sService = service;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java
new file mode 100644
index 0000000..1fd7836
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java
@@ -0,0 +1,137 @@
+/*
+ * 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 android.view.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.view.IWindow;
+import android.view.View;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
+ * Such events are generated when something notable happens in the user interface,
+ * for example an {@link android.app.Activity} starts, the focus or selection of a
+ * {@link android.view.View} changes etc. Parties interested in handling accessibility
+ * events implement and register an accessibility service which extends
+ * {@link android.accessibilityservice.AccessibilityService}.
+ *
+ * @see AccessibilityEvent
+ * @see android.accessibilityservice.AccessibilityService
+ * @see android.content.Context#getSystemService
+ */
+public final class AccessibilityManager {
+ private static AccessibilityManager sInstance = new AccessibilityManager();
+
+ /**
+ * Listener for the accessibility state.
+ */
+ public interface AccessibilityStateChangeListener {
+
+ /**
+ * Called back on change in the accessibility state.
+ *
+ * @param enabled Whether accessibility is enabled.
+ */
+ public void onAccessibilityStateChanged(boolean enabled);
+ }
+
+ /**
+ * Get an AccessibilityManager instance (create one if necessary).
+ *
+ * @hide
+ */
+ public static AccessibilityManager getInstance(Context context) {
+ return sInstance;
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param context A {@link Context}.
+ */
+ private AccessibilityManager() {
+ }
+
+ /**
+ * Returns if the {@link AccessibilityManager} is enabled.
+ *
+ * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ return false;
+ }
+
+ /**
+ * Sends an {@link AccessibilityEvent}. If this {@link AccessibilityManager} is not
+ * enabled the call is a NOOP.
+ *
+ * @param event The {@link AccessibilityEvent}.
+ *
+ * @throws IllegalStateException if a client tries to send an {@link AccessibilityEvent}
+ * while accessibility is not enabled.
+ */
+ public void sendAccessibilityEvent(AccessibilityEvent event) {
+ }
+
+ /**
+ * Requests interruption of the accessibility feedback from all accessibility services.
+ */
+ public void interrupt() {
+ }
+
+ /**
+ * Returns the {@link ServiceInfo}s of the installed accessibility services.
+ *
+ * @return An unmodifiable list with {@link ServiceInfo}s.
+ */
+ public List<ServiceInfo> getAccessibilityServiceList() {
+ // normal implementation does this in some case, so let's do the same
+ // (unmodifiableList wrapped around null).
+ List<ServiceInfo> services = null;
+ return Collections.unmodifiableList(services);
+ }
+
+ public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
+ // normal implementation does this in some case, so let's do the same
+ // (unmodifiableList wrapped around null).
+ List<AccessibilityServiceInfo> services = null;
+ return Collections.unmodifiableList(services);
+ }
+
+ public boolean addAccessibilityStateChangeListener(
+ AccessibilityStateChangeListener listener) {
+ return true;
+ }
+
+ public boolean removeAccessibilityStateChangeListener(
+ AccessibilityStateChangeListener listener) {
+ return true;
+ }
+
+ public int addAccessibilityInteractionConnection(IWindow windowToken,
+ IAccessibilityInteractionConnection connection) {
+ return View.NO_ID;
+ }
+
+ public void removeAccessibilityInteractionConnection(IWindow windowToken) {
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
new file mode 100644
index 0000000..dc4f9c8
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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 android.view.inputmethod;
+
+/**
+ * Class allowing access to package-protected methods/fields.
+ */
+public class InputMethodManager_Accessor {
+
+ public static void resetInstance() {
+ InputMethodManager.sInstance = null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
new file mode 100644
index 0000000..7c98847
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2011 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 android.view.inputmethod;
+
+import com.android.layoutlib.bridge.android.BridgeIInputMethodManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.os.Looper;
+
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link InputMethodManager}
+ *
+ * Through the layoutlib_create tool, the original methods of InputMethodManager have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class InputMethodManager_Delegate {
+
+ // ---- Overridden methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static InputMethodManager getInstance() {
+ synchronized (InputMethodManager.class) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm == null) {
+ imm = new InputMethodManager(
+ new BridgeIInputMethodManager(), Looper.getMainLooper());
+ InputMethodManager.sInstance = imm;
+ }
+ return imm;
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/webkit/WebView.java b/tools/layoutlib/bridge/src/android/webkit/WebView.java
new file mode 100644
index 0000000..202f204
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/webkit/WebView.java
@@ -0,0 +1,238 @@
+/*
+ * 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.
+ */
+
+package android.webkit;
+
+import com.android.layoutlib.bridge.MockView;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Picture;
+import android.os.Bundle;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Mock version of the WebView.
+ * Only non override public methods from the real WebView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ *
+ * TODO: generate automatically.
+ *
+ */
+public class WebView extends MockView {
+
+ /**
+ * Construct a new WebView with a Context object.
+ * @param context A Context object used to access application assets.
+ */
+ public WebView(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Construct a new WebView with layout parameters.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
+ */
+ public WebView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.webViewStyle);
+ }
+
+ /**
+ * Construct a new WebView with layout parameters and a default style.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
+ * @param defStyle The default style resource ID.
+ */
+ public WebView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ // START FAKE PUBLIC METHODS
+
+ public void setHorizontalScrollbarOverlay(boolean overlay) {
+ }
+
+ public void setVerticalScrollbarOverlay(boolean overlay) {
+ }
+
+ public boolean overlayHorizontalScrollbar() {
+ return false;
+ }
+
+ public boolean overlayVerticalScrollbar() {
+ return false;
+ }
+
+ public void savePassword(String host, String username, String password) {
+ }
+
+ public void setHttpAuthUsernamePassword(String host, String realm,
+ String username, String password) {
+ }
+
+ public String[] getHttpAuthUsernamePassword(String host, String realm) {
+ return null;
+ }
+
+ public void destroy() {
+ }
+
+ public static void enablePlatformNotifications() {
+ }
+
+ public static void disablePlatformNotifications() {
+ }
+
+ public void loadUrl(String url) {
+ }
+
+ public void loadData(String data, String mimeType, String encoding) {
+ }
+
+ public void loadDataWithBaseURL(String baseUrl, String data,
+ String mimeType, String encoding, String failUrl) {
+ }
+
+ public void stopLoading() {
+ }
+
+ public void reload() {
+ }
+
+ public boolean canGoBack() {
+ return false;
+ }
+
+ public void goBack() {
+ }
+
+ public boolean canGoForward() {
+ return false;
+ }
+
+ public void goForward() {
+ }
+
+ public boolean canGoBackOrForward(int steps) {
+ return false;
+ }
+
+ public void goBackOrForward(int steps) {
+ }
+
+ public boolean pageUp(boolean top) {
+ return false;
+ }
+
+ public boolean pageDown(boolean bottom) {
+ return false;
+ }
+
+ public void clearView() {
+ }
+
+ public Picture capturePicture() {
+ return null;
+ }
+
+ public float getScale() {
+ return 0;
+ }
+
+ public void setInitialScale(int scaleInPercent) {
+ }
+
+ public void invokeZoomPicker() {
+ }
+
+ public void requestFocusNodeHref(Message hrefMsg) {
+ }
+
+ public void requestImageRef(Message msg) {
+ }
+
+ public String getUrl() {
+ return null;
+ }
+
+ public String getTitle() {
+ return null;
+ }
+
+ public Bitmap getFavicon() {
+ return null;
+ }
+
+ public int getProgress() {
+ return 0;
+ }
+
+ public int getContentHeight() {
+ return 0;
+ }
+
+ public void pauseTimers() {
+ }
+
+ public void resumeTimers() {
+ }
+
+ public void clearCache() {
+ }
+
+ public void clearFormData() {
+ }
+
+ public void clearHistory() {
+ }
+
+ public void clearSslPreferences() {
+ }
+
+ public static String findAddress(String addr) {
+ return null;
+ }
+
+ public void documentHasImages(Message response) {
+ }
+
+ public void setWebViewClient(WebViewClient client) {
+ }
+
+ public void setDownloadListener(DownloadListener listener) {
+ }
+
+ public void setWebChromeClient(WebChromeClient client) {
+ }
+
+ public void addJavascriptInterface(Object obj, String interfaceName) {
+ }
+
+ public View getZoomControls() {
+ return null;
+ }
+
+ public boolean zoomIn() {
+ return false;
+ }
+
+ public boolean zoomOut() {
+ return false;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/policy/PolicyManager.java b/tools/layoutlib/bridge/src/com/android/internal/policy/PolicyManager.java
new file mode 100644
index 0000000..0100dc5
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/policy/PolicyManager.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012 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.internal.policy;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.RenderAction;
+
+import android.content.Context;
+import android.view.BridgeInflater;
+import android.view.FallbackEventHandler;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManagerPolicy;
+
+/**
+ * Custom implementation of PolicyManager that does nothing to run in LayoutLib.
+ *
+ */
+public class PolicyManager {
+
+ public static Window makeNewWindow(Context context) {
+ // this will likely crash somewhere beyond so we log it.
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "Call to PolicyManager.makeNewWindow is not supported", null);
+ return null;
+ }
+
+ public static LayoutInflater makeNewLayoutInflater(Context context) {
+ return new BridgeInflater(context, RenderAction.getCurrentContext().getProjectCallback());
+ }
+
+ public static WindowManagerPolicy makeNewWindowManager() {
+ // this will likely crash somewhere beyond so we log it.
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "Call to PolicyManager.makeNewWindowManager is not supported", null);
+ return null;
+ }
+
+ public static FallbackEventHandler makeNewFallbackEventHandler(Context context) {
+ return new FallbackEventHandler() {
+ @Override
+ public void setView(View v) {
+ }
+
+ @Override
+ public void preDispatchKeyEvent(KeyEvent event) {
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return false;
+ }
+ };
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java
new file mode 100644
index 0000000..3017292
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011 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.internal.textservice;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.textservice.SpellCheckerInfo;
+import android.view.textservice.SpellCheckerSubtype;
+
+
+/**
+ * Delegate used to provide new implementation of a select few methods of
+ * {@link ITextServicesManager$Stub}
+ *
+ * Through the layoutlib_create tool, the original methods of Stub have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class ITextServicesManager_Stub_Delegate {
+
+ @LayoutlibDelegate
+ public static ITextServicesManager asInterface(IBinder obj) {
+ // ignore the obj and return a fake interface implementation
+ return new FakeTextServicesManager();
+ }
+
+ private static class FakeTextServicesManager implements ITextServicesManager {
+
+ @Override
+ public void finishSpellCheckerService(ISpellCheckerSessionListener arg0)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void getSpellCheckerService(String arg0, String arg1,
+ ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean isSpellCheckerEnabled() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setSpellCheckerEnabled(boolean arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ }
+ }
diff --git a/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java
new file mode 100644
index 0000000..bf998b8
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 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.internal.util;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link XmlUtils}
+ *
+ * Through the layoutlib_create tool, the original methods of XmlUtils have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class XmlUtils_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static final int convertValueToInt(CharSequence charSeq, int defaultValue) {
+ if (null == charSeq)
+ return defaultValue;
+
+ String nm = charSeq.toString();
+
+ // This code is copied from the original implementation. The issue is that
+ // The Dalvik libraries are able to handle Integer.parse("XXXXXXXX", 16) where XXXXXXX
+ // is > 80000000 but the Java VM cannot.
+
+ int sign = 1;
+ int index = 0;
+ int len = nm.length();
+ int base = 10;
+
+ if ('-' == nm.charAt(0)) {
+ sign = -1;
+ index++;
+ }
+
+ if ('0' == nm.charAt(index)) {
+ // Quick check for a zero by itself
+ if (index == (len - 1))
+ return 0;
+
+ char c = nm.charAt(index + 1);
+
+ if ('x' == c || 'X' == c) {
+ index += 2;
+ base = 16;
+ } else {
+ index++;
+ base = 8;
+ }
+ }
+ else if ('#' == nm.charAt(index)) {
+ index++;
+ base = 16;
+ }
+
+ return ((int)Long.parseLong(nm.substring(index), base)) * sign;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
new file mode 100644
index 0000000..ab4be71
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -0,0 +1,639 @@
+/*
+ * 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.
+ */
+
+package com.android.layoutlib.bridge;
+
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
+import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
+
+import com.android.ide.common.rendering.api.Capability;
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.Result.Status;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layoutlib.bridge.impl.FontLoader;
+import com.android.layoutlib.bridge.impl.RenderDrawable;
+import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+import com.android.layoutlib.bridge.util.DynamicIdMap;
+import com.android.ninepatch.NinePatchChunk;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.create.MethodAdapter;
+import com.android.tools.layoutlib.create.OverrideMethod;
+import com.android.util.Pair;
+import com.ibm.icu.util.ULocale;
+
+import android.content.res.BridgeAssetManager;
+import android.graphics.Bitmap;
+import android.graphics.Typeface_Accessor;
+import android.graphics.Typeface_Delegate;
+import android.os.Looper;
+import android.os.Looper_Accessor;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import java.io.File;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Main entry point of the LayoutLib Bridge.
+ * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
+ * {@link #createScene(SceneParams)}
+ */
+public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
+
+ private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
+
+ public static class StaticMethodNotImplementedException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public StaticMethodNotImplementedException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Lock to ensure only one rendering/inflating happens at a time.
+ * This is due to some singleton in the Android framework.
+ */
+ private final static ReentrantLock sLock = new ReentrantLock();
+
+ /**
+ * Maps from id to resource type/name. This is for com.android.internal.R
+ */
+ private final static Map<Integer, Pair<ResourceType, String>> sRMap =
+ new HashMap<Integer, Pair<ResourceType, String>>();
+
+ /**
+ * Same as sRMap except for int[] instead of int resources. This is for android.R only.
+ */
+ private final static Map<IntArray, String> sRArrayMap = new HashMap<IntArray, String>();
+ /**
+ * Reverse map compared to sRMap, resource type -> (resource name -> id).
+ * This is for com.android.internal.R.
+ */
+ private final static Map<ResourceType, Map<String, Integer>> sRevRMap =
+ new EnumMap<ResourceType, Map<String,Integer>>(ResourceType.class);
+
+ // framework resources are defined as 0x01XX#### where XX is the resource type (layout,
+ // drawable, etc...). Using FF as the type allows for 255 resource types before we get a
+ // collision which should be fine.
+ private final static int DYNAMIC_ID_SEED_START = 0x01ff0000;
+ private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START);
+
+ 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<NinePatchChunk>>> sProject9PatchCache =
+ new HashMap<Object, Map<String, SoftReference<NinePatchChunk>>>();
+
+ private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache =
+ new HashMap<String, SoftReference<Bitmap>>();
+ private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
+ new HashMap<String, SoftReference<NinePatchChunk>>();
+
+ private static Map<String, Map<String, Integer>> sEnumValueMap;
+ private static Map<String, String> sPlatformProperties;
+
+ /**
+ * int[] wrapper to use as keys in maps.
+ */
+ private final static class IntArray {
+ private int[] mArray;
+
+ private IntArray() {
+ // do nothing
+ }
+
+ private IntArray(int[] a) {
+ mArray = a;
+ }
+
+ private void set(int[] a) {
+ mArray = a;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mArray);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ IntArray other = (IntArray) obj;
+ if (!Arrays.equals(mArray, other.mArray)) return false;
+ return true;
+ }
+ }
+
+ /** Instance of IntArrayWrapper to be reused in {@link #resolveResourceId(int[])}. */
+ private final static IntArray sIntArrayWrapper = new IntArray();
+
+ /**
+ * A default log than prints to stdout/stderr.
+ */
+ private final static LayoutLog sDefaultLog = new LayoutLog() {
+ @Override
+ public void error(String tag, String message, Object data) {
+ System.err.println(message);
+ }
+
+ @Override
+ public void error(String tag, String message, Throwable throwable, Object data) {
+ System.err.println(message);
+ }
+
+ @Override
+ public void warning(String tag, String message, Object data) {
+ System.out.println(message);
+ }
+ };
+
+ /**
+ * Current log.
+ */
+ private static LayoutLog sCurrentLog = sDefaultLog;
+
+ private EnumSet<Capability> mCapabilities;
+
+ @Override
+ public int getApiLevel() {
+ return com.android.ide.common.rendering.api.Bridge.API_CURRENT;
+ }
+
+ @Override
+ public EnumSet<Capability> getCapabilities() {
+ return mCapabilities;
+ }
+
+ @Override
+ public boolean init(Map<String,String> platformProperties,
+ File fontLocation,
+ Map<String, Map<String, Integer>> enumValueMap,
+ LayoutLog log) {
+ sPlatformProperties = platformProperties;
+ sEnumValueMap = enumValueMap;
+
+ // don't use EnumSet.allOf(), because the bridge doesn't come with its specific version
+ // of layoutlib_api. It is provided by the client which could have a more recent version
+ // with newer, unsupported capabilities.
+ mCapabilities = EnumSet.of(
+ Capability.UNBOUND_RENDERING,
+ Capability.CUSTOM_BACKGROUND_COLOR,
+ Capability.RENDER,
+ Capability.LAYOUT_ONLY,
+ Capability.EMBEDDED_LAYOUT,
+ Capability.VIEW_MANIPULATION,
+ Capability.PLAY_ANIMATION,
+ Capability.ANIMATED_VIEW_MANIPULATION,
+ Capability.ADAPTER_BINDING,
+ Capability.EXTENDED_VIEWINFO,
+ Capability.FIXED_SCALABLE_NINE_PATCH,
+ Capability.RTL);
+
+
+ BridgeAssetManager.initSystem();
+
+ // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
+ // on static (native) methods which prints the signature on the console and
+ // throws an exception.
+ // This is useful when testing the rendering in ADT to identify static native
+ // methods that are ignored -- layoutlib_create makes them returns 0/false/null
+ // which is generally OK yet might be a problem, so this is how you'd find out.
+ //
+ // Currently layoutlib_create only overrides static native method.
+ // Static non-natives are not overridden and thus do not get here.
+ final String debug = System.getenv("DEBUG_LAYOUT");
+ if (debug != null && !debug.equals("0") && !debug.equals("false")) {
+
+ OverrideMethod.setDefaultListener(new MethodAdapter() {
+ @Override
+ public void onInvokeV(String signature, boolean isNative, Object caller) {
+ sDefaultLog.error(null, "Missing Stub: " + signature +
+ (isNative ? " (native)" : ""), null /*data*/);
+
+ if (debug.equalsIgnoreCase("throw")) {
+ // Throwing this exception doesn't seem that useful. It breaks
+ // the layout editor yet doesn't display anything meaningful to the
+ // user. Having the error in the console is just as useful. We'll
+ // throw it only if the environment variable is "throw" or "THROW".
+ throw new StaticMethodNotImplementedException(signature);
+ }
+ }
+ });
+ }
+
+ // load the fonts.
+ FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath());
+ if (fontLoader != null) {
+ Typeface_Delegate.init(fontLoader);
+ } else {
+ log.error(LayoutLog.TAG_BROKEN,
+ "Failed create FontLoader in layout lib.", null);
+ return false;
+ }
+
+ // now parse com.android.internal.R (and only this one as android.R is a subset of
+ // the internal version), and put the content in the maps.
+ try {
+ Class<?> r = com.android.internal.R.class;
+
+ for (Class<?> inner : r.getDeclaredClasses()) {
+ String resTypeName = inner.getSimpleName();
+ ResourceType resType = ResourceType.getEnum(resTypeName);
+ if (resType != null) {
+ Map<String, Integer> fullMap = new HashMap<String, Integer>();
+ sRevRMap.put(resType, fullMap);
+
+ for (Field f : inner.getDeclaredFields()) {
+ // only process static final fields. Since the final attribute may have
+ // been altered by layoutlib_create, we only check static
+ int modifiers = f.getModifiers();
+ if (Modifier.isStatic(modifiers)) {
+ Class<?> type = f.getType();
+ if (type.isArray() && type.getComponentType() == int.class) {
+ // if the object is an int[] we put it in sRArrayMap using an IntArray
+ // wrapper that properly implements equals and hashcode for the array
+ // objects, as required by the map contract.
+ sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName());
+ } else if (type == int.class) {
+ Integer value = (Integer) f.get(null);
+ sRMap.put(value, Pair.of(resType, f.getName()));
+ fullMap.put(f.getName(), value);
+ } else {
+ assert false;
+ }
+ }
+ }
+ }
+ }
+ } catch (Throwable throwable) {
+ if (log != null) {
+ log.error(LayoutLog.TAG_BROKEN,
+ "Failed to load com.android.internal.R from the layout library jar",
+ throwable);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean dispose() {
+ BridgeAssetManager.clearSystem();
+
+ // dispose of the default typeface.
+ Typeface_Accessor.resetDefaults();
+
+ return true;
+ }
+
+ /**
+ * Starts a layout session by inflating and rendering it. The method returns a
+ * {@link RenderSession} on which further actions can be taken.
+ *
+ * @param params the {@link SessionParams} object with all the information necessary to create
+ * the scene.
+ * @return a new {@link RenderSession} object that contains the result of the layout.
+ * @since 5
+ */
+ @Override
+ public RenderSession createSession(SessionParams params) {
+ try {
+ Result lastResult = SUCCESS.createResult();
+ RenderSessionImpl scene = new RenderSessionImpl(params);
+ try {
+ prepareThread();
+ lastResult = scene.init(params.getTimeout());
+ if (lastResult.isSuccess()) {
+ lastResult = scene.inflate();
+ if (lastResult.isSuccess()) {
+ lastResult = scene.render(true /*freshRender*/);
+ }
+ }
+ } finally {
+ scene.release();
+ cleanupThread();
+ }
+
+ return new BridgeRenderSession(scene, lastResult);
+ } catch (Throwable t) {
+ // get the real cause of the exception.
+ Throwable t2 = t;
+ while (t2.getCause() != null) {
+ t2 = t.getCause();
+ }
+ return new BridgeRenderSession(null,
+ ERROR_UNKNOWN.createResult(t2.getMessage(), t));
+ }
+ }
+
+ @Override
+ public Result renderDrawable(DrawableParams params) {
+ try {
+ Result lastResult = SUCCESS.createResult();
+ RenderDrawable action = new RenderDrawable(params);
+ try {
+ prepareThread();
+ lastResult = action.init(params.getTimeout());
+ if (lastResult.isSuccess()) {
+ lastResult = action.render();
+ }
+ } finally {
+ action.release();
+ cleanupThread();
+ }
+
+ return lastResult;
+ } catch (Throwable t) {
+ // get the real cause of the exception.
+ Throwable t2 = t;
+ while (t2.getCause() != null) {
+ t2 = t.getCause();
+ }
+ return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
+ }
+ }
+
+ @Override
+ public void clearCaches(Object projectKey) {
+ if (projectKey != null) {
+ sProjectBitmapCache.remove(projectKey);
+ sProject9PatchCache.remove(projectKey);
+ }
+ }
+
+ @Override
+ public Result getViewParent(Object viewObject) {
+ if (viewObject instanceof View) {
+ return Status.SUCCESS.createResult(((View)viewObject).getParent());
+ }
+
+ throw new IllegalArgumentException("viewObject is not a View");
+ }
+
+ @Override
+ public Result getViewIndex(Object viewObject) {
+ if (viewObject instanceof View) {
+ View view = (View) viewObject;
+ ViewParent parentView = view.getParent();
+
+ if (parentView instanceof ViewGroup) {
+ Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view));
+ }
+
+ return Status.SUCCESS.createResult();
+ }
+
+ throw new IllegalArgumentException("viewObject is not a View");
+ }
+
+ @Override
+ public boolean isRtl(String locale) {
+ return isLocaleRtl(locale);
+ }
+
+ public static boolean isLocaleRtl(String locale) {
+ if (locale == null) {
+ locale = "";
+ }
+ ULocale uLocale = new ULocale(locale);
+ return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL) ?
+ true : false;
+ }
+
+ /**
+ * Returns the lock for the bridge
+ */
+ public static ReentrantLock getLock() {
+ return sLock;
+ }
+
+ /**
+ * Prepares the current thread for rendering.
+ *
+ * Note that while this can be called several time, the first call to {@link #cleanupThread()}
+ * will do the clean-up, and make the thread unable to do further scene actions.
+ */
+ public static void prepareThread() {
+ // 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.prepareMainLooper();
+ }
+ }
+
+ /**
+ * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
+ * <p>
+ * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
+ * call to this will prevent the thread from doing further scene actions
+ */
+ public static void cleanupThread() {
+ // clean up the looper
+ Looper_Accessor.cleanupThread();
+ }
+
+ public static LayoutLog getLog() {
+ return sCurrentLog;
+ }
+
+ public static void setLog(LayoutLog log) {
+ // check only the thread currently owning the lock can do this.
+ if (sLock.isHeldByCurrentThread() == false) {
+ throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
+ }
+
+ if (log != null) {
+ sCurrentLog = log;
+ } else {
+ sCurrentLog = sDefaultLog;
+ }
+ }
+
+ /**
+ * Returns details of a framework resource from its integer value.
+ * @param value the integer value
+ * @return a Pair containing the resource type and name, or null if the id
+ * does not match any resource.
+ */
+ public static Pair<ResourceType, String> resolveResourceId(int value) {
+ Pair<ResourceType, String> pair = sRMap.get(value);
+ if (pair == null) {
+ pair = sDynamicIds.resolveId(value);
+ if (pair == null) {
+ //System.out.println(String.format("Missing id: %1$08X (%1$d)", value));
+ }
+ }
+ return pair;
+ }
+
+ /**
+ * Returns the name of a framework resource whose value is an int array.
+ * @param array
+ */
+ public static String resolveResourceId(int[] array) {
+ sIntArrayWrapper.set(array);
+ return sRArrayMap.get(sIntArrayWrapper);
+ }
+
+ /**
+ * Returns the integer id of a framework resource, from a given resource type and resource name.
+ * @param type the type of the resource
+ * @param name the name of the resource.
+ * @return an {@link Integer} containing the resource id, or null if no resource were found.
+ */
+ public static Integer getResourceId(ResourceType type, String name) {
+ Map<String, Integer> map = sRevRMap.get(type);
+ Integer value = null;
+ if (map != null) {
+ value = map.get(name);
+ }
+
+ if (value == null) {
+ value = sDynamicIds.getId(type, name);
+ }
+
+ return value;
+ }
+
+ /**
+ * Returns the list of possible enums for a given attribute name.
+ */
+ public static Map<String, Integer> getEnumValues(String attributeName) {
+ if (sEnumValueMap != null) {
+ return sEnumValueMap.get(attributeName);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the platform build properties.
+ */
+ public static Map<String, String> getPlatformProperties() {
+ return sPlatformProperties;
+ }
+
+ /**
+ * Returns the bitmap for a specific path, from a specific project cache, or from the
+ * framework cache.
+ * @param value the path of the bitmap
+ * @param projectKey the key of the project, or null to query the framework cache.
+ * @return the cached Bitmap or null if not found.
+ */
+ public static Bitmap getCachedBitmap(String value, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
+ if (map != null) {
+ 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;
+ }
+
+ /**
+ * Sets a bitmap in a project cache or in the framework cache.
+ * @param value the path of the bitmap
+ * @param bmp the Bitmap object
+ * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
+ */
+ public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
+
+ if (map == null) {
+ map = new HashMap<String, SoftReference<Bitmap>>();
+ sProjectBitmapCache.put(projectKey, map);
+ }
+
+ map.put(value, new SoftReference<Bitmap>(bmp));
+ } else {
+ sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp));
+ }
+ }
+
+ /**
+ * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
+ * framework cache.
+ * @param value the path of the 9 patch
+ * @param projectKey the key of the project, or null to query the framework cache.
+ * @return the cached 9 patch or null if not found.
+ */
+ public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
+
+ if (map != null) {
+ SoftReference<NinePatchChunk> ref = map.get(value);
+ if (ref != null) {
+ return ref.get();
+ }
+ }
+ } else {
+ SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
+ if (ref != null) {
+ return ref.get();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets a 9 patch chunk in a project cache or in the framework cache.
+ * @param value the path of the 9 patch
+ * @param ninePatch the 9 patch object
+ * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
+ */
+ public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
+
+ if (map == null) {
+ map = new HashMap<String, SoftReference<NinePatchChunk>>();
+ sProject9PatchCache.put(projectKey, map);
+ }
+
+ map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
+ } else {
+ sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java
new file mode 100644
index 0000000..eb9e7f1
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+package com.android.layoutlib.bridge;
+
+/**
+ * Constant definition class.<br>
+ * <br>
+ * Most constants have a prefix defining the content.
+ * <ul>
+ * <li><code>WS_</code> Workspace path constant. Those are absolute paths,
+ * from the project root.</li>
+ * <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
+ * <li><code>FN_</code> File name constant.</li>
+ * <li><code>FD_</code> Folder name constant.</li>
+ * <li><code>EXT_</code> File extension constant. This does NOT include a dot.</li>
+ * <li><code>DOT_</code> File extension constant. This start with a dot.</li>
+ * <li><code>RE_</code> Regexp constant.</li>
+ * <li><code>NS_</code> Namespace constant.</li>
+ * <li><code>CLASS_</code> Fully qualified class name.</li>
+ * </ul>
+ *
+ */
+public class BridgeConstants {
+
+ /** Namespace for the resource XML */
+ public final static String NS_RESOURCES = "http://schemas.android.com/apk/res/android";
+
+ /** App auto namespace */
+ public final static String NS_APP_RES_AUTO = "http://schemas.android.com/apk/res-auto";
+
+ public final static String R = "com.android.internal.R";
+
+
+ public final static String MATCH_PARENT = "match_parent";
+ public final static String FILL_PARENT = "fill_parent";
+ public final static String WRAP_CONTENT = "wrap_content";
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
new file mode 100644
index 0000000..f9f4b3a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2010 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 com.android.ide.common.rendering.api.IAnimationListener;
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.ide.common.rendering.api.RenderParams;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.awt.image.BufferedImage;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An implementation of {@link RenderSession}.
+ *
+ * This is a pretty basic class that does almost nothing. All of the work is done in
+ * {@link RenderSessionImpl}.
+ *
+ */
+public class BridgeRenderSession extends RenderSession {
+
+ private final RenderSessionImpl mSession;
+ private Result mLastResult;
+
+ @Override
+ public Result getResult() {
+ return mLastResult;
+ }
+
+ @Override
+ public BufferedImage getImage() {
+ return mSession.getImage();
+ }
+
+ @Override
+ public boolean isAlphaChannelImage() {
+ return mSession.isAlphaChannelImage();
+ }
+
+ @Override
+ public List<ViewInfo> getRootViews() {
+ return mSession.getViewInfos();
+ }
+
+ @Override
+ public Map<String, String> getDefaultProperties(Object viewObject) {
+ return mSession.getDefaultProperties(viewObject);
+ }
+
+ @Override
+ public Result getProperty(Object objectView, String propertyName) {
+ // pass
+ return super.getProperty(objectView, propertyName);
+ }
+
+ @Override
+ public Result setProperty(Object objectView, String propertyName, String propertyValue) {
+ // pass
+ return super.setProperty(objectView, propertyName, propertyValue);
+ }
+
+ @Override
+ public Result render(long timeout) {
+ try {
+ Bridge.prepareThread();
+ mLastResult = mSession.acquire(timeout);
+ if (mLastResult.isSuccess()) {
+ mLastResult = mSession.render(false /*freshRender*/);
+ }
+ } finally {
+ mSession.release();
+ Bridge.cleanupThread();
+ }
+
+ return mLastResult;
+ }
+
+ @Override
+ public Result animate(Object targetObject, String animationName,
+ boolean isFrameworkAnimation, IAnimationListener listener) {
+ try {
+ Bridge.prepareThread();
+ mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
+ if (mLastResult.isSuccess()) {
+ mLastResult = mSession.animate(targetObject, animationName, isFrameworkAnimation,
+ listener);
+ }
+ } finally {
+ mSession.release();
+ Bridge.cleanupThread();
+ }
+
+ return mLastResult;
+ }
+
+ @Override
+ public Result insertChild(Object parentView, ILayoutPullParser childXml, int index,
+ IAnimationListener listener) {
+ if (parentView instanceof ViewGroup == false) {
+ throw new IllegalArgumentException("parentView is not a ViewGroup");
+ }
+
+ try {
+ Bridge.prepareThread();
+ mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
+ if (mLastResult.isSuccess()) {
+ mLastResult = mSession.insertChild((ViewGroup) parentView, childXml, index,
+ listener);
+ }
+ } finally {
+ mSession.release();
+ Bridge.cleanupThread();
+ }
+
+ return mLastResult;
+ }
+
+
+ @Override
+ public Result moveChild(Object parentView, Object childView, int index,
+ Map<String, String> layoutParams, IAnimationListener listener) {
+ if (parentView instanceof ViewGroup == false) {
+ throw new IllegalArgumentException("parentView is not a ViewGroup");
+ }
+ if (childView instanceof View == false) {
+ throw new IllegalArgumentException("childView is not a View");
+ }
+
+ try {
+ Bridge.prepareThread();
+ mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
+ if (mLastResult.isSuccess()) {
+ mLastResult = mSession.moveChild((ViewGroup) parentView, (View) childView, index,
+ layoutParams, listener);
+ }
+ } finally {
+ mSession.release();
+ Bridge.cleanupThread();
+ }
+
+ return mLastResult;
+ }
+
+ @Override
+ public Result removeChild(Object childView, IAnimationListener listener) {
+ if (childView instanceof View == false) {
+ throw new IllegalArgumentException("childView is not a View");
+ }
+
+ try {
+ Bridge.prepareThread();
+ mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
+ if (mLastResult.isSuccess()) {
+ mLastResult = mSession.removeChild((View) childView, listener);
+ }
+ } finally {
+ mSession.release();
+ Bridge.cleanupThread();
+ }
+
+ return mLastResult;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ /*package*/ BridgeRenderSession(RenderSessionImpl scene, Result lastResult) {
+ mSession = scene;
+ if (scene != null) {
+ mSession.setScene(this);
+ }
+ mLastResult = lastResult;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
new file mode 100644
index 0000000..3d50b2a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package com.android.layoutlib.bridge;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.Gravity;
+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).
+ */
+public class MockView extends TextView {
+
+ public MockView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ setText(this.getClass().getSimpleName());
+ setTextColor(0xFF000000);
+ setGravity(Gravity.CENTER);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ canvas.drawARGB(0xFF, 0x7F, 0x7F, 0x7F);
+
+ super.onDraw(canvas);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
new file mode 100644
index 0000000..89288bf
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2010 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.android;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.content.IContentProvider;
+import android.content.OperationApplicationException;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+/**
+ * Mock implementation of {@link IContentProvider}.
+ *
+ * TODO: never return null when the method is not supposed to. Return fake data instead.
+ */
+public final class BridgeContentProvider implements IContentProvider {
+ @Override
+ public ContentProviderResult[] applyBatch(String callingPackage,
+ ArrayList<ContentProviderOperation> arg0)
+ throws RemoteException, OperationApplicationException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int bulkInsert(String callingPackage, Uri arg0, ContentValues[] arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public Bundle call(String callingPackage, String arg0, String arg1, Bundle arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int delete(String callingPackage, Uri arg0, String arg1, String[] arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public String getType(Uri arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Uri insert(String callingPackage, Uri arg0, ContentValues arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public AssetFileDescriptor openAssetFile(
+ String callingPackage, Uri arg0, String arg1, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(
+ String callingPackage, Uri arg0, String arg1, ICancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Cursor query(String callingPackage, Uri arg0, String[] arg1, String arg2, String[] arg3,
+ String arg4, ICancellationSignal arg5) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int update(String callingPackage, Uri arg0, ContentValues arg1, String arg2,
+ String[] arg3) throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public String[] getStreamTypes(Uri arg0, String arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public AssetFileDescriptor openTypedAssetFile(String callingPackage, Uri arg0, String arg1,
+ Bundle arg2, ICancellationSignal signal) throws RemoteException, FileNotFoundException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal createCancellationSignal() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ @Override
+ public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java
new file mode 100644
index 0000000..8d259d7
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java
@@ -0,0 +1,120 @@
+/*
+ * 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.android;
+
+import android.content.ContentResolver;
+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 {
+
+ private BridgeContentProvider mProvider = null;
+
+ public BridgeContentResolver(Context context) {
+ super(context);
+ }
+
+ @Override
+ public IContentProvider acquireProvider(Context c, String name) {
+ if (mProvider == null) {
+ mProvider = new BridgeContentProvider();
+ }
+
+ return mProvider;
+ }
+
+ @Override
+ public IContentProvider acquireExistingProvider(Context c, String name) {
+ if (mProvider == null) {
+ mProvider = new BridgeContentProvider();
+ }
+
+ return mProvider;
+ }
+
+ @Override
+ public boolean releaseProvider(IContentProvider icp) {
+ // ignore
+ return false;
+ }
+
+ @Override
+ protected IContentProvider acquireUnstableProvider(Context c, String name) {
+ return acquireProvider(c, name);
+ }
+
+ @Override
+ public boolean releaseUnstableProvider(IContentProvider icp) {
+ return releaseProvider(icp);
+ }
+
+ /** @hide */
+ @Override
+ public void unstableProviderDied(IContentProvider icp) {
+ }
+
+ /**
+ * Stub for the layoutlib bridge content resolver.
+ */
+ @Override
+ public void registerContentObserver(Uri uri, boolean notifyForDescendents,
+ ContentObserver observer) {
+ // pass
+ }
+
+ /**
+ * Stub for the layoutlib bridge content resolver.
+ */
+ @Override
+ public void unregisterContentObserver(ContentObserver observer) {
+ // pass
+ }
+
+ /**
+ * Stub for the layoutlib bridge content resolver.
+ */
+ @Override
+ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+ // pass
+ }
+
+ /**
+ * Stub for the layoutlib bridge content resolver.
+ */
+ @Override
+ public void startSync(Uri uri, Bundle extras) {
+ // pass
+ }
+
+ /**
+ * Stub for the layoutlib bridge content resolver.
+ */
+ @Override
+ public void cancelSync(Uri uri) {
+ // pass
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
new file mode 100644
index 0000000..b9294ab
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -0,0 +1,1437 @@
+/*
+ * 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.
+ */
+
+package com.android.layoutlib.bridge.android;
+
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
+import com.android.layoutlib.bridge.android.view.WindowManagerImpl;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.Stack;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.BridgeResources;
+import android.content.res.BridgeTypedArray;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.BridgeInflater;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.textservice.TextServicesManager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Custom implementation of Context/Activity to handle non compiled resources.
+ */
+public final class BridgeContext extends Context {
+
+ private Resources mSystemResources;
+ private final HashMap<View, Object> mViewKeyMap = new HashMap<View, Object>();
+ private final Object mProjectKey;
+ private final DisplayMetrics mMetrics;
+ private final RenderResources mRenderResources;
+ private final Configuration mConfig;
+ private final ApplicationInfo mApplicationInfo;
+ private final IProjectCallback mProjectCallback;
+ private final WindowManager mWindowManager;
+
+ private Resources.Theme mTheme;
+
+ private final Map<Object, Map<String, String>> mDefaultPropMaps =
+ new IdentityHashMap<Object, Map<String,String>>();
+
+ // maps for dynamically generated id representing style objects (StyleResourceValue)
+ private Map<Integer, StyleResourceValue> mDynamicIdToStyleMap;
+ private Map<StyleResourceValue, Integer> mStyleToDynamicIdMap;
+ private int mDynamicIdGenerator = 0x01030000; // Base id for framework R.style
+
+ // cache for TypedArray generated from IStyleResourceValue object
+ private Map<int[], Map<Integer, TypedArray>> mTypedArrayCache;
+ private BridgeInflater mBridgeInflater;
+
+ private BridgeContentResolver mContentResolver;
+
+ private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>();
+
+ /**
+ * @param projectKey An Object identifying the project. This is used for the cache mechanism.
+ * @param metrics the {@link DisplayMetrics}.
+ * @param renderResources the configured resources (both framework and projects) for this
+ * render.
+ * @param projectCallback
+ * @param config the Configuration object for this render.
+ * @param targetSdkVersion the targetSdkVersion of the application.
+ */
+ public BridgeContext(Object projectKey, DisplayMetrics metrics,
+ RenderResources renderResources,
+ IProjectCallback projectCallback,
+ Configuration config,
+ int targetSdkVersion,
+ boolean hasRtlSupport) {
+ mProjectKey = projectKey;
+ mMetrics = metrics;
+ mProjectCallback = projectCallback;
+
+ mRenderResources = renderResources;
+ mConfig = config;
+
+ mApplicationInfo = new ApplicationInfo();
+ mApplicationInfo.targetSdkVersion = targetSdkVersion;
+ if (hasRtlSupport) {
+ mApplicationInfo.flags = mApplicationInfo.flags | ApplicationInfo.FLAG_SUPPORTS_RTL;
+ }
+
+ mWindowManager = new WindowManagerImpl(mMetrics);
+ }
+
+ /**
+ * Initializes the {@link Resources} singleton to be linked to this {@link Context}, its
+ * {@link DisplayMetrics}, {@link Configuration}, and {@link IProjectCallback}.
+ *
+ * @see #disposeResources()
+ */
+ public void initResources() {
+ AssetManager assetManager = AssetManager.getSystem();
+
+ mSystemResources = BridgeResources.initSystem(
+ this,
+ assetManager,
+ mMetrics,
+ mConfig,
+ mProjectCallback);
+ mTheme = mSystemResources.newTheme();
+ }
+
+ /**
+ * Disposes the {@link Resources} singleton.
+ */
+ public void disposeResources() {
+ BridgeResources.disposeSystem();
+ }
+
+ public void setBridgeInflater(BridgeInflater inflater) {
+ mBridgeInflater = inflater;
+ }
+
+ public void addViewKey(View view, Object viewKey) {
+ mViewKeyMap.put(view, viewKey);
+ }
+
+ public Object getViewKey(View view) {
+ return mViewKeyMap.get(view);
+ }
+
+ public Object getProjectKey() {
+ return mProjectKey;
+ }
+
+ public DisplayMetrics getMetrics() {
+ return mMetrics;
+ }
+
+ public IProjectCallback getProjectCallback() {
+ return mProjectCallback;
+ }
+
+ public RenderResources getRenderResources() {
+ return mRenderResources;
+ }
+
+ public Map<String, String> getDefaultPropMap(Object key) {
+ return mDefaultPropMaps.get(key);
+ }
+
+ public Configuration getConfiguration() {
+ return mConfig;
+ }
+
+ /**
+ * Adds a parser to the stack.
+ * @param parser the parser to add.
+ */
+ public void pushParser(BridgeXmlBlockParser parser) {
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("PUSH " + parser.getParser().toString());
+ }
+ mParserStack.push(parser);
+ }
+
+ /**
+ * Removes the parser at the top of the stack
+ */
+ public void popParser() {
+ BridgeXmlBlockParser parser = mParserStack.pop();
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("POPD " + parser.getParser().toString());
+ }
+ }
+
+ /**
+ * Returns the current parser at the top the of the stack.
+ * @return a parser or null.
+ */
+ public BridgeXmlBlockParser getCurrentParser() {
+ return mParserStack.peek();
+ }
+
+ /**
+ * Returns the previous parser.
+ * @return a parser or null if there isn't any previous parser
+ */
+ public BridgeXmlBlockParser getPreviousParser() {
+ if (mParserStack.size() < 2) {
+ return null;
+ }
+ return mParserStack.get(mParserStack.size() - 2);
+ }
+
+ public boolean resolveThemeAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
+ Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(resid);
+ boolean isFrameworkRes = true;
+ if (resourceInfo == null) {
+ resourceInfo = mProjectCallback.resolveResourceId(resid);
+ isFrameworkRes = false;
+ }
+
+ if (resourceInfo == null) {
+ return false;
+ }
+
+ ResourceValue value = mRenderResources.findItemInTheme(resourceInfo.getSecond(),
+ isFrameworkRes);
+ if (resolveRefs) {
+ value = mRenderResources.resolveResValue(value);
+ }
+
+ // check if this is a style resource
+ if (value instanceof StyleResourceValue) {
+ // get the id that will represent this style.
+ outValue.resourceId = getDynamicIdByStyle((StyleResourceValue)value);
+ return true;
+ }
+
+
+ int a;
+ // if this is a framework value.
+ if (value.isFramework()) {
+ // look for idName in the android R classes.
+ // use 0 a default res value as it's not a valid id value.
+ a = getFrameworkResourceValue(value.getResourceType(), value.getName(), 0 /*defValue*/);
+ } else {
+ // look for idName in the project R class.
+ // use 0 a default res value as it's not a valid id value.
+ a = getProjectResourceValue(value.getResourceType(), value.getName(), 0 /*defValue*/);
+ }
+
+ if (a != 0) {
+ outValue.resourceId = a;
+ return true;
+ }
+
+ return false;
+ }
+
+
+ public ResourceReference resolveId(int id) {
+ // first get the String related to this id in the framework
+ Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
+
+ if (resourceInfo != null) {
+ return new ResourceReference(resourceInfo.getSecond(), true);
+ }
+
+ // didn't find a match in the framework? look in the project.
+ if (mProjectCallback != null) {
+ resourceInfo = mProjectCallback.resolveResourceId(id);
+
+ if (resourceInfo != null) {
+ return new ResourceReference(resourceInfo.getSecond(), false);
+ }
+ }
+
+ return null;
+ }
+
+ public Pair<View, Boolean> inflateView(ResourceReference resource, ViewGroup parent,
+ boolean attachToRoot, boolean skipCallbackParser) {
+ boolean isPlatformLayout = resource.isFramework();
+
+ if (isPlatformLayout == false && skipCallbackParser == false) {
+ // check if the project callback can provide us with a custom parser.
+ ILayoutPullParser parser = getParser(resource);
+
+ if (parser != null) {
+ BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser,
+ this, resource.isFramework());
+ try {
+ pushParser(blockParser);
+ return Pair.of(
+ mBridgeInflater.inflate(blockParser, parent, attachToRoot),
+ true);
+ } finally {
+ popParser();
+ }
+ }
+ }
+
+ ResourceValue resValue;
+ if (resource instanceof ResourceValue) {
+ resValue = (ResourceValue) resource;
+ } else {
+ if (isPlatformLayout) {
+ resValue = mRenderResources.getFrameworkResource(ResourceType.LAYOUT,
+ resource.getName());
+ } else {
+ resValue = mRenderResources.getProjectResource(ResourceType.LAYOUT,
+ resource.getName());
+ }
+ }
+
+ if (resValue != null) {
+
+ File xml = new File(resValue.getValue());
+ if (xml.isFile()) {
+ // we need to create a pull parser around the layout XML file, and then
+ // give that to our XmlBlockParser
+ try {
+ XmlPullParser parser = ParserFactory.create(xml);
+
+ // set the resource ref to have correct view cookies
+ mBridgeInflater.setResourceReference(resource);
+
+ BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser,
+ this, resource.isFramework());
+ try {
+ pushParser(blockParser);
+ return Pair.of(
+ mBridgeInflater.inflate(blockParser, parent, attachToRoot),
+ false);
+ } finally {
+ popParser();
+ }
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to configure parser for " + xml, e, null /*data*/);
+ // we'll return null below.
+ } catch (FileNotFoundException e) {
+ // this shouldn't happen since we check above.
+ } finally {
+ mBridgeInflater.setResourceReference(null);
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ String.format("File %s is missing!", xml), null);
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ String.format("Layout %s%s does not exist.", isPlatformLayout ? "android:" : "",
+ resource.getName()), null);
+ }
+
+ return Pair.of(null, false);
+ }
+
+ @SuppressWarnings("deprecation")
+ private ILayoutPullParser getParser(ResourceReference resource) {
+ ILayoutPullParser parser;
+ if (resource instanceof ResourceValue) {
+ parser = mProjectCallback.getParser((ResourceValue) resource);
+ } else {
+ parser = mProjectCallback.getParser(resource.getName());
+ }
+ return parser;
+ }
+
+ // ------------ Context methods
+
+ @Override
+ public Resources getResources() {
+ return mSystemResources;
+ }
+
+ @Override
+ public Theme getTheme() {
+ return mTheme;
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return this.getClass().getClassLoader();
+ }
+
+ @Override
+ public Object getSystemService(String service) {
+ if (LAYOUT_INFLATER_SERVICE.equals(service)) {
+ return mBridgeInflater;
+ }
+
+ if (TEXT_SERVICES_MANAGER_SERVICE.equals(service)) {
+ // we need to return a valid service to avoid NPE
+ return TextServicesManager.getInstance();
+ }
+
+ if (WINDOW_SERVICE.equals(service)) {
+ return mWindowManager;
+ }
+
+ // needed by SearchView
+ if (INPUT_METHOD_SERVICE.equals(service)) {
+ return null;
+ }
+
+ if (POWER_SERVICE.equals(service)) {
+ return new PowerManager(this, new BridgePowerManager(), new Handler());
+ }
+
+ throw new UnsupportedOperationException("Unsupported Service: " + service);
+ }
+
+
+ @Override
+ public final TypedArray obtainStyledAttributes(int[] attrs) {
+ return createStyleBasedTypedArray(mRenderResources.getCurrentTheme(), attrs);
+ }
+
+ @Override
+ public final TypedArray obtainStyledAttributes(int resid, int[] attrs)
+ throws Resources.NotFoundException {
+ // get the StyleResourceValue based on the resId;
+ StyleResourceValue style = getStyleByDynamicId(resid);
+
+ if (style == null) {
+ throw new Resources.NotFoundException();
+ }
+
+ if (mTypedArrayCache == null) {
+ mTypedArrayCache = new HashMap<int[], Map<Integer,TypedArray>>();
+
+ Map<Integer, TypedArray> map = new HashMap<Integer, TypedArray>();
+ mTypedArrayCache.put(attrs, map);
+
+ BridgeTypedArray ta = createStyleBasedTypedArray(style, attrs);
+ map.put(resid, ta);
+
+ return ta;
+ }
+
+ // get the 2nd map
+ Map<Integer, TypedArray> map = mTypedArrayCache.get(attrs);
+ if (map == null) {
+ map = new HashMap<Integer, TypedArray>();
+ mTypedArrayCache.put(attrs, map);
+ }
+
+ // get the array from the 2nd map
+ TypedArray ta = map.get(resid);
+
+ if (ta == null) {
+ ta = createStyleBasedTypedArray(style, attrs);
+ map.put(resid, ta);
+ }
+
+ return ta;
+ }
+
+ @Override
+ public final TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs) {
+ return obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
+ @Override
+ public TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs,
+ int defStyleAttr, int defStyleRes) {
+
+ Map<String, String> defaultPropMap = null;
+ boolean isPlatformFile = true;
+
+ // Hint: for XmlPullParser, attach source //DEVICE_SRC/dalvik/libcore/xml/src/java
+ if (set instanceof BridgeXmlBlockParser) {
+ BridgeXmlBlockParser parser = null;
+ parser = (BridgeXmlBlockParser)set;
+
+ isPlatformFile = parser.isPlatformFile();
+
+ Object key = parser.getViewCookie();
+ if (key != null) {
+ defaultPropMap = mDefaultPropMaps.get(key);
+ if (defaultPropMap == null) {
+ defaultPropMap = new HashMap<String, String>();
+ mDefaultPropMaps.put(key, defaultPropMap);
+ }
+ }
+
+ } else if (set instanceof BridgeLayoutParamsMapAttributes) {
+ // this is only for temp layout params generated dynamically, so this is never
+ // platform content.
+ isPlatformFile = false;
+ } else if (set != null) { // null parser is ok
+ // really this should not be happening since its instantiated in Bridge
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Parser is not a BridgeXmlBlockParser!", null /*data*/);
+ return null;
+ }
+
+ List<Pair<String, Boolean>> attributeList = searchAttrs(attrs);
+
+ BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length,
+ isPlatformFile);
+
+ // look for a custom style.
+ String customStyle = null;
+ if (set != null) {
+ customStyle = set.getAttributeValue(null /* namespace*/, "style");
+ }
+
+ StyleResourceValue customStyleValues = null;
+ if (customStyle != null) {
+ ResourceValue item = mRenderResources.findResValue(customStyle,
+ false /*forceFrameworkOnly*/);
+
+ // resolve it in case it links to something else
+ item = mRenderResources.resolveResValue(item);
+
+ if (item instanceof StyleResourceValue) {
+ customStyleValues = (StyleResourceValue)item;
+ }
+ }
+
+ // resolve the defStyleAttr value into a IStyleResourceValue
+ StyleResourceValue defStyleValues = null;
+
+ if (defStyleAttr != 0) {
+ // get the name from the int.
+ Pair<String, Boolean> defStyleAttribute = searchAttr(defStyleAttr);
+
+ if (defaultPropMap != null) {
+ String defStyleName = defStyleAttribute.getFirst();
+ if (defStyleAttribute.getSecond()) {
+ defStyleName = "android:" + defStyleName;
+ }
+ defaultPropMap.put("style", defStyleName);
+ }
+
+ // look for the style in the current theme, and its parent:
+ ResourceValue item = mRenderResources.findItemInTheme(defStyleAttribute.getFirst(),
+ defStyleAttribute.getSecond());
+
+ if (item != null) {
+ // item is a reference to a style entry. Search for it.
+ item = mRenderResources.findResValue(item.getValue(),
+ false /*forceFrameworkOnly*/);
+
+ if (item instanceof StyleResourceValue) {
+ defStyleValues = (StyleResourceValue)item;
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
+ String.format(
+ "Failed to find style '%s' in current theme",
+ defStyleAttribute.getFirst()),
+ null /*data*/);
+ }
+ } else if (defStyleRes != 0) {
+ boolean isFrameworkRes = true;
+ Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes);
+ if (value == null) {
+ value = mProjectCallback.resolveResourceId(defStyleRes);
+ isFrameworkRes = false;
+ }
+
+ if (value != null) {
+ if (value.getFirst() == ResourceType.STYLE) {
+ // look for the style in the current theme, and its parent:
+ ResourceValue item = mRenderResources.findItemInTheme(value.getSecond(),
+ isFrameworkRes);
+ if (item != null) {
+ if (item instanceof StyleResourceValue) {
+ if (defaultPropMap != null) {
+ defaultPropMap.put("style", item.getName());
+ }
+
+ defStyleValues = (StyleResourceValue)item;
+ }
+ } else {
+ Bridge.getLog().error(null,
+ String.format(
+ "Style with id 0x%x (resolved to '%s') does not exist.",
+ defStyleRes, value.getSecond()),
+ null /*data*/);
+ }
+ } else {
+ Bridge.getLog().error(null,
+ String.format(
+ "Resouce id 0x%x is not of type STYLE (instead %s)",
+ defStyleRes, value.getFirst().toString()),
+ null /*data*/);
+ }
+ } else {
+ Bridge.getLog().error(null,
+ String.format(
+ "Failed to find style with id 0x%x in current theme",
+ defStyleRes),
+ null /*data*/);
+ }
+ }
+
+ String appNamespace = mProjectCallback.getNamespace();
+
+ if (attributeList != null) {
+ for (int index = 0 ; index < attributeList.size() ; index++) {
+ Pair<String, Boolean> attribute = attributeList.get(index);
+
+ if (attribute == null) {
+ continue;
+ }
+
+ String attrName = attribute.getFirst();
+ boolean frameworkAttr = attribute.getSecond().booleanValue();
+ String value = null;
+ if (set != null) {
+ value = set.getAttributeValue(
+ frameworkAttr ? BridgeConstants.NS_RESOURCES : appNamespace,
+ attrName);
+
+ // if this is an app attribute, and the first get fails, try with the
+ // new res-auto namespace as well
+ if (frameworkAttr == false && value == null) {
+ value = set.getAttributeValue(BridgeConstants.NS_APP_RES_AUTO, attrName);
+ }
+ }
+
+ // if there's no direct value for this attribute in the XML, we look for default
+ // values in the widget defStyle, and then in the theme.
+ if (value == null) {
+ ResourceValue resValue = null;
+
+ // look for the value in the custom style first (and its parent if needed)
+ if (customStyleValues != null) {
+ resValue = mRenderResources.findItemInStyle(customStyleValues,
+ attrName, frameworkAttr);
+ }
+
+ // then look for the value in the default Style (and its parent if needed)
+ if (resValue == null && defStyleValues != null) {
+ resValue = mRenderResources.findItemInStyle(defStyleValues,
+ attrName, frameworkAttr);
+ }
+
+ // if the item is not present in the defStyle, we look in the main theme (and
+ // its parent themes)
+ if (resValue == null) {
+ resValue = mRenderResources.findItemInTheme(attrName, frameworkAttr);
+ }
+
+ // if we found a value, we make sure this doesn't reference another value.
+ // So we resolve it.
+ if (resValue != null) {
+ // put the first default value, before the resolution.
+ if (defaultPropMap != null) {
+ defaultPropMap.put(attrName, resValue.getValue());
+ }
+
+ resValue = mRenderResources.resolveResValue(resValue);
+ }
+
+ ta.bridgeSetValue(index, attrName, frameworkAttr, resValue);
+ } else {
+ // there is a value in the XML, but we need to resolve it in case it's
+ // referencing another resource or a theme value.
+ ta.bridgeSetValue(index, attrName, frameworkAttr,
+ mRenderResources.resolveValue(null, attrName, value, isPlatformFile));
+ }
+ }
+ }
+
+ ta.sealArray();
+
+ return ta;
+ }
+
+ @Override
+ public Looper getMainLooper() {
+ return Looper.myLooper();
+ }
+
+
+ // ------------- private new methods
+
+ /**
+ * Creates a {@link BridgeTypedArray} by filling the values defined by the int[] with the
+ * values found in the given style.
+ * @see #obtainStyledAttributes(int, int[])
+ */
+ private BridgeTypedArray createStyleBasedTypedArray(StyleResourceValue style, int[] attrs)
+ throws Resources.NotFoundException {
+
+ List<Pair<String, Boolean>> attributes = searchAttrs(attrs);
+
+ BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length,
+ false);
+
+ // for each attribute, get its name so that we can search it in the style
+ for (int i = 0 ; i < attrs.length ; i++) {
+ Pair<String, Boolean> attribute = attributes.get(i);
+
+ if (attribute != null) {
+ // look for the value in the given style
+ ResourceValue resValue = mRenderResources.findItemInStyle(style,
+ attribute.getFirst(), attribute.getSecond());
+
+ if (resValue != null) {
+ // resolve it to make sure there are no references left.
+ ta.bridgeSetValue(i, attribute.getFirst(), attribute.getSecond(),
+ mRenderResources.resolveResValue(resValue));
+ }
+ }
+ }
+
+ ta.sealArray();
+
+ return ta;
+ }
+
+
+ /**
+ * The input int[] attrs is a list of attributes. The returns a list of information about
+ * each attributes. The information is (name, isFramework)
+ * <p/>
+ *
+ * @param attrs An attribute array reference given to obtainStyledAttributes.
+ * @return List of attribute information.
+ */
+ private List<Pair<String, Boolean>> searchAttrs(int[] attrs) {
+ List<Pair<String, Boolean>> results = new ArrayList<Pair<String, Boolean>>(attrs.length);
+
+ // for each attribute, get its name so that we can search it in the style
+ for (int i = 0 ; i < attrs.length ; i++) {
+ Pair<ResourceType, String> resolvedResource = Bridge.resolveResourceId(attrs[i]);
+ boolean isFramework = false;
+ if (resolvedResource != null) {
+ isFramework = true;
+ } else {
+ resolvedResource = mProjectCallback.resolveResourceId(attrs[i]);
+ }
+
+ if (resolvedResource != null) {
+ results.add(Pair.of(resolvedResource.getSecond(), isFramework));
+ } else {
+ results.add(null);
+ }
+ }
+
+ return results;
+ }
+
+ /**
+ * Searches for the attribute referenced by its internal id.
+ *
+ * @param attr An attribute reference given to obtainStyledAttributes such as defStyle.
+ * @return A (name, isFramework) pair describing the attribute if found. Returns null
+ * if nothing is found.
+ */
+ public Pair<String, Boolean> searchAttr(int attr) {
+ Pair<ResourceType, String> info = Bridge.resolveResourceId(attr);
+ if (info != null) {
+ return Pair.of(info.getSecond(), Boolean.TRUE);
+ }
+
+ info = mProjectCallback.resolveResourceId(attr);
+ if (info != null) {
+ return Pair.of(info.getSecond(), Boolean.FALSE);
+ }
+
+ return null;
+ }
+
+ public int getDynamicIdByStyle(StyleResourceValue resValue) {
+ if (mDynamicIdToStyleMap == null) {
+ // create the maps.
+ mDynamicIdToStyleMap = new HashMap<Integer, StyleResourceValue>();
+ mStyleToDynamicIdMap = new HashMap<StyleResourceValue, Integer>();
+ }
+
+ // look for an existing id
+ Integer id = mStyleToDynamicIdMap.get(resValue);
+
+ if (id == null) {
+ // generate a new id
+ id = Integer.valueOf(++mDynamicIdGenerator);
+
+ // and add it to the maps.
+ mDynamicIdToStyleMap.put(id, resValue);
+ mStyleToDynamicIdMap.put(resValue, id);
+ }
+
+ return id;
+ }
+
+ private StyleResourceValue getStyleByDynamicId(int i) {
+ if (mDynamicIdToStyleMap != null) {
+ return mDynamicIdToStyleMap.get(i);
+ }
+
+ return null;
+ }
+
+ public int getFrameworkResourceValue(ResourceType resType, String resName, int defValue) {
+ Integer value = Bridge.getResourceId(resType, resName);
+ if (value != null) {
+ return value.intValue();
+ }
+
+ return defValue;
+ }
+
+ public int getProjectResourceValue(ResourceType resType, String resName, int defValue) {
+ if (mProjectCallback != null) {
+ Integer value = mProjectCallback.getResourceId(resType, resName);
+ if (value != null) {
+ return value.intValue();
+ }
+ }
+
+ return defValue;
+ }
+
+ //------------ NOT OVERRIDEN --------------------
+
+ @Override
+ public boolean bindService(Intent arg0, ServiceConnection arg1, int arg2) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String arg0) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkCallingOrSelfUriPermission(Uri arg0, int arg1) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkCallingPermission(String arg0) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkCallingUriPermission(Uri arg0, int arg1) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkPermission(String arg0, int arg1, int arg2) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkUriPermission(Uri arg0, int arg1, int arg2, int arg3) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public int checkUriPermission(Uri arg0, String arg1, String arg2, int arg3,
+ int arg4, int arg5) {
+ // pass
+ return 0;
+ }
+
+ @Override
+ public void clearWallpaper() {
+ // pass
+
+ }
+
+ @Override
+ public Context createPackageContext(String arg0, int arg1) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Context createPackageContextAsUser(String arg0, int arg1, UserHandle user) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Context createConfigurationContext(Configuration overrideConfiguration) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Context createDisplayContext(Display display) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public String[] databaseList() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public boolean deleteDatabase(String arg0) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public boolean deleteFile(String arg0) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String arg0, String arg1) {
+ // pass
+
+ }
+
+ @Override
+ public void enforceCallingOrSelfUriPermission(Uri arg0, int arg1,
+ String arg2) {
+ // pass
+
+ }
+
+ @Override
+ public void enforceCallingPermission(String arg0, String arg1) {
+ // pass
+
+ }
+
+ @Override
+ public void enforceCallingUriPermission(Uri arg0, int arg1, String arg2) {
+ // pass
+
+ }
+
+ @Override
+ public void enforcePermission(String arg0, int arg1, int arg2, String arg3) {
+ // pass
+
+ }
+
+ @Override
+ public void enforceUriPermission(Uri arg0, int arg1, int arg2, int arg3,
+ String arg4) {
+ // pass
+
+ }
+
+ @Override
+ public void enforceUriPermission(Uri arg0, String arg1, String arg2,
+ int arg3, int arg4, int arg5, String arg6) {
+ // pass
+
+ }
+
+ @Override
+ public String[] fileList() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public AssetManager getAssets() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getCacheDir() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getExternalCacheDir() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ if (mContentResolver == null) {
+ mContentResolver = new BridgeContentResolver(this);
+ }
+ return mContentResolver;
+ }
+
+ @Override
+ public File getDatabasePath(String arg0) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getDir(String arg0, int arg1) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getFileStreamPath(String arg0) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getFilesDir() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getExternalFilesDir(String type) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public String getPackageCodePath() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public String getPackageName() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public String getBasePackageName() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public String getOpPackageName() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ return mApplicationInfo;
+ }
+
+ @Override
+ public String getPackageResourcePath() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public File getSharedPrefsFile(String name) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(String arg0, int arg1) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Drawable getWallpaper() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumWidth() {
+ return -1;
+ }
+
+ @Override
+ public int getWallpaperDesiredMinimumHeight() {
+ return -1;
+ }
+
+ @Override
+ public void grantUriPermission(String arg0, Uri arg1, int arg2) {
+ // pass
+
+ }
+
+ @Override
+ public FileInputStream openFileInput(String arg0) throws FileNotFoundException {
+ // pass
+ return null;
+ }
+
+ @Override
+ public FileOutputStream openFileOutput(String arg0, int arg1) throws FileNotFoundException {
+ // pass
+ return null;
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1, CursorFactory arg2) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1,
+ CursorFactory arg2, DatabaseErrorHandler arg3) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Drawable peekWallpaper() {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1,
+ String arg2, Handler arg3) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public Intent registerReceiverAsUser(BroadcastReceiver arg0, UserHandle arg0p5,
+ IntentFilter arg1, String arg2, Handler arg3) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public void removeStickyBroadcast(Intent arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void revokeUriPermission(Uri arg0, int arg1) {
+ // pass
+
+ }
+
+ @Override
+ public void sendBroadcast(Intent arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void sendBroadcast(Intent arg0, String arg1) {
+ // pass
+
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
+ // pass
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent arg0, String arg1) {
+ // pass
+
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent arg0, String arg1,
+ BroadcastReceiver arg2, Handler arg3, int arg4, String arg5,
+ Bundle arg6) {
+ // pass
+
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent, String receiverPermission, int appOp,
+ BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+ String initialData, Bundle initialExtras) {
+ // pass
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+ // pass
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission) {
+ // pass
+ }
+
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
+ // pass
+ }
+
+ @Override
+ public void sendStickyBroadcast(Intent arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void sendStickyOrderedBroadcast(Intent intent,
+ BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ // pass
+ }
+
+ @Override
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ // pass
+ }
+
+ @Override
+ public void sendStickyOrderedBroadcastAsUser(Intent intent,
+ UserHandle user, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ // pass
+ }
+
+ @Override
+ public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ // pass
+ }
+
+ @Override
+ public void setTheme(int arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void setWallpaper(Bitmap arg0) throws IOException {
+ // pass
+
+ }
+
+ @Override
+ public void setWallpaper(InputStream arg0) throws IOException {
+ // pass
+
+ }
+
+ @Override
+ public void startActivity(Intent arg0) {
+ // pass
+ }
+
+ @Override
+ public void startActivity(Intent arg0, Bundle arg1) {
+ // pass
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intent,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+ throws IntentSender.SendIntentException {
+ // pass
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intent,
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ Bundle options) throws IntentSender.SendIntentException {
+ // pass
+ }
+
+ @Override
+ public boolean startInstrumentation(ComponentName arg0, String arg1,
+ Bundle arg2) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public ComponentName startService(Intent arg0) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public boolean stopService(Intent arg0) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public ComponentName startServiceAsUser(Intent arg0, UserHandle arg1) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public boolean stopServiceAsUser(Intent arg0, UserHandle arg1) {
+ // pass
+ return false;
+ }
+
+ @Override
+ public void unbindService(ServiceConnection arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver arg0) {
+ // pass
+
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ @Override
+ public void startActivities(Intent[] arg0) {
+ // pass
+
+ }
+
+ @Override
+ public void startActivities(Intent[] arg0, Bundle arg1) {
+ // pass
+
+ }
+
+ @Override
+ public boolean isRestricted() {
+ return false;
+ }
+
+ @Override
+ public File getObbDir() {
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "OBB not supported", null);
+ return null;
+ }
+
+ @Override
+ public DisplayAdjustments getDisplayAdjustments(int displayId) {
+ // pass
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int getUserId() {
+ return 0; // not used
+ }
+
+ @Override
+ public File[] getExternalFilesDirs(String type) {
+ // pass
+ return new File[0];
+ }
+
+ @Override
+ public File[] getObbDirs() {
+ // pass
+ return new File[0];
+ }
+
+ @Override
+ public File[] getExternalCacheDirs() {
+ // pass
+ return new File[0];
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
new file mode 100644
index 0000000..66e67db5
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2011 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.android;
+
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.internal.view.InputBindResult;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.text.style.SuggestionSpan;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.List;
+
+/**
+ * Basic implementation of IInputMethodManager that does nothing.
+ *
+ */
+public class BridgeIInputMethodManager implements IInputMethodManager {
+
+ @Override
+ public void addClient(IInputMethodClient arg0, IInputContext arg1, int arg2, int arg3)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void finishInput(IInputMethodClient arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public InputMethodSubtype getCurrentInputMethodSubtype() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodList() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String arg0,
+ boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public List<InputMethodInfo> getInputMethodList() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public InputMethodSubtype getLastInputMethodSubtype() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public List getShortcutInputMethodsAndSubtypes() throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void hideMySoftInput(IBinder arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean hideSoftInput(IInputMethodClient arg0, int arg1, ResultReceiver arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean notifySuggestionPicked(SuggestionSpan arg0, String arg1, int arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void registerSuggestionSpansForNotification(SuggestionSpan[] arg0)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void removeClient(IInputMethodClient arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setAdditionalInputMethodSubtypes(String arg0, InputMethodSubtype[] arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public boolean setCurrentInputMethodSubtype(InputMethodSubtype arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void setImeWindowStatus(IBinder arg0, int arg1, int arg2) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setInputMethod(IBinder arg0, String arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setInputMethodAndSubtype(IBinder arg0, String arg1, InputMethodSubtype arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean setInputMethodEnabled(String arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void showInputMethodAndSubtypeEnablerFromClient(IInputMethodClient arg0, String arg1)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void showInputMethodPickerFromClient(IInputMethodClient arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void showMySoftInput(IBinder arg0, int arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean showSoftInput(IInputMethodClient arg0, int arg1, ResultReceiver arg2)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public InputBindResult startInput(IInputMethodClient client, IInputContext inputContext,
+ EditorInfo attribute, int controlFlags) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean switchToLastInputMethod(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean switchToNextInputMethod(IBinder arg0, boolean arg1) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean shouldOfferSwitchingToNextInputMethod(IBinder arg0) throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public int getInputMethodWindowVisibleHeight() throws RemoteException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public void notifyTextCommitted() throws RemoteException {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void updateStatusIcon(IBinder arg0, String arg1, int arg2) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
+ int controlFlags, int softInputMode, int windowFlags, EditorInfo attribute,
+ IInputContext inputContext) throws RemoteException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java
new file mode 100644
index 0000000..f5912e7
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2010 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.android;
+
+import com.android.layoutlib.bridge.BridgeConstants;
+
+import android.util.AttributeSet;
+
+import java.util.Map;
+
+/**
+ * An implementation of the {@link AttributeSet} interface on top of a map of attribute in the form
+ * of (name, value).
+ *
+ * This is meant to be called only from {@link BridgeContext#obtainStyledAttributes(AttributeSet, int[], int, int)}
+ * in the case of LayoutParams and therefore isn't a full implementation.
+ */
+public class BridgeLayoutParamsMapAttributes implements AttributeSet {
+
+ private final Map<String, String> mAttributes;
+
+ public BridgeLayoutParamsMapAttributes(Map<String, String> attributes) {
+ mAttributes = attributes;
+ }
+
+ @Override
+ public String getAttributeValue(String namespace, String name) {
+ if (BridgeConstants.NS_RESOURCES.equals(namespace)) {
+ return mAttributes.get(name);
+ }
+
+ return null;
+ }
+
+ // ---- the following methods are not called from
+ // BridgeContext#obtainStyledAttributes(AttributeSet, int[], int, int)
+ // Should they ever be called, we'll just implement them on a need basis.
+
+ @Override
+ public int getAttributeCount() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getAttributeName(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getAttributeValue(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPositionDescription() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeNameResource(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeListValue(String namespace, String attribute,
+ String[] options, int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(String namespace, String attribute,
+ boolean defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeResourceValue(String namespace, String attribute,
+ int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeIntValue(String namespace, String attribute,
+ int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(String namespace, String attribute,
+ int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public float getAttributeFloatValue(String namespace, String attribute,
+ float defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeListValue(int index,
+ String[] options, int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeResourceValue(int index, int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeIntValue(int index, int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public float getAttributeFloatValue(int index, float defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getIdAttribute() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getClassAttribute() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getIdAttributeResourceValue(int defaultValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getStyleAttribute() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
new file mode 100644
index 0000000..281337c
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2012 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.android;
+
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.RemoteException;
+import android.os.WorkSource;
+
+/**
+ * Fake implementation of IPowerManager.
+ *
+ */
+public class BridgePowerManager implements IPowerManager {
+
+ @Override
+ public boolean isScreenOn() throws RemoteException {
+ return true;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // pass for now.
+ return null;
+ }
+
+ @Override
+ public void acquireWakeLock(IBinder arg0, int arg1, String arg2, String arg2_5, WorkSource arg3)
+ throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void acquireWakeLockWithUid(IBinder arg0, int arg1, String arg2, String arg2_5, int arg3)
+ throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void crash(String arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void goToSleep(long arg0, int arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void nap(long arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void reboot(boolean confirm, String reason, boolean wait) {
+ // pass for now.
+ }
+
+ @Override
+ public void shutdown(boolean confirm, boolean wait) {
+ // pass for now.
+ }
+
+ @Override
+ public void releaseWakeLock(IBinder arg0, int arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void updateWakeLockUids(IBinder arg0, int[] arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setAttentionLight(boolean arg0, int arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setTemporaryScreenBrightnessSettingOverride(int arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setMaximumScreenOffTimeoutFromDeviceAdmin(int arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setStayOnSetting(int arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void updateWakeLockWorkSource(IBinder arg0, WorkSource arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public boolean isWakeLockLevelSupported(int level) throws RemoteException {
+ // pass for now.
+ return true;
+ }
+
+ @Override
+ public void userActivity(long time, int event, int flags) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void wakeUp(long time) throws RemoteException {
+ // pass for now.
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
new file mode 100644
index 0000000..df576d2
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 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.android;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.view.DragEvent;
+import android.view.IWindow;
+
+/**
+ * Implementation of {@link IWindow} to pass to the AttachInfo.
+ */
+public final class BridgeWindow implements IWindow {
+
+ @Override
+ public void dispatchAppVisibility(boolean arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchGetNewSurface() throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void executeCommand(String arg0, String arg1, ParcelFileDescriptor arg2)
+ throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void resized(Rect arg1, Rect arg1p5, Rect arg2, Rect arg3,
+ boolean arg4, Configuration arg5) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void moved(int arg0, int arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchScreenState(boolean on) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
+ boolean sync) {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchWallpaperCommand(String action, int x, int y,
+ int z, Bundle extras, boolean sync) {
+ // pass for now.
+ }
+
+ @Override
+ public void closeSystemDialogs(String reason) {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchDragEvent(DragEvent event) {
+ // pass for now.
+ }
+
+ @Override
+ public void dispatchSystemUiVisibilityChanged(int seq, int globalUi,
+ int localValue, int localChanges) {
+ // pass for now.
+ }
+
+ @Override
+ public void doneAnimating() {
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // pass for now.
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
new file mode 100644
index 0000000..09e6878
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2010 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.android;
+
+import android.content.ClipData;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.IWindow;
+import android.view.IWindowId;
+import android.view.IWindowSession;
+import android.view.InputChannel;
+import android.view.Surface;
+import android.view.SurfaceView;
+import android.view.WindowManager.LayoutParams;
+
+/**
+ * Implementation of {@link IWindowSession} so that mSession is not null in
+ * the {@link SurfaceView}.
+ */
+public final class BridgeWindowSession implements IWindowSession {
+
+ @Override
+ public int add(IWindow arg0, int seq, LayoutParams arg1, int arg2, Rect arg3,
+ InputChannel outInputchannel)
+ throws RemoteException {
+ // pass for now.
+ return 0;
+ }
+
+ @Override
+ public int addToDisplay(IWindow arg0, int seq, LayoutParams arg1, int arg2, int displayId,
+ Rect arg3, InputChannel outInputchannel)
+ throws RemoteException {
+ // pass for now.
+ return 0;
+ }
+
+ @Override
+ public int addWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2,
+ Rect arg3)
+ throws RemoteException {
+ // pass for now.
+ return 0;
+ }
+
+ @Override
+ public int addToDisplayWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2,
+ int displayId, Rect arg3)
+ throws RemoteException {
+ // pass for now.
+ return 0;
+ }
+
+ @Override
+ public void finishDrawing(IWindow arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public boolean getInTouchMode() throws RemoteException {
+ // pass for now.
+ return false;
+ }
+
+ @Override
+ public boolean performHapticFeedback(IWindow window, int effectId, boolean always) {
+ // pass for now.
+ return false;
+ }
+ @Override
+ public int relayout(IWindow arg0, int seq, LayoutParams arg1, int arg2, int arg3, int arg4,
+ int arg4_5, Rect arg5Z, Rect arg5, Rect arg6, Rect arg7, Configuration arg7b,
+ Surface arg8) throws RemoteException {
+ // pass for now.
+ return 0;
+ }
+
+ @Override
+ public void performDeferredDestroy(IWindow window) {
+ // pass for now.
+ }
+
+ @Override
+ public boolean outOfMemory(IWindow window) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
+ // pass for now.
+ }
+
+ @Override
+ public void remove(IWindow arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setInTouchMode(boolean arg0) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setTransparentRegion(IWindow arg0, Region arg1) throws RemoteException {
+ // pass for now.
+ }
+
+ @Override
+ public void setInsets(IWindow window, int touchable, Rect contentInsets,
+ Rect visibleInsets, Region touchableRegion) {
+ // pass for now.
+ }
+
+ @Override
+ public IBinder prepareDrag(IWindow window, int flags,
+ int thumbnailWidth, int thumbnailHeight, Surface outSurface)
+ throws RemoteException {
+ // pass for now
+ return null;
+ }
+
+ @Override
+ public boolean performDrag(IWindow window, IBinder dragToken,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data)
+ throws RemoteException {
+ // pass for now
+ return false;
+ }
+
+ @Override
+ public void reportDropResult(IWindow window, boolean consumed) throws RemoteException {
+ // pass for now
+ }
+
+ @Override
+ public void dragRecipientEntered(IWindow window) throws RemoteException {
+ // pass for now
+ }
+
+ @Override
+ public void dragRecipientExited(IWindow window) throws RemoteException {
+ // pass for now
+ }
+
+ @Override
+ public void setWallpaperPosition(IBinder window, float x, float y,
+ float xStep, float yStep) {
+ // pass for now.
+ }
+
+ @Override
+ public void wallpaperOffsetsComplete(IBinder window) {
+ // pass for now.
+ }
+
+ @Override
+ public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+ int z, Bundle extras, boolean sync) {
+ // pass for now.
+ return null;
+ }
+
+ @Override
+ public void wallpaperCommandComplete(IBinder window, Bundle result) {
+ // pass for now.
+ }
+
+ @Override
+ public void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
+ float dsdx, float dtdx, float dsdy, float dtdy) {
+ // pass for now.
+ }
+
+ @Override
+ public IBinder asBinder() {
+ // pass for now.
+ return null;
+ }
+
+ @Override
+ public void onRectangleOnScreenRequested(IBinder window, Rect rectangle, boolean immediate) {
+ // pass for now.
+ }
+
+ @Override
+ public IWindowId getWindowId(IBinder window) throws RemoteException {
+ // pass for now.
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
new file mode 100644
index 0000000..ac8712e
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
@@ -0,0 +1,494 @@
+/*
+ * 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.
+ */
+
+package com.android.layoutlib.bridge.android;
+
+
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.BridgeXmlPullAttributes;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * {@link BridgeXmlBlockParser} reimplements most of android.xml.XmlBlock.Parser.
+ * It delegates to both an instance of {@link XmlPullParser} and an instance of
+ * XmlPullAttributes (for the {@link AttributeSet} part).
+ */
+public class BridgeXmlBlockParser implements XmlResourceParser {
+
+ private final XmlPullParser mParser;
+ private final BridgeXmlPullAttributes mAttrib;
+ private final BridgeContext mContext;
+ private final boolean mPlatformFile;
+
+ private boolean mStarted = false;
+ private int mEventType = START_DOCUMENT;
+
+ private boolean mPopped = true; // default to true in case it's not pushed.
+
+ /**
+ * Builds a {@link BridgeXmlBlockParser}.
+ * @param parser The XmlPullParser to get the content from.
+ * @param context the Context.
+ * @param platformFile Indicates whether the the file is a platform file or not.
+ */
+ public BridgeXmlBlockParser(XmlPullParser parser, BridgeContext context, boolean platformFile) {
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("CRTE " + parser.toString());
+ }
+
+ mParser = parser;
+ mContext = context;
+ mPlatformFile = platformFile;
+ mAttrib = new BridgeXmlPullAttributes(parser, context, mPlatformFile);
+
+ if (mContext != null) {
+ mContext.pushParser(this);
+ mPopped = false;
+ }
+ }
+
+ public XmlPullParser getParser() {
+ return mParser;
+ }
+
+ public boolean isPlatformFile() {
+ return mPlatformFile;
+ }
+
+ public Object getViewCookie() {
+ if (mParser instanceof ILayoutPullParser) {
+ return ((ILayoutPullParser)mParser).getViewCookie();
+ }
+
+ return null;
+ }
+
+ public void ensurePopped() {
+ if (mContext != null && mPopped == false) {
+ mContext.popParser();
+ mPopped = true;
+ }
+ }
+
+ // ------- XmlResourceParser implementation
+
+ @Override
+ public void setFeature(String name, boolean state)
+ throws XmlPullParserException {
+ if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
+ return;
+ }
+ if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
+ return;
+ }
+ throw new XmlPullParserException("Unsupported feature: " + name);
+ }
+
+ @Override
+ public boolean getFeature(String name) {
+ if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
+ return true;
+ }
+ if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setProperty(String name, Object value) throws XmlPullParserException {
+ throw new XmlPullParserException("setProperty() not supported");
+ }
+
+ @Override
+ public Object getProperty(String name) {
+ return null;
+ }
+
+ @Override
+ public void setInput(Reader in) throws XmlPullParserException {
+ mParser.setInput(in);
+ }
+
+ @Override
+ public void setInput(InputStream inputStream, String inputEncoding)
+ throws XmlPullParserException {
+ mParser.setInput(inputStream, inputEncoding);
+ }
+
+ @Override
+ public void defineEntityReplacementText(String entityName,
+ String replacementText) throws XmlPullParserException {
+ throw new XmlPullParserException(
+ "defineEntityReplacementText() not supported");
+ }
+
+ @Override
+ public String getNamespacePrefix(int pos) throws XmlPullParserException {
+ throw new XmlPullParserException("getNamespacePrefix() not supported");
+ }
+
+ @Override
+ public String getInputEncoding() {
+ return null;
+ }
+
+ @Override
+ public String getNamespace(String prefix) {
+ throw new RuntimeException("getNamespace() not supported");
+ }
+
+ @Override
+ public int getNamespaceCount(int depth) throws XmlPullParserException {
+ throw new XmlPullParserException("getNamespaceCount() not supported");
+ }
+
+ @Override
+ public String getPositionDescription() {
+ return "Binary XML file line #" + getLineNumber();
+ }
+
+ @Override
+ public String getNamespaceUri(int pos) throws XmlPullParserException {
+ throw new XmlPullParserException("getNamespaceUri() not supported");
+ }
+
+ @Override
+ public int getColumnNumber() {
+ return -1;
+ }
+
+ @Override
+ public int getDepth() {
+ return mParser.getDepth();
+ }
+
+ @Override
+ public String getText() {
+ return mParser.getText();
+ }
+
+ @Override
+ public int getLineNumber() {
+ return mParser.getLineNumber();
+ }
+
+ @Override
+ public int getEventType() {
+ return mEventType;
+ }
+
+ @Override
+ public boolean isWhitespace() throws XmlPullParserException {
+ // Original comment: whitespace was stripped by aapt.
+ return mParser.isWhitespace();
+ }
+
+ @Override
+ public String getPrefix() {
+ throw new RuntimeException("getPrefix not supported");
+ }
+
+ @Override
+ public char[] getTextCharacters(int[] holderForStartAndLength) {
+ String txt = getText();
+ char[] chars = null;
+ if (txt != null) {
+ holderForStartAndLength[0] = 0;
+ holderForStartAndLength[1] = txt.length();
+ chars = new char[txt.length()];
+ txt.getChars(0, txt.length(), chars, 0);
+ }
+ return chars;
+ }
+
+ @Override
+ public String getNamespace() {
+ return mParser.getNamespace();
+ }
+
+ @Override
+ public String getName() {
+ return mParser.getName();
+ }
+
+ @Override
+ public String getAttributeNamespace(int index) {
+ return mParser.getAttributeNamespace(index);
+ }
+
+ @Override
+ public String getAttributeName(int index) {
+ return mParser.getAttributeName(index);
+ }
+
+ @Override
+ public String getAttributePrefix(int index) {
+ throw new RuntimeException("getAttributePrefix not supported");
+ }
+
+ @Override
+ public boolean isEmptyElementTag() {
+ // XXX Need to detect this.
+ return false;
+ }
+
+ @Override
+ public int getAttributeCount() {
+ return mParser.getAttributeCount();
+ }
+
+ @Override
+ public String getAttributeValue(int index) {
+ return mParser.getAttributeValue(index);
+ }
+
+ @Override
+ public String getAttributeType(int index) {
+ return "CDATA";
+ }
+
+ @Override
+ public boolean isAttributeDefault(int index) {
+ return false;
+ }
+
+ @Override
+ public int nextToken() throws XmlPullParserException, IOException {
+ return next();
+ }
+
+ @Override
+ public String getAttributeValue(String namespace, String name) {
+ return mParser.getAttributeValue(namespace, name);
+ }
+
+ @Override
+ public int next() throws XmlPullParserException, IOException {
+ if (!mStarted) {
+ mStarted = true;
+
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("STRT " + mParser.toString());
+ }
+
+ return START_DOCUMENT;
+ }
+
+ int ev = mParser.next();
+
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("NEXT " + mParser.toString() + " " +
+ eventTypeToString(mEventType) + " -> " + eventTypeToString(ev));
+ }
+
+ if (ev == END_TAG && mParser.getDepth() == 1) {
+ // done with parser remove it from the context stack.
+ ensurePopped();
+
+ if (ParserFactory.LOG_PARSER) {
+ System.out.println("");
+ }
+ }
+
+ mEventType = ev;
+ return ev;
+ }
+
+ public static String eventTypeToString(int eventType) {
+ switch (eventType) {
+ case START_DOCUMENT:
+ return "START_DOC";
+ case END_DOCUMENT:
+ return "END_DOC";
+ case START_TAG:
+ return "START_TAG";
+ case END_TAG:
+ return "END_TAG";
+ case TEXT:
+ return "TEXT";
+ case CDSECT:
+ return "CDSECT";
+ case ENTITY_REF:
+ return "ENTITY_REF";
+ case IGNORABLE_WHITESPACE:
+ return "IGNORABLE_WHITESPACE";
+ case PROCESSING_INSTRUCTION:
+ return "PROCESSING_INSTRUCTION";
+ case COMMENT:
+ return "COMMENT";
+ case DOCDECL:
+ return "DOCDECL";
+ }
+
+ return "????";
+ }
+
+ @Override
+ public void require(int type, String namespace, String name)
+ throws XmlPullParserException {
+ if (type != getEventType()
+ || (namespace != null && !namespace.equals(getNamespace()))
+ || (name != null && !name.equals(getName())))
+ throw new XmlPullParserException("expected " + TYPES[type]
+ + getPositionDescription());
+ }
+
+ @Override
+ public String nextText() throws XmlPullParserException, IOException {
+ if (getEventType() != START_TAG) {
+ throw new XmlPullParserException(getPositionDescription()
+ + ": parser must be on START_TAG to read next text", this,
+ null);
+ }
+ int eventType = next();
+ if (eventType == TEXT) {
+ String result = getText();
+ eventType = next();
+ if (eventType != END_TAG) {
+ throw new XmlPullParserException(
+ getPositionDescription()
+ + ": event TEXT it must be immediately followed by END_TAG",
+ this, null);
+ }
+ return result;
+ } else if (eventType == END_TAG) {
+ return "";
+ } else {
+ throw new XmlPullParserException(getPositionDescription()
+ + ": parser must be on START_TAG or TEXT to read text",
+ this, null);
+ }
+ }
+
+ @Override
+ public int nextTag() throws XmlPullParserException, IOException {
+ int eventType = next();
+ if (eventType == TEXT && isWhitespace()) { // skip whitespace
+ eventType = next();
+ }
+ if (eventType != START_TAG && eventType != END_TAG) {
+ throw new XmlPullParserException(getPositionDescription()
+ + ": expected start or end tag", this, null);
+ }
+ return eventType;
+ }
+
+ // AttributeSet implementation
+
+
+ @Override
+ public void close() {
+ // pass
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+ return mAttrib.getAttributeBooleanValue(index, defaultValue);
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(String namespace, String attribute,
+ boolean defaultValue) {
+ return mAttrib.getAttributeBooleanValue(namespace, attribute, defaultValue);
+ }
+
+ @Override
+ public float getAttributeFloatValue(int index, float defaultValue) {
+ return mAttrib.getAttributeFloatValue(index, defaultValue);
+ }
+
+ @Override
+ public float getAttributeFloatValue(String namespace, String attribute, float defaultValue) {
+ return mAttrib.getAttributeFloatValue(namespace, attribute, defaultValue);
+ }
+
+ @Override
+ public int getAttributeIntValue(int index, int defaultValue) {
+ return mAttrib.getAttributeIntValue(index, defaultValue);
+ }
+
+ @Override
+ public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
+ return mAttrib.getAttributeIntValue(namespace, attribute, defaultValue);
+ }
+
+ @Override
+ public int getAttributeListValue(int index, String[] options, int defaultValue) {
+ return mAttrib.getAttributeListValue(index, options, defaultValue);
+ }
+
+ @Override
+ public int getAttributeListValue(String namespace, String attribute,
+ String[] options, int defaultValue) {
+ return mAttrib.getAttributeListValue(namespace, attribute, options, defaultValue);
+ }
+
+ @Override
+ public int getAttributeNameResource(int index) {
+ return mAttrib.getAttributeNameResource(index);
+ }
+
+ @Override
+ public int getAttributeResourceValue(int index, int defaultValue) {
+ return mAttrib.getAttributeResourceValue(index, defaultValue);
+ }
+
+ @Override
+ public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
+ return mAttrib.getAttributeResourceValue(namespace, attribute, defaultValue);
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+ return mAttrib.getAttributeUnsignedIntValue(index, defaultValue);
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) {
+ return mAttrib.getAttributeUnsignedIntValue(namespace, attribute, defaultValue);
+ }
+
+ @Override
+ public String getClassAttribute() {
+ return mAttrib.getClassAttribute();
+ }
+
+ @Override
+ public String getIdAttribute() {
+ return mAttrib.getIdAttribute();
+ }
+
+ @Override
+ public int getIdAttributeResourceValue(int defaultValue) {
+ return mAttrib.getIdAttributeResourceValue(defaultValue);
+ }
+
+ @Override
+ public int getStyleAttribute() {
+ return mAttrib.getStyleAttribute();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
new file mode 100644
index 0000000..7e5ae8d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 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.android.view;
+
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.DisplayInfo;
+import android.view.View;
+import android.view.WindowManager;
+
+public class WindowManagerImpl implements WindowManager {
+
+ private final DisplayMetrics mMetrics;
+ private final Display mDisplay;
+
+ public WindowManagerImpl(DisplayMetrics metrics) {
+ mMetrics = metrics;
+
+ DisplayInfo info = new DisplayInfo();
+ info.logicalHeight = mMetrics.heightPixels;
+ info.logicalWidth = mMetrics.widthPixels;
+ mDisplay = new Display(null, Display.DEFAULT_DISPLAY, info,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ }
+
+ @Override
+ public Display getDefaultDisplay() {
+ return mDisplay;
+ }
+
+
+ @Override
+ public void addView(View arg0, android.view.ViewGroup.LayoutParams arg1) {
+ // pass
+ }
+
+ @Override
+ public void removeView(View arg0) {
+ // pass
+ }
+
+ @Override
+ public void updateViewLayout(View arg0, android.view.ViewGroup.LayoutParams arg1) {
+ // pass
+ }
+
+
+ @Override
+ public void removeViewImmediate(View arg0) {
+ // pass
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
new file mode 100644
index 0000000..86797e5
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2011 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.bars;
+
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.resources.Density;
+import com.android.resources.LayoutDirection;
+import com.android.resources.ResourceType;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap_Delegate;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Base "bar" class for the window decor around the the edited layout.
+ * This is basically an horizontal layout that loads a given layout on creation (it is read
+ * through {@link Class#getResourceAsStream(String)}).
+ *
+ * The given layout should be a merge layout so that all the children belong to this class directly.
+ *
+ * It also provides a few utility methods to configure the content of the layout.
+ */
+abstract class CustomBar extends LinearLayout {
+
+ protected abstract TextView getStyleableTextView();
+
+ protected CustomBar(Context context, Density density, int orientation, String layoutPath,
+ String name) throws XmlPullParserException {
+ super(context);
+ setOrientation(orientation);
+ if (orientation == LinearLayout.HORIZONTAL) {
+ setGravity(Gravity.CENTER_VERTICAL);
+ } else {
+ setGravity(Gravity.CENTER_HORIZONTAL);
+ }
+
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ XmlPullParser parser = ParserFactory.create(getClass().getResourceAsStream(layoutPath),
+ name);
+
+ BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
+ parser, (BridgeContext) context, false /*platformFile*/);
+
+ try {
+ inflater.inflate(bridgeParser, this, true);
+ } finally {
+ bridgeParser.ensurePopped();
+ }
+ }
+
+ private InputStream getIcon(String iconName, Density[] densityInOut, LayoutDirection direction,
+ String[] pathOut, boolean tryOtherDensities) {
+ // current density
+ Density density = densityInOut[0];
+
+ // bitmap url relative to this class
+ if (direction != null) {
+ pathOut[0] = "/bars/" + direction.getResourceValue() + "-" + density.getResourceValue()
+ + "/" + iconName;
+ } else {
+ pathOut[0] = "/bars/" + density.getResourceValue() + "/" + iconName;
+ }
+
+ InputStream stream = getClass().getResourceAsStream(pathOut[0]);
+ if (stream == null && tryOtherDensities) {
+ for (Density d : Density.values()) {
+ if (d != density) {
+ densityInOut[0] = d;
+ stream = getIcon(iconName, densityInOut, direction, pathOut,
+ false /*tryOtherDensities*/);
+ if (stream != null) {
+ return stream;
+ }
+ }
+ // couldn't find resource with direction qualifier. try without.
+ if (direction != null) {
+ return getIcon(iconName, densityInOut, null, pathOut, true);
+ }
+ }
+ }
+
+ return stream;
+ }
+
+ protected void loadIcon(int index, String iconName, Density density) {
+ loadIcon(index, iconName, density, false);
+ }
+
+ protected void loadIcon(int index, String iconName, Density density, boolean isRtl) {
+ View child = getChildAt(index);
+ if (child instanceof ImageView) {
+ ImageView imageView = (ImageView) child;
+
+ String[] pathOut = new String[1];
+ Density[] densityInOut = new Density[] { density };
+ LayoutDirection dir = isRtl ? LayoutDirection.RTL : LayoutDirection.LTR;
+ InputStream stream = getIcon(iconName, densityInOut, dir, pathOut,
+ true /*tryOtherDensities*/);
+ density = densityInOut[0];
+
+ if (stream != null) {
+ // look for a cached bitmap
+ Bitmap bitmap = Bridge.getCachedBitmap(pathOut[0], true /*isFramework*/);
+ if (bitmap == null) {
+ try {
+ bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density);
+ Bridge.setCachedBitmap(pathOut[0], bitmap, true /*isFramework*/);
+ } catch (IOException e) {
+ return;
+ }
+ }
+
+ if (bitmap != null) {
+ BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(),
+ bitmap);
+ imageView.setImageDrawable(drawable);
+ }
+ }
+ }
+ }
+
+ protected void loadIcon(int index, String iconReference) {
+ ResourceValue value = getResourceValue(iconReference);
+ if (value != null) {
+ loadIcon(index, value);
+ }
+ }
+
+ protected void loadIconById(int id, String iconReference) {
+ ResourceValue value = getResourceValue(iconReference);
+ if (value != null) {
+ loadIconById(id, value);
+ }
+ }
+
+
+ protected Drawable loadIcon(int index, ResourceType type, String name) {
+ BridgeContext bridgeContext = (BridgeContext) mContext;
+ RenderResources res = bridgeContext.getRenderResources();
+
+ // find the resource
+ ResourceValue value = res.getFrameworkResource(type, name);
+
+ // resolve it if needed
+ value = res.resolveResValue(value);
+ return loadIcon(index, value);
+ }
+
+ private Drawable loadIcon(int index, ResourceValue value) {
+ View child = getChildAt(index);
+ if (child instanceof ImageView) {
+ ImageView imageView = (ImageView) child;
+
+ return loadIcon(imageView, value);
+ }
+
+ return null;
+ }
+
+ private Drawable loadIconById(int id, ResourceValue value) {
+ View child = findViewById(id);
+ if (child instanceof ImageView) {
+ ImageView imageView = (ImageView) child;
+
+ return loadIcon(imageView, value);
+ }
+
+ return null;
+ }
+
+
+ private Drawable loadIcon(ImageView imageView, ResourceValue value) {
+ Drawable drawable = ResourceHelper.getDrawable(value, (BridgeContext) mContext);
+ if (drawable != null) {
+ imageView.setImageDrawable(drawable);
+ }
+
+ return drawable;
+ }
+
+ protected TextView setText(int index, String stringReference) {
+ View child = getChildAt(index);
+ if (child instanceof TextView) {
+ TextView textView = (TextView) child;
+ setText(textView, stringReference);
+ return textView;
+ }
+
+ return null;
+ }
+
+ protected TextView setTextById(int id, String stringReference) {
+ View child = findViewById(id);
+ if (child instanceof TextView) {
+ TextView textView = (TextView) child;
+ setText(textView, stringReference);
+ return textView;
+ }
+
+ return null;
+ }
+
+ private void setText(TextView textView, String stringReference) {
+ ResourceValue value = getResourceValue(stringReference);
+ if (value != null) {
+ textView.setText(value.getValue());
+ } else {
+ textView.setText(stringReference);
+ }
+ }
+
+ protected void setStyle(String themeEntryName) {
+
+ BridgeContext bridgeContext = (BridgeContext) mContext;
+ RenderResources res = bridgeContext.getRenderResources();
+
+ ResourceValue value = res.findItemInTheme(themeEntryName, true /*isFrameworkAttr*/);
+ value = res.resolveResValue(value);
+
+ if (value instanceof StyleResourceValue == false) {
+ return;
+ }
+
+ StyleResourceValue style = (StyleResourceValue) value;
+
+ // get the background
+ ResourceValue backgroundValue = res.findItemInStyle(style, "background",
+ true /*isFrameworkAttr*/);
+ backgroundValue = res.resolveResValue(backgroundValue);
+ if (backgroundValue != null) {
+ Drawable d = ResourceHelper.getDrawable(backgroundValue, bridgeContext);
+ if (d != null) {
+ setBackground(d);
+ }
+ }
+
+ TextView textView = getStyleableTextView();
+ if (textView != null) {
+ // get the text style
+ ResourceValue textStyleValue = res.findItemInStyle(style, "titleTextStyle",
+ true /*isFrameworkAttr*/);
+ textStyleValue = res.resolveResValue(textStyleValue);
+ if (textStyleValue instanceof StyleResourceValue) {
+ StyleResourceValue textStyle = (StyleResourceValue) textStyleValue;
+
+ ResourceValue textSize = res.findItemInStyle(textStyle, "textSize",
+ true /*isFrameworkAttr*/);
+ textSize = res.resolveResValue(textSize);
+
+ if (textSize != null) {
+ TypedValue out = new TypedValue();
+ if (ResourceHelper.parseFloatAttribute("textSize", textSize.getValue(), out,
+ true /*requireUnit*/)) {
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ out.getDimension(bridgeContext.getResources().getDisplayMetrics()));
+ }
+ }
+
+
+ ResourceValue textColor = res.findItemInStyle(textStyle, "textColor",
+ true /*isFrameworkAttr*/);
+ textColor = res.resolveResValue(textColor);
+ if (textColor != null) {
+ ColorStateList stateList = ResourceHelper.getColorStateList(
+ textColor, bridgeContext);
+ if (stateList != null) {
+ textView.setTextColor(stateList);
+ }
+ }
+ }
+ }
+ }
+
+ private ResourceValue getResourceValue(String reference) {
+ BridgeContext bridgeContext = (BridgeContext) mContext;
+ RenderResources res = bridgeContext.getRenderResources();
+
+ // find the resource
+ ResourceValue value = res.findResValue(reference, false /*isFramework*/);
+
+ // resolve it if needed
+ return res.resolveResValue(value);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java
new file mode 100644
index 0000000..226649d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2011 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.bars;
+
+import com.android.resources.Density;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class FakeActionBar extends CustomBar {
+
+ private TextView mTextView;
+
+ public FakeActionBar(Context context, Density density, String label, String icon)
+ throws XmlPullParserException {
+ super(context, density, LinearLayout.HORIZONTAL, "/bars/action_bar.xml", "action_bar.xml");
+
+ // Cannot access the inside items through id because no R.id values have been
+ // created for them.
+ // We do know the order though.
+ loadIconById(android.R.id.home, icon);
+ mTextView = setText(1, label);
+
+ setStyle("actionBarStyle");
+ }
+
+ @Override
+ protected TextView getStyleableTextView() {
+ return mTextView;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
new file mode 100644
index 0000000..112c267
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011 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.bars;
+
+import com.android.resources.Density;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class NavigationBar extends CustomBar {
+
+ public NavigationBar(Context context, Density density, int orientation, boolean isRtl,
+ boolean rtlEnabled) throws XmlPullParserException {
+ super(context, density, orientation, "/bars/navigation_bar.xml", "navigation_bar.xml");
+
+ setBackgroundColor(0xFF000000);
+
+ // Cannot access the inside items through id because no R.id values have been
+ // created for them.
+ // We do know the order though.
+ // 0 is a spacer.
+ int back = 1;
+ int recent = 3;
+ if (orientation == LinearLayout.VERTICAL || (isRtl && !rtlEnabled)) {
+ // If RTL is enabled, then layoutlib mirrors the layout for us.
+ back = 3;
+ recent = 1;
+ }
+
+ loadIcon(back, "ic_sysbar_back.png", density, isRtl);
+ loadIcon(2, "ic_sysbar_home.png", density, isRtl);
+ loadIcon(recent, "ic_sysbar_recent.png", density, isRtl);
+ }
+
+ @Override
+ protected TextView getStyleableTextView() {
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
new file mode 100644
index 0000000..1498044
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 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.bars;
+
+import com.android.resources.Density;
+import com.android.resources.ResourceType;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LevelListDrawable;
+import android.view.Gravity;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class StatusBar extends CustomBar {
+
+ public StatusBar(Context context, Density density, int direction, boolean RtlEnabled)
+ throws XmlPullParserException {
+ // FIXME: if direction is RTL but it's not enabled in application manifest, mirror this bar.
+ super(context, density, LinearLayout.HORIZONTAL, "/bars/status_bar.xml", "status_bar.xml");
+
+ // FIXME: use FILL_H?
+ setGravity(Gravity.START | Gravity.TOP | Gravity.RIGHT);
+ setBackgroundColor(0xFF000000);
+
+ // Cannot access the inside items through id because no R.id values have been
+ // created for them.
+ // We do know the order though.
+ // 0 is the spacer
+ loadIcon(1, "stat_sys_wifi_signal_4_fully.png", density);
+ loadIcon(2, "stat_sys_battery_charge_anim100.png", density);
+ }
+
+ @Override
+ protected TextView getStyleableTextView() {
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java
new file mode 100644
index 0000000..c27859f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 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.bars;
+
+import com.android.resources.Density;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class TitleBar extends CustomBar {
+
+ private TextView mTextView;
+
+ public TitleBar(Context context, Density density, String label)
+ throws XmlPullParserException {
+ super(context, density, LinearLayout.HORIZONTAL, "/bars/title_bar.xml", "title_bar.xml");
+
+ // Cannot access the inside items through id because no R.id values have been
+ // created for them.
+ // We do know the order though.
+ mTextView = setText(0, label);
+
+ setStyle("windowTitleBackgroundStyle");
+ }
+
+ @Override
+ protected TextView getStyleableTextView() {
+ return mTextView;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
new file mode 100644
index 0000000..ae1217d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2010 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.impl;
+
+import com.android.layoutlib.bridge.util.Debug;
+import com.android.layoutlib.bridge.util.SparseWeakArray;
+
+import android.util.SparseArray;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages native delegates.
+ *
+ * This is used in conjunction with layoublib_create: certain Android java classes are mere
+ * wrappers around a heavily native based implementation, and we need a way to run these classes
+ * in our Eclipse rendering framework without bringing all the native code from the Android
+ * platform.
+ *
+ * Thus we instruct layoutlib_create to modify the bytecode of these classes to replace their
+ * native methods by "delegate calls".
+ *
+ * For example, a native method android.graphics.Matrix.init(...) will actually become
+ * a call to android.graphics.Matrix_Delegate.init(...).
+ *
+ * The Android java classes that use native code uses an int (Java side) to reference native
+ * objects. This int is generally directly the pointer to the C structure counterpart.
+ * Typically a creation method will return such an int, and then this int will be passed later
+ * to a Java method to identify the C object to manipulate.
+ *
+ * Since we cannot use the Java object reference as the int directly, DelegateManager manages the
+ * int -> Delegate class link.
+ *
+ * Native methods usually always have the int as parameters. The first thing the delegate method
+ * will do is call {@link #getDelegate(int)} to get the Java object matching the int.
+ *
+ * Typical native init methods are returning a new int back to the Java class, so
+ * {@link #addNewDelegate(Object)} does the same.
+ *
+ * The JNI references are counted, so we do the same through a {@link WeakReference}. Because
+ * the Java object needs to count as a reference (even though it only holds an int), we use the
+ * following mechanism:
+ *
+ * - {@link #addNewDelegate(Object)} and {@link #removeJavaReferenceFor(int)} adds and removes
+ * the delegate to/from a list. This list hold the reference and prevents the GC from reclaiming
+ * the delegate.
+ *
+ * - {@link #addNewDelegate(Object)} also adds the delegate to a {@link SparseArray} that holds a
+ * {@link WeakReference} to the delegate. This allows the delegate to be deleted automatically
+ * when nothing references it. This means that any class that holds a delegate (except for the
+ * Java main class) must not use the int but the Delegate class instead. The integers must
+ * only be used in the API between the main Java class and the Delegate.
+ *
+ * @param <T> the delegate class to manage
+ */
+public final class DelegateManager<T> {
+ private final Class<T> mClass;
+ private final SparseWeakArray<T> mDelegates = new SparseWeakArray<T>();
+ /** list used to store delegates when their main object holds a reference to them.
+ * This is to ensure that the WeakReference in the SparseWeakArray doesn't get GC'ed
+ * @see #addNewDelegate(Object)
+ * @see #removeJavaReferenceFor(int)
+ */
+ private final List<T> mJavaReferences = new ArrayList<T>();
+ private int mDelegateCounter = 0;
+
+ public DelegateManager(Class<T> theClass) {
+ mClass = theClass;
+ }
+
+ /**
+ * Returns the delegate from the given native int.
+ * <p>
+ * If the int is zero, then this will always return null.
+ * <p>
+ * If the int is non zero and the delegate is not found, this will throw an assert.
+ *
+ * @param native_object the native int.
+ * @return the delegate or null if not found.
+ */
+ public T getDelegate(int native_object) {
+ if (native_object > 0) {
+ T delegate = mDelegates.get(native_object);
+
+ if (Debug.DEBUG) {
+ if (delegate == null) {
+ System.out.println("Unknown " + mClass.getSimpleName() + " with int " +
+ native_object);
+ }
+ }
+
+ assert delegate != null;
+ return delegate;
+ }
+ return null;
+ }
+
+ /**
+ * Adds a delegate to the manager and returns the native int used to identify it.
+ * @param newDelegate the delegate to add
+ * @return a unique native int to identify the delegate
+ */
+ public int addNewDelegate(T newDelegate) {
+ int native_object = ++mDelegateCounter;
+ mDelegates.put(native_object, newDelegate);
+ assert !mJavaReferences.contains(newDelegate);
+ mJavaReferences.add(newDelegate);
+
+ if (Debug.DEBUG) {
+ System.out.println("New " + mClass.getSimpleName() + " with int " + native_object);
+ }
+
+ return native_object;
+ }
+
+ /**
+ * Removes the main reference on the given delegate.
+ * @param native_object the native integer representing the delegate.
+ */
+ public void removeJavaReferenceFor(int native_object) {
+ T delegate = getDelegate(native_object);
+
+ if (Debug.DEBUG) {
+ System.out.println("Removing main Java ref on " + mClass.getSimpleName() +
+ " with int " + native_object);
+ }
+
+ mJavaReferences.remove(delegate);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
new file mode 100644
index 0000000..108b651
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java
@@ -0,0 +1,396 @@
+/*
+ * 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.
+ */
+
+package com.android.layoutlib.bridge.impl;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import android.graphics.Typeface;
+
+import java.awt.Font;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Provides {@link Font} object to the layout lib.
+ * <p/>
+ * The fonts are loaded from the SDK directory. Family/style mapping is done by parsing the
+ * fonts.xml file located alongside the ttf files.
+ */
+public final class FontLoader {
+ private static final String FONTS_SYSTEM = "system_fonts.xml";
+ private static final String FONTS_VENDOR = "vendor_fonts.xml";
+ private static final String FONTS_FALLBACK = "fallback_fonts.xml";
+
+ private static final String NODE_FAMILYSET = "familyset";
+ private static final String NODE_FAMILY = "family";
+ private static final String NODE_NAME = "name";
+ private static final String NODE_FILE = "file";
+
+ private static final String ATTRIBUTE_VARIANT = "variant";
+ private static final String ATTRIBUTE_VALUE_ELEGANT = "elegant";
+ private static final String FONT_SUFFIX_NONE = ".ttf";
+ private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf";
+ private static final String FONT_SUFFIX_BOLD = "-Bold.ttf";
+ private static final String FONT_SUFFIX_ITALIC = "-Italic.ttf";
+ private static final String FONT_SUFFIX_BOLDITALIC = "-BoldItalic.ttf";
+
+ // This must match the values of Typeface styles so that we can use them for indices in this
+ // array.
+ private static final int[] AWT_STYLES = new int[] {
+ Font.PLAIN,
+ Font.BOLD,
+ Font.ITALIC,
+ Font.BOLD | Font.ITALIC
+ };
+ private static int[] DERIVE_BOLD_ITALIC = new int[] {
+ Typeface.ITALIC, Typeface.BOLD, Typeface.NORMAL
+ };
+ private static int[] DERIVE_ITALIC = new int[] { Typeface.NORMAL };
+ private static int[] DERIVE_BOLD = new int[] { Typeface.NORMAL };
+
+ private static final List<FontInfo> mMainFonts = new ArrayList<FontInfo>();
+ private static final List<FontInfo> mFallbackFonts = new ArrayList<FontInfo>();
+
+ private final String mOsFontsLocation;
+
+ public static FontLoader create(String fontOsLocation) {
+ try {
+ SAXParserFactory parserFactory = SAXParserFactory.newInstance();
+ parserFactory.setNamespaceAware(true);
+
+ // parse the system fonts
+ FontHandler handler = parseFontFile(parserFactory, fontOsLocation, FONTS_SYSTEM);
+ List<FontInfo> systemFonts = handler.getFontList();
+
+
+ // parse the fallback fonts
+ handler = parseFontFile(parserFactory, fontOsLocation, FONTS_FALLBACK);
+ List<FontInfo> fallbackFonts = handler.getFontList();
+
+ return new FontLoader(fontOsLocation, systemFonts, fallbackFonts);
+ } catch (ParserConfigurationException e) {
+ // return null below
+ } catch (SAXException e) {
+ // return null below
+ } catch (FileNotFoundException e) {
+ // return null below
+ } catch (IOException e) {
+ // return null below
+ }
+
+ return null;
+ }
+
+ private static FontHandler parseFontFile(SAXParserFactory parserFactory,
+ String fontOsLocation, String fontFileName)
+ throws ParserConfigurationException, SAXException, IOException, FileNotFoundException {
+
+ SAXParser parser = parserFactory.newSAXParser();
+ File f = new File(fontOsLocation, fontFileName);
+
+ FontHandler definitionParser = new FontHandler(
+ fontOsLocation + File.separator);
+ parser.parse(new FileInputStream(f), definitionParser);
+ return definitionParser;
+ }
+
+ private FontLoader(String fontOsLocation,
+ List<FontInfo> fontList, List<FontInfo> fallBackList) {
+ mOsFontsLocation = fontOsLocation;
+ mMainFonts.addAll(fontList);
+ mFallbackFonts.addAll(fallBackList);
+ }
+
+
+ public String getOsFontsLocation() {
+ return mOsFontsLocation;
+ }
+
+ /**
+ * Returns a {@link Font} object given a family name and a style value (constant in
+ * {@link Typeface}).
+ * @param family the family name
+ * @param style a 1-item array containing the requested style. Based on the font being read
+ * the actual style may be different. The array contains the actual style after
+ * the method returns.
+ * @return the font object or null if no match could be found.
+ */
+ public synchronized List<Font> getFont(String family, int style) {
+ List<Font> result = new ArrayList<Font>();
+
+ if (family == null) {
+ return result;
+ }
+
+
+ // get the font objects from the main list based on family.
+ for (FontInfo info : mMainFonts) {
+ if (info.families.contains(family)) {
+ result.add(info.font[style]);
+ break;
+ }
+ }
+
+ // add all the fallback fonts for the given style
+ for (FontInfo info : mFallbackFonts) {
+ result.add(info.font[style]);
+ }
+
+ return result;
+ }
+
+
+ public synchronized List<Font> getFallbackFonts(int style) {
+ List<Font> result = new ArrayList<Font>();
+ // add all the fallback fonts
+ for (FontInfo info : mFallbackFonts) {
+ result.add(info.font[style]);
+ }
+ return result;
+ }
+
+
+ private final static class FontInfo {
+ final Font[] font = new Font[4]; // Matches the 4 type-face styles.
+ final Set<String> families;
+
+ FontInfo() {
+ families = new HashSet<String>();
+ }
+ }
+
+ private final static class FontHandler extends DefaultHandler {
+ private final String mOsFontsLocation;
+
+ private FontInfo mFontInfo = null;
+ private final StringBuilder mBuilder = new StringBuilder();
+ private List<FontInfo> mFontList = new ArrayList<FontInfo>();
+ private boolean isCompactFont = true;
+
+ private FontHandler(String osFontsLocation) {
+ super();
+ mOsFontsLocation = osFontsLocation;
+ }
+
+ public List<FontInfo> getFontList() {
+ return mFontList;
+ }
+
+ /* (non-Javadoc)
+ * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ @Override
+ public void startElement(String uri, String localName, String name, Attributes attributes)
+ throws SAXException {
+ if (NODE_FAMILYSET.equals(localName)) {
+ mFontList = new ArrayList<FontInfo>();
+ } else if (NODE_FAMILY.equals(localName)) {
+ if (mFontList != null) {
+ mFontInfo = null;
+ }
+ } else if (NODE_NAME.equals(localName)) {
+ if (mFontList != null && mFontInfo == null) {
+ mFontInfo = new FontInfo();
+ }
+ } else if (NODE_FILE.equals(localName)) {
+ if (mFontList != null && mFontInfo == null) {
+ mFontInfo = new FontInfo();
+ }
+ if (ATTRIBUTE_VALUE_ELEGANT.equals(attributes.getValue(ATTRIBUTE_VARIANT))) {
+ isCompactFont = false;
+ } else {
+ isCompactFont = true;
+ }
+ }
+
+ mBuilder.setLength(0);
+
+ super.startElement(uri, localName, name, attributes);
+ }
+
+ /* (non-Javadoc)
+ * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
+ */
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ if (isCompactFont) {
+ mBuilder.append(ch, start, length);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
+ */
+ @Override
+ public void endElement(String uri, String localName, String name) throws SAXException {
+ if (NODE_FAMILY.equals(localName)) {
+ if (mFontInfo != null) {
+ // if has a normal font file, add to the list
+ if (mFontInfo.font[Typeface.NORMAL] != null) {
+ mFontList.add(mFontInfo);
+
+ // create missing font styles, order is important.
+ if (mFontInfo.font[Typeface.BOLD_ITALIC] == null) {
+ computeDerivedFont(Typeface.BOLD_ITALIC, DERIVE_BOLD_ITALIC);
+ }
+ if (mFontInfo.font[Typeface.ITALIC] == null) {
+ computeDerivedFont(Typeface.ITALIC, DERIVE_ITALIC);
+ }
+ if (mFontInfo.font[Typeface.BOLD] == null) {
+ computeDerivedFont(Typeface.BOLD, DERIVE_BOLD);
+ }
+ }
+
+ mFontInfo = null;
+ }
+ } else if (NODE_NAME.equals(localName)) {
+ // handle a new name for an existing Font Info
+ if (mFontInfo != null) {
+ String family = trimXmlWhitespaces(mBuilder.toString());
+ mFontInfo.families.add(family);
+ }
+ } else if (NODE_FILE.equals(localName)) {
+ // handle a new file for an existing Font Info
+ if (isCompactFont && mFontInfo != null) {
+ String fileName = trimXmlWhitespaces(mBuilder.toString());
+ Font font = getFont(fileName);
+ if (font != null) {
+ if (fileName.endsWith(FONT_SUFFIX_REGULAR)) {
+ mFontInfo.font[Typeface.NORMAL] = font;
+ } else if (fileName.endsWith(FONT_SUFFIX_BOLD)) {
+ mFontInfo.font[Typeface.BOLD] = font;
+ } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) {
+ mFontInfo.font[Typeface.ITALIC] = font;
+ } else if (fileName.endsWith(FONT_SUFFIX_BOLDITALIC)) {
+ mFontInfo.font[Typeface.BOLD_ITALIC] = font;
+ } else if (fileName.endsWith(FONT_SUFFIX_NONE)) {
+ mFontInfo.font[Typeface.NORMAL] = font;
+ }
+ }
+ }
+ }
+ }
+
+ private Font getFont(String fileName) {
+ try {
+ File file = new File(mOsFontsLocation, fileName);
+ if (file.exists()) {
+ return Font.createFont(Font.TRUETYPE_FONT, file);
+ }
+ } catch (Exception e) {
+
+ }
+
+ return null;
+ }
+
+ private void computeDerivedFont( int toCompute, int[] basedOnList) {
+ for (int basedOn : basedOnList) {
+ if (mFontInfo.font[basedOn] != null) {
+ mFontInfo.font[toCompute] =
+ mFontInfo.font[basedOn].deriveFont(AWT_STYLES[toCompute]);
+ return;
+ }
+ }
+
+ // we really shouldn't stop there. This means we don't have a NORMAL font...
+ assert false;
+ }
+
+ private String trimXmlWhitespaces(String value) {
+ if (value == null) {
+ return null;
+ }
+
+ // look for carriage return and replace all whitespace around it by just 1 space.
+ int index;
+
+ while ((index = value.indexOf('\n')) != -1) {
+ // look for whitespace on each side
+ int left = index - 1;
+ while (left >= 0) {
+ if (Character.isWhitespace(value.charAt(left))) {
+ left--;
+ } else {
+ break;
+ }
+ }
+
+ int right = index + 1;
+ int count = value.length();
+ while (right < count) {
+ if (Character.isWhitespace(value.charAt(right))) {
+ right++;
+ } else {
+ break;
+ }
+ }
+
+ // remove all between left and right (non inclusive) and replace by a single space.
+ String leftString = null;
+ if (left >= 0) {
+ leftString = value.substring(0, left + 1);
+ }
+ String rightString = null;
+ if (right < count) {
+ rightString = value.substring(right);
+ }
+
+ if (leftString != null) {
+ value = leftString;
+ if (rightString != null) {
+ value += " " + rightString;
+ }
+ } else {
+ value = rightString != null ? rightString : "";
+ }
+ }
+
+ // now we un-escape the string
+ int length = value.length();
+ char[] buffer = value.toCharArray();
+
+ for (int i = 0 ; i < length ; i++) {
+ if (buffer[i] == '\\') {
+ if (buffer[i+1] == 'n') {
+ // replace the char with \n
+ buffer[i+1] = '\n';
+ }
+
+ // offset the rest of the buffer since we go from 2 to 1 char
+ System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
+ length--;
+ }
+ }
+
+ return new String(buffer, 0, length);
+ }
+
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
new file mode 100644
index 0000000..21d6b1a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -0,0 +1,803 @@
+/*
+ * Copyright (C) 2010 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.impl;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+
+import android.graphics.Bitmap_Delegate;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint_Delegate;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.Region_Delegate;
+import android.graphics.Shader_Delegate;
+import android.graphics.Xfermode_Delegate;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+
+/**
+ * Class representing a graphics context snapshot, as well as a context stack as a linked list.
+ * <p>
+ * This is based on top of {@link Graphics2D} but can operate independently if none are available
+ * yet when setting transforms and clip information.
+ * <p>
+ * This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and
+ * {@link #draw(Drawable, Paint_Delegate)}
+ *
+ * Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through
+ * a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer}
+ * for each layer. Doing a save() will duplicate this list so that each graphics2D object
+ * ({@link Layer#getGraphics()}) is configured only for the new snapshot.
+ */
+public class GcSnapshot {
+
+ private final GcSnapshot mPrevious;
+ private final int mFlags;
+
+ /** list of layers. The first item in the list is always the */
+ private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
+
+ /** temp transform in case transformation are set before a Graphics2D exists */
+ private AffineTransform mTransform = null;
+ /** temp clip in case clipping is set before a Graphics2D exists */
+ private Area mClip = null;
+
+ // local layer data
+ /** a local layer created with {@link Canvas#saveLayer(RectF, Paint, int)}.
+ * If this is null, this does not mean there's no layer, just that the snapshot is not the
+ * one that created the layer.
+ * @see #getLayerSnapshot()
+ */
+ private final Layer mLocalLayer;
+ private final Paint_Delegate mLocalLayerPaint;
+ private final Rect mLayerBounds;
+
+ public interface Drawable {
+ void draw(Graphics2D graphics, Paint_Delegate paint);
+ }
+
+ /**
+ * Class containing information about a layer.
+ *
+ * This contains graphics, bitmap and layer information.
+ */
+ private static class Layer {
+ private final Graphics2D mGraphics;
+ private final Bitmap_Delegate mBitmap;
+ private final BufferedImage mImage;
+ /** the flags that were used to configure the layer. This is never changed, and passed
+ * as is when {@link #makeCopy()} is called */
+ private final int mFlags;
+ /** the original content of the layer when the next object was created. This is not
+ * passed in {@link #makeCopy()} and instead is recreated when a new layer is added
+ * (depending on its flags) */
+ private BufferedImage mOriginalCopy;
+
+ /**
+ * Creates a layer with a graphics and a bitmap. This is only used to create
+ * the base layer.
+ *
+ * @param graphics the graphics
+ * @param bitmap the bitmap
+ */
+ Layer(Graphics2D graphics, Bitmap_Delegate bitmap) {
+ mGraphics = graphics;
+ mBitmap = bitmap;
+ mImage = mBitmap.getImage();
+ mFlags = 0;
+ }
+
+ /**
+ * Creates a layer with a graphics and an image. If the image belongs to a
+ * {@link Bitmap_Delegate} (case of the base layer), then
+ * {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should be used.
+ *
+ * @param graphics the graphics the new graphics for this layer
+ * @param image the image the image from which the graphics came
+ * @param flags the flags that were used to save this layer
+ */
+ Layer(Graphics2D graphics, BufferedImage image, int flags) {
+ mGraphics = graphics;
+ mBitmap = null;
+ mImage = image;
+ mFlags = flags;
+ }
+
+ /** The Graphics2D, guaranteed to be non null */
+ Graphics2D getGraphics() {
+ return mGraphics;
+ }
+
+ /** The BufferedImage, guaranteed to be non null */
+ BufferedImage getImage() {
+ return mImage;
+ }
+
+ /** Returns the layer save flags. This is only valid for additional layers.
+ * For the base layer this will always return 0;
+ * For a given layer, all further copies of this {@link Layer} object in new snapshots
+ * will always return the same value.
+ */
+ int getFlags() {
+ return mFlags;
+ }
+
+ Layer makeCopy() {
+ if (mBitmap != null) {
+ return new Layer((Graphics2D) mGraphics.create(), mBitmap);
+ }
+
+ return new Layer((Graphics2D) mGraphics.create(), mImage, mFlags);
+ }
+
+ /** sets an optional copy of the original content to be used during restore */
+ void setOriginalCopy(BufferedImage image) {
+ mOriginalCopy = image;
+ }
+
+ BufferedImage getOriginalCopy() {
+ return mOriginalCopy;
+ }
+
+ void change() {
+ if (mBitmap != null) {
+ mBitmap.change();
+ }
+ }
+
+ /**
+ * Sets the clip for the graphics2D object associated with the layer.
+ * This should be used over the normal Graphics2D setClip method.
+ *
+ * @param clipShape the shape to use a the clip shape.
+ */
+ void setClip(Shape clipShape) {
+ // because setClip is only guaranteed to work with rectangle shape,
+ // first reset the clip to max and then intersect the current (empty)
+ // clip with the shap.
+ mGraphics.setClip(null);
+ mGraphics.clip(clipShape);
+ }
+
+ /**
+ * Clips the layer with the given shape. This performs an intersect between the current
+ * clip shape and the given shape.
+ * @param shape the new clip shape.
+ */
+ public void clip(Shape shape) {
+ mGraphics.clip(shape);
+ }
+ }
+
+ /**
+ * Creates the root snapshot associating it with a given bitmap.
+ * <p>
+ * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be
+ * called before the snapshot can be used to draw. Transform and clip operations are permitted
+ * before.
+ *
+ * @param image the image to associate to the snapshot or null.
+ * @return the root snapshot
+ */
+ public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) {
+ GcSnapshot snapshot = new GcSnapshot();
+ if (bitmap != null) {
+ snapshot.setBitmap(bitmap);
+ }
+
+ return snapshot;
+ }
+
+ /**
+ * Saves the current state according to the given flags and returns the new current snapshot.
+ * <p/>
+ * This is the equivalent of {@link Canvas#save(int)}
+ *
+ * @param flags the save flags.
+ * @return the new snapshot
+ *
+ * @see Canvas#save(int)
+ */
+ public GcSnapshot save(int flags) {
+ return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
+ }
+
+ /**
+ * Saves the current state and creates a new layer, and returns the new current snapshot.
+ * <p/>
+ * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)}
+ *
+ * @param layerBounds the layer bounds
+ * @param paint the Paint information used to blit the layer back into the layers underneath
+ * upon restore
+ * @param flags the save flags.
+ * @return the new snapshot
+ *
+ * @see Canvas#saveLayer(RectF, Paint, int)
+ */
+ public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) {
+ return new GcSnapshot(this, layerBounds, paint, flags);
+ }
+
+ /**
+ * Creates the root snapshot.
+ * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible.
+ */
+ private GcSnapshot() {
+ mPrevious = null;
+ mFlags = 0;
+ mLocalLayer = null;
+ mLocalLayerPaint = null;
+ mLayerBounds = null;
+ }
+
+ /**
+ * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored
+ * into the main graphics when {@link #restore()} is called.
+ *
+ * @param previous the previous snapshot head.
+ * @param layerBounds the region of the layer. Optional, if null, this is a normal save()
+ * @param paint the Paint information used to blit the layer back into the layers underneath
+ * upon restore
+ * @param flags the flags regarding what should be saved.
+ */
+ private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) {
+ assert previous != null;
+ mPrevious = previous;
+ mFlags = flags;
+
+ // make a copy of the current layers before adding the new one.
+ // This keeps the same BufferedImage reference but creates new Graphics2D for this
+ // snapshot.
+ // It does not copy whatever original copy the layers have, as they will be done
+ // only if the new layer doesn't clip drawing to itself.
+ for (Layer layer : mPrevious.mLayers) {
+ mLayers.add(layer.makeCopy());
+ }
+
+ if (layerBounds != null) {
+ // get the current transform
+ AffineTransform matrix = mLayers.get(0).getGraphics().getTransform();
+
+ // transform the layerBounds with the current transform and stores it into a int rect
+ RectF rect2 = new RectF();
+ mapRect(matrix, rect2, layerBounds);
+ mLayerBounds = new Rect();
+ rect2.round(mLayerBounds);
+
+ // get the base layer (always at index 0)
+ Layer baseLayer = mLayers.get(0);
+
+ // create the image for the layer
+ BufferedImage layerImage = new BufferedImage(
+ baseLayer.getImage().getWidth(),
+ baseLayer.getImage().getHeight(),
+ (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
+ BufferedImage.TYPE_INT_ARGB :
+ BufferedImage.TYPE_INT_RGB);
+
+ // create a graphics for it so that drawing can be done.
+ Graphics2D layerGraphics = layerImage.createGraphics();
+
+ // because this layer inherits the current context for transform and clip,
+ // set them to one from the base layer.
+ AffineTransform currentMtx = baseLayer.getGraphics().getTransform();
+ layerGraphics.setTransform(currentMtx);
+
+ // create a new layer for this new layer and add it to the list at the end.
+ mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage, flags));
+
+ // set the clip on it.
+ Shape currentClip = baseLayer.getGraphics().getClip();
+ mLocalLayer.setClip(currentClip);
+
+ // if the drawing is not clipped to the local layer only, we save the current content
+ // of all other layers. We are only interested in the part that will actually
+ // be drawn, so we create as small bitmaps as we can.
+ // This is so that we can erase the drawing that goes in the layers below that will
+ // be coming from the layer itself.
+ if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) {
+ int w = mLayerBounds.width();
+ int h = mLayerBounds.height();
+ for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
+ Layer layer = mLayers.get(i);
+ BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D graphics = image.createGraphics();
+ graphics.drawImage(layer.getImage(),
+ 0, 0, w, h,
+ mLayerBounds.left, mLayerBounds.top,
+ mLayerBounds.right, mLayerBounds.bottom,
+ null);
+ graphics.dispose();
+ layer.setOriginalCopy(image);
+ }
+ }
+ } else {
+ mLocalLayer = null;
+ mLayerBounds = null;
+ }
+
+ mLocalLayerPaint = paint;
+ }
+
+ public void dispose() {
+ for (Layer layer : mLayers) {
+ layer.getGraphics().dispose();
+ }
+
+ if (mPrevious != null) {
+ mPrevious.dispose();
+ }
+ }
+
+ /**
+ * Restores the top {@link GcSnapshot}, and returns the next one.
+ */
+ public GcSnapshot restore() {
+ return doRestore();
+ }
+
+ /**
+ * Restores the {@link GcSnapshot} to <var>saveCount</var>.
+ * @param saveCount the saveCount or -1 to only restore 1.
+ *
+ * @return the new head of the Gc snapshot stack.
+ */
+ public GcSnapshot restoreTo(int saveCount) {
+ return doRestoreTo(size(), saveCount);
+ }
+
+ public int size() {
+ if (mPrevious != null) {
+ return mPrevious.size() + 1;
+ }
+
+ return 1;
+ }
+
+ /**
+ * Link the snapshot to a Bitmap_Delegate.
+ * <p/>
+ * This is only for the case where the snapshot was created with a null image when calling
+ * {@link #createDefaultSnapshot(Bitmap_Delegate)}, and is therefore not yet linked to
+ * a previous snapshot.
+ * <p/>
+ * If any transform or clip information was set before, they are put into the Graphics object.
+ * @param bitmap the bitmap to link to.
+ */
+ public void setBitmap(Bitmap_Delegate bitmap) {
+ // create a new Layer for the bitmap. This will be the base layer.
+ Graphics2D graphics2D = bitmap.getImage().createGraphics();
+ Layer baseLayer = new Layer(graphics2D, bitmap);
+
+ // Set the current transform and clip which can either come from mTransform/mClip if they
+ // were set when there was no bitmap/layers or from the current base layers if there is
+ // one already.
+
+ graphics2D.setTransform(getTransform());
+ // reset mTransform in case there was one.
+ mTransform = null;
+
+ baseLayer.setClip(getClip());
+ // reset mClip in case there was one.
+ mClip = null;
+
+ // replace whatever current layers we have with this.
+ mLayers.clear();
+ mLayers.add(baseLayer);
+
+ }
+
+ public void translate(float dx, float dy) {
+ if (mLayers.size() > 0) {
+ for (Layer layer : mLayers) {
+ layer.getGraphics().translate(dx, dy);
+ }
+ } else {
+ if (mTransform == null) {
+ mTransform = new AffineTransform();
+ }
+ mTransform.translate(dx, dy);
+ }
+ }
+
+ public void rotate(double radians) {
+ if (mLayers.size() > 0) {
+ for (Layer layer : mLayers) {
+ layer.getGraphics().rotate(radians);
+ }
+ } else {
+ if (mTransform == null) {
+ mTransform = new AffineTransform();
+ }
+ mTransform.rotate(radians);
+ }
+ }
+
+ public void scale(float sx, float sy) {
+ if (mLayers.size() > 0) {
+ for (Layer layer : mLayers) {
+ layer.getGraphics().scale(sx, sy);
+ }
+ } else {
+ if (mTransform == null) {
+ mTransform = new AffineTransform();
+ }
+ mTransform.scale(sx, sy);
+ }
+ }
+
+ public AffineTransform getTransform() {
+ if (mLayers.size() > 0) {
+ // all graphics2D in the list have the same transform
+ return mLayers.get(0).getGraphics().getTransform();
+ } else {
+ if (mTransform == null) {
+ mTransform = new AffineTransform();
+ }
+ return mTransform;
+ }
+ }
+
+ public void setTransform(AffineTransform transform) {
+ if (mLayers.size() > 0) {
+ for (Layer layer : mLayers) {
+ layer.getGraphics().setTransform(transform);
+ }
+ } else {
+ if (mTransform == null) {
+ mTransform = new AffineTransform();
+ }
+ mTransform.setTransform(transform);
+ }
+ }
+
+ public boolean clip(Shape shape, int regionOp) {
+ // Simple case of intersect with existing layers.
+ // Because Graphics2D#setClip works a bit peculiarly, we optimize
+ // the case of clipping by intersection, as it's supported natively.
+ if (regionOp == Region.Op.INTERSECT.nativeInt && mLayers.size() > 0) {
+ for (Layer layer : mLayers) {
+ layer.clip(shape);
+ }
+
+ Shape currentClip = getClip();
+ return currentClip != null && currentClip.getBounds().isEmpty() == false;
+ }
+
+ Area area = null;
+
+ if (regionOp == Region.Op.REPLACE.nativeInt) {
+ area = new Area(shape);
+ } else {
+ area = Region_Delegate.combineShapes(getClip(), shape, regionOp);
+ }
+
+ assert area != null;
+
+ if (mLayers.size() > 0) {
+ if (area != null) {
+ for (Layer layer : mLayers) {
+ layer.setClip(area);
+ }
+ }
+
+ Shape currentClip = getClip();
+ return currentClip != null && currentClip.getBounds().isEmpty() == false;
+ } else {
+ if (area != null) {
+ mClip = area;
+ } else {
+ mClip = new Area();
+ }
+
+ return mClip.getBounds().isEmpty() == false;
+ }
+ }
+
+ public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
+ return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
+ }
+
+ /**
+ * Returns the current clip, or null if none have been setup.
+ */
+ public Shape getClip() {
+ if (mLayers.size() > 0) {
+ // they all have the same clip
+ return mLayers.get(0).getGraphics().getClip();
+ } else {
+ return mClip;
+ }
+ }
+
+ private GcSnapshot doRestoreTo(int size, int saveCount) {
+ if (size <= saveCount) {
+ return this;
+ }
+
+ // restore the current one first.
+ GcSnapshot previous = doRestore();
+
+ if (size == saveCount + 1) { // this was the only one that needed restore.
+ return previous;
+ } else {
+ return previous.doRestoreTo(size - 1, saveCount);
+ }
+ }
+
+ /**
+ * Executes the Drawable's draw method, with a null paint delegate.
+ * <p/>
+ * Note that the method can be called several times if there are more than one active layer.
+ * @param drawable
+ */
+ public void draw(Drawable drawable) {
+ draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/);
+ }
+
+ /**
+ * Executes the Drawable's draw method.
+ * <p/>
+ * Note that the method can be called several times if there are more than one active layer.
+ * @param drawable
+ * @param paint
+ * @param compositeOnly whether the paint is used for composite only. This is typically
+ * the case for bitmaps.
+ * @param forceSrcMode if true, this overrides the composite to be SRC
+ */
+ public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly,
+ boolean forceSrcMode) {
+ // the current snapshot may not have a mLocalLayer (ie it was created on save() instead
+ // of saveLayer(), but that doesn't mean there's no layer.
+ // mLayers however saves all the information we need (flags).
+ if (mLayers.size() == 1) {
+ // no layer, only base layer. easy case.
+ drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceSrcMode);
+ } else {
+ // draw in all the layers until the layer save flags tells us to stop (ie drawing
+ // in that layer is limited to the layer itself.
+ int flags;
+ int i = mLayers.size() - 1;
+
+ do {
+ Layer layer = mLayers.get(i);
+
+ drawInLayer(layer, drawable, paint, compositeOnly, forceSrcMode);
+
+ // then go to previous layer, only if there are any left, and its flags
+ // doesn't restrict drawing to the layer itself.
+ i--;
+ flags = layer.getFlags();
+ } while (i >= 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
+ }
+ }
+
+ private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint,
+ boolean compositeOnly, boolean forceSrcMode) {
+ Graphics2D originalGraphics = layer.getGraphics();
+ // get a Graphics2D object configured with the drawing parameters.
+ Graphics2D configuredGraphics2D =
+ paint != null ?
+ createCustomGraphics(originalGraphics, paint, compositeOnly, forceSrcMode) :
+ (Graphics2D) originalGraphics.create();
+
+ try {
+ drawable.draw(configuredGraphics2D, paint);
+ layer.change();
+ } finally {
+ // dispose Graphics2D object
+ configuredGraphics2D.dispose();
+ }
+ }
+
+ private GcSnapshot doRestore() {
+ if (mPrevious != null) {
+ if (mLocalLayer != null) {
+ // prepare to blit the layers in which we have draw, in the layer beneath
+ // them, starting with the top one (which is the current local layer).
+ int i = mLayers.size() - 1;
+ int flags;
+ do {
+ Layer dstLayer = mLayers.get(i - 1);
+
+ restoreLayer(dstLayer);
+
+ flags = dstLayer.getFlags();
+ i--;
+ } while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
+ }
+
+ // if this snapshot does not save everything, then set the previous snapshot
+ // to this snapshot content
+
+ // didn't save the matrix? set the current matrix on the previous snapshot
+ if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
+ AffineTransform mtx = getTransform();
+ for (Layer layer : mPrevious.mLayers) {
+ layer.getGraphics().setTransform(mtx);
+ }
+ }
+
+ // didn't save the clip? set the current clip on the previous snapshot
+ if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
+ Shape clip = getClip();
+ for (Layer layer : mPrevious.mLayers) {
+ layer.setClip(clip);
+ }
+ }
+ }
+
+ for (Layer layer : mLayers) {
+ layer.getGraphics().dispose();
+ }
+
+ return mPrevious;
+ }
+
+ private void restoreLayer(Layer dstLayer) {
+
+ Graphics2D baseGfx = dstLayer.getImage().createGraphics();
+
+ // if the layer contains an original copy this means the flags
+ // didn't restrict drawing to the local layer and we need to make sure the
+ // layer bounds in the layer beneath didn't receive any drawing.
+ // so we use the originalCopy to erase the new drawings in there.
+ BufferedImage originalCopy = dstLayer.getOriginalCopy();
+ if (originalCopy != null) {
+ Graphics2D g = (Graphics2D) baseGfx.create();
+ g.setComposite(AlphaComposite.Src);
+
+ g.drawImage(originalCopy,
+ mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
+ 0, 0, mLayerBounds.width(), mLayerBounds.height(),
+ null);
+ g.dispose();
+ }
+
+ // now draw put the content of the local layer onto the layer,
+ // using the paint information
+ Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
+ true /*alphaOnly*/, false /*forceSrcMode*/);
+
+ g.drawImage(mLocalLayer.getImage(),
+ mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
+ mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
+ null);
+ g.dispose();
+
+ baseGfx.dispose();
+ }
+
+ /**
+ * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
+ * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
+ */
+ private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
+ boolean compositeOnly, boolean forceSrcMode) {
+ // make new one graphics
+ Graphics2D g = (Graphics2D) original.create();
+
+ // configure it
+
+ if (paint.isAntiAliased()) {
+ g.setRenderingHint(
+ RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setRenderingHint(
+ RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ }
+
+ boolean customShader = false;
+
+ // get the shader first, as it'll replace the color if it can be used it.
+ if (compositeOnly == false) {
+ Shader_Delegate shaderDelegate = paint.getShader();
+ if (shaderDelegate != null) {
+ if (shaderDelegate.isSupported()) {
+ java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
+ assert shaderPaint != null;
+ if (shaderPaint != null) {
+ g.setPaint(shaderPaint);
+ customShader = true;
+ }
+ } else {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
+ shaderDelegate.getSupportMessage(),
+ null /*throwable*/, null /*data*/);
+ }
+ }
+
+ // if no shader, use the paint color
+ if (customShader == false) {
+ g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
+ }
+
+ // set the stroke
+ g.setStroke(paint.getJavaStroke());
+ }
+
+ // the alpha for the composite. Always opaque if the normal paint color is used since
+ // it contains the alpha
+ int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF;
+
+ if (forceSrcMode) {
+ g.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC, (float) alpha / 255.f));
+ } else {
+ boolean customXfermode = false;
+ Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
+ if (xfermodeDelegate != null) {
+ if (xfermodeDelegate.isSupported()) {
+ Composite composite = xfermodeDelegate.getComposite(alpha);
+ assert composite != null;
+ if (composite != null) {
+ g.setComposite(composite);
+ customXfermode = true;
+ }
+ } else {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE,
+ xfermodeDelegate.getSupportMessage(),
+ null /*throwable*/, null /*data*/);
+ }
+ }
+
+ // if there was no custom xfermode, but we have alpha (due to a shader and a non
+ // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
+ // that will handle the alpha.
+ if (customXfermode == false && alpha != 0xFF) {
+ g.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, (float) alpha / 255.f));
+ }
+ }
+
+ return g;
+ }
+
+ private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
+ // array with 4 corners
+ float[] corners = new float[] {
+ src.left, src.top,
+ src.right, src.top,
+ src.right, src.bottom,
+ src.left, src.bottom,
+ };
+
+ // apply the transform to them.
+ matrix.transform(corners, 0, corners, 0, 4);
+
+ // now put the result in the rect. We take the min/max of Xs and min/max of Ys
+ dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
+ dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
+
+ dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
+ dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
new file mode 100644
index 0000000..803849f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2011 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.impl;
+
+
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A factory for {@link XmlPullParser}.
+ *
+ */
+public class ParserFactory {
+
+ private final static String ENCODING = "UTF-8"; //$NON-NLS-1$
+
+ public final static boolean LOG_PARSER = false;
+
+ public static XmlPullParser create(File f)
+ throws XmlPullParserException, FileNotFoundException {
+ InputStream stream = new FileInputStream(f);
+ return create(stream, f.getName(), f.length());
+ }
+
+ public static XmlPullParser create(InputStream stream, String name)
+ throws XmlPullParserException {
+ return create(stream, name, -1);
+ }
+
+ private static XmlPullParser create(InputStream stream, String name, long size)
+ throws XmlPullParserException {
+ KXmlParser parser = instantiateParser(name);
+
+ stream = readAndClose(stream, name, size);
+
+ parser.setInput(stream, ENCODING);
+ return parser;
+ }
+
+ private static KXmlParser instantiateParser(String name) throws XmlPullParserException {
+ KXmlParser parser;
+ if (name != null) {
+ parser = new CustomParser(name);
+ } else {
+ parser = new KXmlParser();
+ }
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ return parser;
+ }
+
+ private static InputStream readAndClose(InputStream stream, String name, long size)
+ throws XmlPullParserException {
+ // just a sanity check. It's doubtful we'll have such big files!
+ if (size > Integer.MAX_VALUE) {
+ throw new XmlPullParserException("File " + name + " is too big to be parsed");
+ }
+ int intSize = (int) size;
+
+ // create a buffered reader to facilitate reading.
+ BufferedInputStream bufferedStream = new BufferedInputStream(stream);
+ try {
+ int avail;
+ if (intSize != -1) {
+ avail = intSize;
+ } else {
+ // get the size to read.
+ avail = bufferedStream.available();
+ }
+
+ // create the initial buffer and read it.
+ byte[] buffer = new byte[avail];
+ int read = stream.read(buffer);
+
+ // this is the easy case.
+ if (read == intSize) {
+ return new ByteArrayInputStream(buffer);
+ }
+
+ // check if there is more to read (read() does not necessarily read all that
+ // available() returned!)
+ while ((avail = bufferedStream.available()) > 0) {
+ if (read + avail > buffer.length) {
+ // just allocate what is needed. We're mostly reading small files
+ // so it shouldn't be too problematic.
+ byte[] moreBuffer = new byte[read + avail];
+ System.arraycopy(buffer, 0, moreBuffer, 0, read);
+ buffer = moreBuffer;
+ }
+
+ read += stream.read(buffer, read, avail);
+ }
+
+ // return a new stream encapsulating this buffer.
+ return new ByteArrayInputStream(buffer);
+
+ } catch (IOException e) {
+ throw new XmlPullParserException("Failed to read " + name, null, e);
+ } finally {
+ try {
+ bufferedStream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ private static class CustomParser extends KXmlParser {
+ private final String mName;
+
+ CustomParser(String name) {
+ super();
+ mName = name;
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java
new file mode 100644
index 0000000..7b70180
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 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.impl;
+
+import com.android.ide.common.rendering.api.IAnimationListener;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.Result.Status;
+
+import android.animation.AnimationThread;
+import android.animation.Animator;
+
+public class PlayAnimationThread extends AnimationThread {
+
+ private final Animator mAnimator;
+
+ public PlayAnimationThread(Animator animator, RenderSessionImpl scene, String animName,
+ IAnimationListener listener) {
+ super(scene, animName, listener);
+ mAnimator = animator;
+ }
+
+ @Override
+ public Result preAnimation() {
+ // start the animation. This will send a message to the handler right away, so
+ // the queue is filled when this method returns.
+ mAnimator.start();
+
+ return Status.SUCCESS.createResult();
+ }
+
+ @Override
+ public void postAnimation() {
+ // nothing to be done.
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
new file mode 100644
index 0000000..23d08e3
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2010 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.impl;
+
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED;
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT;
+import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
+
+import com.android.ide.common.rendering.api.HardwareConfig;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderParams;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.RenderResources.FrameworkResourceIdProvider;
+import com.android.ide.common.rendering.api.Result;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.resources.Density;
+import com.android.resources.ResourceType;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenSize;
+
+import android.content.res.Configuration;
+import android.os.HandlerThread_Delegate;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.view.ViewConfiguration_Accessor;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodManager_Accessor;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Base class for rendering action.
+ *
+ * It provides life-cycle methods to init and stop the rendering.
+ * The most important methods are:
+ * {@link #init(long)} and {@link #acquire(long)} to start a rendering and {@link #release()}
+ * after the rendering.
+ *
+ *
+ * @param <T> the {@link RenderParams} implementation
+ *
+ */
+public abstract class RenderAction<T extends RenderParams> extends FrameworkResourceIdProvider {
+
+ /**
+ * The current context being rendered. This is set through {@link #acquire(long)} and
+ * {@link #init(long)}, and unset in {@link #release()}.
+ */
+ private static BridgeContext sCurrentContext = null;
+
+ private final T mParams;
+
+ private BridgeContext mContext;
+
+ /**
+ * Creates a renderAction.
+ * <p>
+ * This <b>must</b> be followed by a call to {@link RenderAction#init()}, which act as a
+ * call to {@link RenderAction#acquire(long)}
+ *
+ * @param params the RenderParams. This must be a copy that the action can keep
+ *
+ */
+ protected RenderAction(T params) {
+ mParams = params;
+ }
+
+ /**
+ * Initializes and acquires the scene, creating various Android objects such as context,
+ * inflater, and parser.
+ *
+ * @param timeout the time to wait if another rendering is happening.
+ *
+ * @return whether the scene was prepared
+ *
+ * @see #acquire(long)
+ * @see #release()
+ */
+ public Result init(long timeout) {
+ // acquire the lock. if the result is null, lock was just acquired, otherwise, return
+ // the result.
+ Result result = acquireLock(timeout);
+ if (result != null) {
+ return result;
+ }
+
+ HardwareConfig hardwareConfig = mParams.getHardwareConfig();
+
+ // setup the display Metrics.
+ DisplayMetrics metrics = new DisplayMetrics();
+ metrics.densityDpi = metrics.noncompatDensityDpi =
+ hardwareConfig.getDensity().getDpiValue();
+
+ metrics.density = metrics.noncompatDensity =
+ metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
+
+ metrics.scaledDensity = metrics.noncompatScaledDensity = metrics.density;
+
+ metrics.widthPixels = metrics.noncompatWidthPixels = hardwareConfig.getScreenWidth();
+ metrics.heightPixels = metrics.noncompatHeightPixels = hardwareConfig.getScreenHeight();
+ metrics.xdpi = metrics.noncompatXdpi = hardwareConfig.getXdpi();
+ metrics.ydpi = metrics.noncompatYdpi = hardwareConfig.getYdpi();
+
+ RenderResources resources = mParams.getResources();
+
+ // build the context
+ mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources,
+ mParams.getProjectCallback(), getConfiguration(), mParams.getTargetSdkVersion(),
+ mParams.isRtlSupported());
+
+ setUp();
+
+ return SUCCESS.createResult();
+ }
+
+
+ /**
+ * Prepares the scene for action.
+ * <p>
+ * This call is blocking if another rendering/inflating is currently happening, and will return
+ * whether the preparation worked.
+ *
+ * The preparation can fail if another rendering took too long and the timeout was elapsed.
+ *
+ * More than one call to this from the same thread will have no effect and will return
+ * {@link Result#SUCCESS}.
+ *
+ * After scene actions have taken place, only one call to {@link #release()} must be
+ * done.
+ *
+ * @param timeout the time to wait if another rendering is happening.
+ *
+ * @return whether the scene was prepared
+ *
+ * @see #release()
+ *
+ * @throws IllegalStateException if {@link #init(long)} was never called.
+ */
+ public Result acquire(long timeout) {
+ if (mContext == null) {
+ throw new IllegalStateException("After scene creation, #init() must be called");
+ }
+
+ // acquire the lock. if the result is null, lock was just acquired, otherwise, return
+ // the result.
+ Result result = acquireLock(timeout);
+ if (result != null) {
+ return result;
+ }
+
+ setUp();
+
+ return SUCCESS.createResult();
+ }
+
+ /**
+ * Acquire the lock so that the scene can be acted upon.
+ * <p>
+ * This returns null if the lock was just acquired, otherwise it returns
+ * {@link Result#SUCCESS} if the lock already belonged to that thread, or another
+ * instance (see {@link Result#getStatus()}) if an error occurred.
+ *
+ * @param timeout the time to wait if another rendering is happening.
+ * @return null if the lock was just acquire or another result depending on the state.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene.
+ */
+ private Result acquireLock(long timeout) {
+ ReentrantLock lock = Bridge.getLock();
+ if (lock.isHeldByCurrentThread() == false) {
+ try {
+ boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
+
+ if (acquired == false) {
+ return ERROR_TIMEOUT.createResult();
+ }
+ } catch (InterruptedException e) {
+ return ERROR_LOCK_INTERRUPTED.createResult();
+ }
+ } else {
+ // This thread holds the lock already. Checks that this wasn't for a different context.
+ // If this is called by init, mContext will be null and so should sCurrentContext
+ // anyway
+ if (mContext != sCurrentContext) {
+ throw new IllegalStateException("Acquiring different scenes from same thread without releases");
+ }
+ return SUCCESS.createResult();
+ }
+
+ return null;
+ }
+
+ /**
+ * Cleans up the scene after an action.
+ */
+ public void release() {
+ ReentrantLock lock = Bridge.getLock();
+
+ // with the use of finally blocks, it is possible to find ourself calling this
+ // without a successful call to prepareScene. This test makes sure that unlock() will
+ // not throw IllegalMonitorStateException.
+ if (lock.isHeldByCurrentThread()) {
+ tearDown();
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Sets up the session for rendering.
+ * <p/>
+ * The counterpart is {@link #tearDown()}.
+ */
+ private void setUp() {
+ // make sure the Resources object references the context (and other objects) for this
+ // scene
+ mContext.initResources();
+ sCurrentContext = mContext;
+
+ // create an InputMethodManager
+ InputMethodManager.getInstance();
+
+ LayoutLog currentLog = mParams.getLog();
+ Bridge.setLog(currentLog);
+ mContext.getRenderResources().setFrameworkResourceIdProvider(this);
+ mContext.getRenderResources().setLogger(currentLog);
+ }
+
+ /**
+ * Tear down the session after rendering.
+ * <p/>
+ * The counterpart is {@link #setUp()}.
+ */
+ private void tearDown() {
+ // The context may be null, if there was an error during init().
+ if (mContext != null) {
+ // Make sure to remove static references, otherwise we could not unload the lib
+ mContext.disposeResources();
+ }
+
+ if (sCurrentContext != null) {
+ // quit HandlerThread created during this session.
+ HandlerThread_Delegate.cleanUp(sCurrentContext);
+ }
+
+ // clear the stored ViewConfiguration since the map is per density and not per context.
+ ViewConfiguration_Accessor.clearConfigurations();
+
+ // remove the InputMethodManager
+ InputMethodManager_Accessor.resetInstance();
+
+ sCurrentContext = null;
+
+ Bridge.setLog(null);
+ if (mContext != null) {
+ mContext.getRenderResources().setFrameworkResourceIdProvider(null);
+ mContext.getRenderResources().setLogger(null);
+ }
+
+ mContext = null;
+ }
+
+ public static BridgeContext getCurrentContext() {
+ return sCurrentContext;
+ }
+
+ protected T getParams() {
+ return mParams;
+ }
+
+ protected BridgeContext getContext() {
+ return mContext;
+ }
+
+ /**
+ * Returns the log associated with the session.
+ * @return the log or null if there are none.
+ */
+ public LayoutLog getLog() {
+ if (mParams != null) {
+ return mParams.getLog();
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks that the lock is owned by the current thread and that the current context is the one
+ * from this scene.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ */
+ protected void checkLock() {
+ ReentrantLock lock = Bridge.getLock();
+ if (lock.isHeldByCurrentThread() == false) {
+ throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
+ }
+ if (sCurrentContext != mContext) {
+ throw new IllegalStateException("Thread acquired a scene but is rendering a different one");
+ }
+ }
+
+ private Configuration getConfiguration() {
+ Configuration config = new Configuration();
+
+ HardwareConfig hardwareConfig = mParams.getHardwareConfig();
+
+ ScreenSize screenSize = hardwareConfig.getScreenSize();
+ if (screenSize != null) {
+ switch (screenSize) {
+ case SMALL:
+ config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_SMALL;
+ break;
+ case NORMAL:
+ config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_NORMAL;
+ break;
+ case LARGE:
+ config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_LARGE;
+ break;
+ case XLARGE:
+ config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ break;
+ }
+ }
+
+ Density density = hardwareConfig.getDensity();
+ if (density == null) {
+ density = Density.MEDIUM;
+ }
+
+ config.screenWidthDp = hardwareConfig.getScreenWidth() / density.getDpiValue();
+ config.screenHeightDp = hardwareConfig.getScreenHeight() / density.getDpiValue();
+ if (config.screenHeightDp < config.screenWidthDp) {
+ config.smallestScreenWidthDp = config.screenHeightDp;
+ } else {
+ config.smallestScreenWidthDp = config.screenWidthDp;
+ }
+ config.densityDpi = density.getDpiValue();
+
+ // never run in compat mode:
+ config.compatScreenWidthDp = config.screenWidthDp;
+ config.compatScreenHeightDp = config.screenHeightDp;
+
+ ScreenOrientation orientation = hardwareConfig.getOrientation();
+ if (orientation != null) {
+ switch (orientation) {
+ case PORTRAIT:
+ config.orientation = Configuration.ORIENTATION_PORTRAIT;
+ break;
+ case LANDSCAPE:
+ config.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ break;
+ case SQUARE:
+ config.orientation = Configuration.ORIENTATION_SQUARE;
+ break;
+ }
+ } else {
+ config.orientation = Configuration.ORIENTATION_UNDEFINED;
+ }
+
+ // TODO: fill in more config info.
+
+ return config;
+ }
+
+
+ // --- FrameworkResourceIdProvider methods
+
+ @Override
+ public Integer getId(ResourceType resType, String resName) {
+ return Bridge.getResourceId(resType, resName);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
new file mode 100644
index 0000000..b677131
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2011 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.impl;
+
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
+
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.HardwareConfig;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.Result.Status;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.resources.ResourceType;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap_Delegate;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.view.AttachInfo_Accessor;
+import android.view.View.MeasureSpec;
+import android.widget.FrameLayout;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+/**
+ * Action to render a given Drawable provided through {@link DrawableParams#getDrawable()}.
+ *
+ * The class only provides a simple {@link #render()} method, but the full life-cycle of the
+ * action must be respected.
+ *
+ * @see RenderAction
+ *
+ */
+public class RenderDrawable extends RenderAction<DrawableParams> {
+
+ public RenderDrawable(DrawableParams params) {
+ super(new DrawableParams(params));
+ }
+
+ public Result render() {
+ checkLock();
+ try {
+ // get the drawable resource value
+ DrawableParams params = getParams();
+ HardwareConfig hardwareConfig = params.getHardwareConfig();
+ ResourceValue drawableResource = params.getDrawable();
+
+ // resolve it
+ BridgeContext context = getContext();
+ drawableResource = context.getRenderResources().resolveResValue(drawableResource);
+
+ if (drawableResource == null ||
+ drawableResource.getResourceType() != ResourceType.DRAWABLE) {
+ return Status.ERROR_NOT_A_DRAWABLE.createResult();
+ }
+
+ // create a simple FrameLayout
+ FrameLayout content = new FrameLayout(context);
+
+ // get the actual Drawable object to draw
+ Drawable d = ResourceHelper.getDrawable(drawableResource, context);
+ content.setBackground(d);
+
+ // set the AttachInfo on the root view.
+ AttachInfo_Accessor.setAttachInfo(content);
+
+
+ // measure
+ int w = hardwareConfig.getScreenWidth();
+ int h = hardwareConfig.getScreenHeight();
+ int w_spec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY);
+ int h_spec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
+ content.measure(w_spec, h_spec);
+
+ // now do the layout.
+ content.layout(0, 0, w, h);
+
+ // preDraw setup
+ AttachInfo_Accessor.dispatchOnPreDraw(content);
+
+ // draw into a new image
+ BufferedImage image = getImage(w, h);
+
+ // create an Android bitmap around the BufferedImage
+ Bitmap bitmap = Bitmap_Delegate.createBitmap(image,
+ true /*isMutable*/, hardwareConfig.getDensity());
+
+ // create a Canvas around the Android bitmap
+ Canvas canvas = new Canvas(bitmap);
+ canvas.setDensity(hardwareConfig.getDensity().getDpiValue());
+
+ // and draw
+ content.draw(canvas);
+
+ return Status.SUCCESS.createResult(image);
+ } catch (IOException e) {
+ return ERROR_UNKNOWN.createResult(e.getMessage(), e);
+ }
+ }
+
+ protected BufferedImage getImage(int w, int h) {
+ BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D gc = image.createGraphics();
+ gc.setComposite(AlphaComposite.Src);
+
+ gc.setColor(new Color(0x00000000, true));
+ gc.fillRect(0, 0, w, h);
+
+ // done
+ gc.dispose();
+
+ return image;
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
new file mode 100644
index 0000000..57771e3
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -0,0 +1,1486 @@
+/*
+ * Copyright (C) 2010 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.impl;
+
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND;
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION;
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED;
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN;
+import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.HardwareConfig;
+import com.android.ide.common.rendering.api.IAnimationListener;
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.RenderParams;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.Result.Status;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.internal.util.XmlUtils;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.bars.FakeActionBar;
+import com.android.layoutlib.bridge.bars.NavigationBar;
+import com.android.layoutlib.bridge.bars.StatusBar;
+import com.android.layoutlib.bridge.bars.TitleBar;
+import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
+import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
+import com.android.resources.ResourceType;
+import com.android.resources.ScreenOrientation;
+import com.android.util.Pair;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.animation.AnimationThread;
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.LayoutTransition;
+import android.animation.LayoutTransition.TransitionListener;
+import android.app.Fragment_Delegate;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap_Delegate;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.AttachInfo_Accessor;
+import android.view.BridgeInflater;
+import android.view.IWindowManager;
+import android.view.IWindowManagerImpl;
+import android.view.Surface;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.WindowManagerGlobal_Delegate;
+import android.widget.AbsListView;
+import android.widget.AbsSpinner;
+import android.widget.AdapterView;
+import android.widget.ExpandableListView;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.QuickContactBadge;
+import android.widget.TabHost;
+import android.widget.TabHost.TabSpec;
+import android.widget.TabWidget;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class implementing the render session.
+ *
+ * A session is a stateful representation of a layout file. It is initialized with data coming
+ * through the {@link Bridge} API to inflate the layout. Further actions and rendering can then
+ * be done on the layout.
+ *
+ */
+public class RenderSessionImpl extends RenderAction<SessionParams> {
+
+ private static final int DEFAULT_TITLE_BAR_HEIGHT = 25;
+ private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
+
+ // scene state
+ private RenderSession mScene;
+ private BridgeXmlBlockParser mBlockParser;
+ private BridgeInflater mInflater;
+ private ResourceValue mWindowBackground;
+ private ViewGroup mViewRoot;
+ private FrameLayout mContentRoot;
+ private Canvas mCanvas;
+ private int mMeasuredScreenWidth = -1;
+ private int mMeasuredScreenHeight = -1;
+ private boolean mIsAlphaChannelImage;
+ private boolean mWindowIsFloating;
+
+ private int mStatusBarSize;
+ private int mNavigationBarSize;
+ private int mNavigationBarOrientation = LinearLayout.HORIZONTAL;
+ private int mTitleBarSize;
+ private int mActionBarSize;
+
+
+ // information being returned through the API
+ private BufferedImage mImage;
+ private List<ViewInfo> mViewInfoList;
+
+ private static final class PostInflateException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public PostInflateException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Creates a layout scene with all the information coming from the layout bridge API.
+ * <p>
+ * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init()}, which act as a
+ * call to {@link RenderSessionImpl#acquire(long)}
+ *
+ * @see LayoutBridge#createScene(com.android.layoutlib.api.SceneParams)
+ */
+ public RenderSessionImpl(SessionParams params) {
+ super(new SessionParams(params));
+ }
+
+ /**
+ * Initializes and acquires the scene, creating various Android objects such as context,
+ * inflater, and parser.
+ *
+ * @param timeout the time to wait if another rendering is happening.
+ *
+ * @return whether the scene was prepared
+ *
+ * @see #acquire(long)
+ * @see #release()
+ */
+ @Override
+ public Result init(long timeout) {
+ Result result = super.init(timeout);
+ if (result.isSuccess() == false) {
+ return result;
+ }
+
+ SessionParams params = getParams();
+ BridgeContext context = getContext();
+
+ RenderResources resources = getParams().getResources();
+ DisplayMetrics metrics = getContext().getMetrics();
+
+ // use default of true in case it's not found to use alpha by default
+ mIsAlphaChannelImage = getBooleanThemeValue(resources,
+ "windowIsFloating", true /*defaultValue*/);
+
+ mWindowIsFloating = getBooleanThemeValue(resources, "windowIsFloating",
+ true /*defaultValue*/);
+
+ findBackground(resources);
+ findStatusBar(resources, metrics);
+ findActionBar(resources, metrics);
+ findNavigationBar(resources, metrics);
+
+ // FIXME: find those out, and possibly add them to the render params
+ boolean hasNavigationBar = true;
+ IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(),
+ metrics, Surface.ROTATION_0,
+ hasNavigationBar);
+ WindowManagerGlobal_Delegate.setWindowManagerService(iwm);
+
+ // build the inflater and parser.
+ mInflater = new BridgeInflater(context, params.getProjectCallback());
+ context.setBridgeInflater(mInflater);
+
+ mBlockParser = new BridgeXmlBlockParser(
+ params.getLayoutDescription(), context, false /* platformResourceFlag */);
+
+ return SUCCESS.createResult();
+ }
+
+ /**
+ * Inflates the layout.
+ * <p>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #init(long)} was not called.
+ */
+ public Result inflate() {
+ checkLock();
+
+ try {
+
+ SessionParams params = getParams();
+ HardwareConfig hardwareConfig = params.getHardwareConfig();
+ BridgeContext context = getContext();
+ boolean isRtl = Bridge.isLocaleRtl(params.getLocale());
+ int direction = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
+
+ // the view group that receives the window background.
+ ViewGroup backgroundView = null;
+
+ if (mWindowIsFloating || params.isForceNoDecor()) {
+ backgroundView = mViewRoot = mContentRoot = new FrameLayout(context);
+ mViewRoot.setLayoutDirection(direction);
+ } else {
+ if (hasSoftwareButtons() && mNavigationBarOrientation == LinearLayout.VERTICAL) {
+ /*
+ * This is a special case where the navigation bar is on the right.
+ +-------------------------------------------------+---+
+ | Status bar (always) | |
+ +-------------------------------------------------+ |
+ | (Layout with background drawable) | |
+ | +---------------------------------------------+ | |
+ | | Title/Action bar (optional) | | |
+ | +---------------------------------------------+ | |
+ | | Content, vertical extending | | |
+ | | | | |
+ | +---------------------------------------------+ | |
+ +-------------------------------------------------+---+
+
+ So we create a horizontal layout, with the nav bar on the right,
+ and the left part is the normal layout below without the nav bar at
+ the bottom
+ */
+ LinearLayout topLayout = new LinearLayout(context);
+ topLayout.setLayoutDirection(direction);
+ mViewRoot = topLayout;
+ topLayout.setOrientation(LinearLayout.HORIZONTAL);
+
+ try {
+ NavigationBar navigationBar = new NavigationBar(context,
+ hardwareConfig.getDensity(), LinearLayout.VERTICAL, isRtl,
+ params.isRtlSupported());
+ navigationBar.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ mNavigationBarSize,
+ LayoutParams.MATCH_PARENT));
+ topLayout.addView(navigationBar);
+ } catch (XmlPullParserException e) {
+
+ }
+ }
+
+ /*
+ * we're creating the following layout
+ *
+ +-------------------------------------------------+
+ | Status bar (always) |
+ +-------------------------------------------------+
+ | (Layout with background drawable) |
+ | +---------------------------------------------+ |
+ | | Title/Action bar (optional) | |
+ | +---------------------------------------------+ |
+ | | Content, vertical extending | |
+ | | | |
+ | +---------------------------------------------+ |
+ +-------------------------------------------------+
+ | Navigation bar for soft buttons, maybe see above|
+ +-------------------------------------------------+
+
+ */
+
+ LinearLayout topLayout = new LinearLayout(context);
+ topLayout.setOrientation(LinearLayout.VERTICAL);
+ topLayout.setLayoutDirection(direction);
+ // if we don't already have a view root this is it
+ if (mViewRoot == null) {
+ mViewRoot = topLayout;
+ } else {
+ LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ layoutParams.weight = 1;
+ topLayout.setLayoutParams(layoutParams);
+
+ // this is the case of soft buttons + vertical bar.
+ // this top layout is the first layout in the horizontal layout. see above)
+ if (isRtl && params.isRtlSupported()) {
+ // If RTL is enabled, layoutlib will mirror the layouts. So, add the
+ // topLayout to the right of Navigation Bar and layoutlib will draw it
+ // to the left.
+ mViewRoot.addView(topLayout);
+ } else {
+ // Add the top layout to the left of the Navigation Bar.
+ mViewRoot.addView(topLayout, 0);
+ }
+ }
+
+ if (mStatusBarSize > 0) {
+ // system bar
+ try {
+ StatusBar systemBar = new StatusBar(context, hardwareConfig.getDensity(),
+ direction, params.isRtlSupported());
+ systemBar.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, mStatusBarSize));
+ topLayout.addView(systemBar);
+ } catch (XmlPullParserException e) {
+
+ }
+ }
+
+ LinearLayout backgroundLayout = new LinearLayout(context);
+ backgroundView = backgroundLayout;
+ backgroundLayout.setOrientation(LinearLayout.VERTICAL);
+ LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ layoutParams.weight = 1;
+ backgroundLayout.setLayoutParams(layoutParams);
+ topLayout.addView(backgroundLayout);
+
+
+ // if the theme says no title/action bar, then the size will be 0
+ if (mActionBarSize > 0) {
+ try {
+ FakeActionBar actionBar = new FakeActionBar(context,
+ hardwareConfig.getDensity(),
+ params.getAppLabel(), params.getAppIcon());
+ actionBar.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, mActionBarSize));
+ backgroundLayout.addView(actionBar);
+ } catch (XmlPullParserException e) {
+
+ }
+ } else if (mTitleBarSize > 0) {
+ try {
+ TitleBar titleBar = new TitleBar(context,
+ hardwareConfig.getDensity(), params.getAppLabel());
+ titleBar.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, mTitleBarSize));
+ backgroundLayout.addView(titleBar);
+ } catch (XmlPullParserException e) {
+
+ }
+ }
+
+ // content frame
+ mContentRoot = new FrameLayout(context);
+ layoutParams = new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ layoutParams.weight = 1;
+ mContentRoot.setLayoutParams(layoutParams);
+ backgroundLayout.addView(mContentRoot);
+
+ if (mNavigationBarOrientation == LinearLayout.HORIZONTAL &&
+ mNavigationBarSize > 0) {
+ // system bar
+ try {
+ NavigationBar navigationBar = new NavigationBar(context,
+ hardwareConfig.getDensity(), LinearLayout.HORIZONTAL, isRtl,
+ params.isRtlSupported());
+ navigationBar.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, mNavigationBarSize));
+ topLayout.addView(navigationBar);
+ } catch (XmlPullParserException e) {
+
+ }
+ }
+ }
+
+
+ // Sets the project callback (custom view loader) to the fragment delegate so that
+ // it can instantiate the custom Fragment.
+ Fragment_Delegate.setProjectCallback(params.getProjectCallback());
+
+ View view = mInflater.inflate(mBlockParser, mContentRoot);
+
+ // done with the parser, pop it.
+ context.popParser();
+
+ Fragment_Delegate.setProjectCallback(null);
+
+ // set the AttachInfo on the root view.
+ AttachInfo_Accessor.setAttachInfo(mViewRoot);
+
+ // post-inflate process. For now this supports TabHost/TabWidget
+ postInflateProcess(view, params.getProjectCallback());
+
+ // get the background drawable
+ if (mWindowBackground != null && backgroundView != null) {
+ Drawable d = ResourceHelper.getDrawable(mWindowBackground, context);
+ backgroundView.setBackground(d);
+ }
+
+ return SUCCESS.createResult();
+ } catch (PostInflateException e) {
+ return ERROR_INFLATION.createResult(e.getMessage(), e);
+ } catch (Throwable e) {
+ // get the real cause of the exception.
+ Throwable t = e;
+ while (t.getCause() != null) {
+ t = t.getCause();
+ }
+
+ return ERROR_INFLATION.createResult(t.getMessage(), t);
+ }
+ }
+
+ /**
+ * Renders the scene.
+ * <p>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @param freshRender whether the render is a new one and should erase the existing bitmap (in
+ * the case where bitmaps are reused). This is typically needed when not playing
+ * animations.)
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ *
+ * @see RenderParams#getRenderingMode()
+ * @see RenderSession#render(long)
+ */
+ public Result render(boolean freshRender) {
+ checkLock();
+
+ SessionParams params = getParams();
+
+ try {
+ if (mViewRoot == null) {
+ return ERROR_NOT_INFLATED.createResult();
+ }
+
+ RenderingMode renderingMode = params.getRenderingMode();
+ HardwareConfig hardwareConfig = params.getHardwareConfig();
+
+ // only do the screen measure when needed.
+ boolean newRenderSize = false;
+ if (mMeasuredScreenWidth == -1) {
+ newRenderSize = true;
+ mMeasuredScreenWidth = hardwareConfig.getScreenWidth();
+ mMeasuredScreenHeight = hardwareConfig.getScreenHeight();
+
+ if (renderingMode != RenderingMode.NORMAL) {
+ int widthMeasureSpecMode = renderingMode.isHorizExpand() ?
+ MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
+ : MeasureSpec.EXACTLY;
+ int heightMeasureSpecMode = renderingMode.isVertExpand() ?
+ MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
+ : MeasureSpec.EXACTLY;
+
+ // We used to compare the measured size of the content to the screen size but
+ // this does not work anymore due to the 2 following issues:
+ // - If the content is in a decor (system bar, title/action bar), the root view
+ // will not resize even with the UNSPECIFIED because of the embedded layout.
+ // - If there is no decor, but a dialog frame, then the dialog padding prevents
+ // comparing the size of the content to the screen frame (as it would not
+ // take into account the dialog padding).
+
+ // The solution is to first get the content size in a normal rendering, inside
+ // the decor or the dialog padding.
+ // Then measure only the content with UNSPECIFIED to see the size difference
+ // and apply this to the screen size.
+
+ // first measure the full layout, with EXACTLY to get the size of the
+ // content as it is inside the decor/dialog
+ Pair<Integer, Integer> exactMeasure = measureView(
+ mViewRoot, mContentRoot.getChildAt(0),
+ mMeasuredScreenWidth, MeasureSpec.EXACTLY,
+ mMeasuredScreenHeight, MeasureSpec.EXACTLY);
+
+ // now measure the content only using UNSPECIFIED (where applicable, based on
+ // the rendering mode). This will give us the size the content needs.
+ Pair<Integer, Integer> result = measureView(
+ mContentRoot, mContentRoot.getChildAt(0),
+ mMeasuredScreenWidth, widthMeasureSpecMode,
+ mMeasuredScreenHeight, heightMeasureSpecMode);
+
+ // now look at the difference and add what is needed.
+ if (renderingMode.isHorizExpand()) {
+ int measuredWidth = exactMeasure.getFirst();
+ int neededWidth = result.getFirst();
+ if (neededWidth > measuredWidth) {
+ mMeasuredScreenWidth += neededWidth - measuredWidth;
+ }
+ }
+
+ if (renderingMode.isVertExpand()) {
+ int measuredHeight = exactMeasure.getSecond();
+ int neededHeight = result.getSecond();
+ if (neededHeight > measuredHeight) {
+ mMeasuredScreenHeight += neededHeight - measuredHeight;
+ }
+ }
+ }
+ }
+
+ // measure again with the size we need
+ // This must always be done before the call to layout
+ measureView(mViewRoot, null /*measuredView*/,
+ mMeasuredScreenWidth, MeasureSpec.EXACTLY,
+ mMeasuredScreenHeight, MeasureSpec.EXACTLY);
+
+ // now do the layout.
+ mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
+
+ if (params.isLayoutOnly()) {
+ // delete the canvas and image to reset them on the next full rendering
+ mImage = null;
+ mCanvas = null;
+ } else {
+ AttachInfo_Accessor.dispatchOnPreDraw(mViewRoot);
+
+ // draw the views
+ // create the BufferedImage into which the layout will be rendered.
+ boolean newImage = false;
+ if (newRenderSize || mCanvas == null) {
+ if (params.getImageFactory() != null) {
+ mImage = params.getImageFactory().getImage(
+ mMeasuredScreenWidth,
+ mMeasuredScreenHeight);
+ } else {
+ mImage = new BufferedImage(
+ mMeasuredScreenWidth,
+ mMeasuredScreenHeight,
+ BufferedImage.TYPE_INT_ARGB);
+ newImage = true;
+ }
+
+ if (params.isBgColorOverridden()) {
+ // since we override the content, it's the same as if it was a new image.
+ newImage = true;
+ Graphics2D gc = mImage.createGraphics();
+ gc.setColor(new Color(params.getOverrideBgColor(), true));
+ gc.setComposite(AlphaComposite.Src);
+ gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
+ gc.dispose();
+ }
+
+ // create an Android bitmap around the BufferedImage
+ Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage,
+ true /*isMutable*/, hardwareConfig.getDensity());
+
+ // create a Canvas around the Android bitmap
+ mCanvas = new Canvas(bitmap);
+ mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
+ }
+
+ if (freshRender && newImage == false) {
+ Graphics2D gc = mImage.createGraphics();
+ gc.setComposite(AlphaComposite.Src);
+
+ gc.setColor(new Color(0x00000000, true));
+ gc.fillRect(0, 0,
+ mMeasuredScreenWidth, mMeasuredScreenHeight);
+
+ // done
+ gc.dispose();
+ }
+
+ mViewRoot.draw(mCanvas);
+ }
+
+ mViewInfoList = startVisitingViews(mViewRoot, 0, params.getExtendedViewInfoMode());
+
+ // success!
+ return SUCCESS.createResult();
+ } catch (Throwable e) {
+ // get the real cause of the exception.
+ Throwable t = e;
+ while (t.getCause() != null) {
+ t = t.getCause();
+ }
+
+ return ERROR_UNKNOWN.createResult(t.getMessage(), t);
+ }
+ }
+
+ /**
+ * Executes {@link View#measure(int, int)} on a given view with the given parameters (used
+ * to create measure specs with {@link MeasureSpec#makeMeasureSpec(int, int)}.
+ *
+ * if <var>measuredView</var> is non null, the method returns a {@link Pair} of (width, height)
+ * for the view (using {@link View#getMeasuredWidth()} and {@link View#getMeasuredHeight()}).
+ *
+ * @param viewToMeasure the view on which to execute measure().
+ * @param measuredView if non null, the view to query for its measured width/height.
+ * @param width the width to use in the MeasureSpec.
+ * @param widthMode the MeasureSpec mode to use for the width.
+ * @param height the height to use in the MeasureSpec.
+ * @param heightMode the MeasureSpec mode to use for the height.
+ * @return the measured width/height if measuredView is non-null, null otherwise.
+ */
+ private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
+ int width, int widthMode, int height, int heightMode) {
+ int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
+ int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode);
+ viewToMeasure.measure(w_spec, h_spec);
+
+ if (measuredView != null) {
+ return Pair.of(measuredView.getMeasuredWidth(), measuredView.getMeasuredHeight());
+ }
+
+ return null;
+ }
+
+ /**
+ * Animate an object
+ * <p>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ *
+ * @see RenderSession#animate(Object, String, boolean, IAnimationListener)
+ */
+ public Result animate(Object targetObject, String animationName,
+ boolean isFrameworkAnimation, IAnimationListener listener) {
+ checkLock();
+
+ BridgeContext context = getContext();
+
+ // find the animation file.
+ ResourceValue animationResource = null;
+ int animationId = 0;
+ if (isFrameworkAnimation) {
+ animationResource = context.getRenderResources().getFrameworkResource(
+ ResourceType.ANIMATOR, animationName);
+ if (animationResource != null) {
+ animationId = Bridge.getResourceId(ResourceType.ANIMATOR, animationName);
+ }
+ } else {
+ animationResource = context.getRenderResources().getProjectResource(
+ ResourceType.ANIMATOR, animationName);
+ if (animationResource != null) {
+ animationId = context.getProjectCallback().getResourceId(
+ ResourceType.ANIMATOR, animationName);
+ }
+ }
+
+ if (animationResource != null) {
+ try {
+ Animator anim = AnimatorInflater.loadAnimator(context, animationId);
+ if (anim != null) {
+ anim.setTarget(targetObject);
+
+ new PlayAnimationThread(anim, this, animationName, listener).start();
+
+ return SUCCESS.createResult();
+ }
+ } catch (Exception e) {
+ // get the real cause of the exception.
+ Throwable t = e;
+ while (t.getCause() != null) {
+ t = t.getCause();
+ }
+
+ return ERROR_UNKNOWN.createResult(t.getMessage(), t);
+ }
+ }
+
+ return ERROR_ANIM_NOT_FOUND.createResult();
+ }
+
+ /**
+ * Insert a new child into an existing parent.
+ * <p>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ *
+ * @see RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)
+ */
+ public Result insertChild(final ViewGroup parentView, ILayoutPullParser childXml,
+ final int index, IAnimationListener listener) {
+ checkLock();
+
+ BridgeContext context = getContext();
+
+ // create a block parser for the XML
+ BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
+ childXml, context, false /* platformResourceFlag */);
+
+ // inflate the child without adding it to the root since we want to control where it'll
+ // get added. We do pass the parentView however to ensure that the layoutParams will
+ // be created correctly.
+ final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/);
+ blockParser.ensurePopped();
+
+ invalidateRenderingSize();
+
+ if (listener != null) {
+ new AnimationThread(this, "insertChild", listener) {
+
+ @Override
+ public Result preAnimation() {
+ parentView.setLayoutTransition(new LayoutTransition());
+ return addView(parentView, child, index);
+ }
+
+ @Override
+ public void postAnimation() {
+ parentView.setLayoutTransition(null);
+ }
+ }.start();
+
+ // always return success since the real status will come through the listener.
+ return SUCCESS.createResult(child);
+ }
+
+ // add it to the parentView in the correct location
+ Result result = addView(parentView, child, index);
+ if (result.isSuccess() == false) {
+ return result;
+ }
+
+ result = render(false /*freshRender*/);
+ if (result.isSuccess()) {
+ result = result.getCopyWithData(child);
+ }
+
+ return result;
+ }
+
+ /**
+ * Adds a given view to a given parent at a given index.
+ *
+ * @param parent the parent to receive the view
+ * @param view the view to add to the parent
+ * @param index the index where to do the add.
+ *
+ * @return a Result with {@link Status#SUCCESS} or
+ * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
+ * adding views.
+ */
+ private Result addView(ViewGroup parent, View view, int index) {
+ try {
+ parent.addView(view, index);
+ return SUCCESS.createResult();
+ } catch (UnsupportedOperationException e) {
+ // looks like this is a view class that doesn't support children manipulation!
+ return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
+ }
+ }
+
+ /**
+ * Moves a view to a new parent at a given location
+ * <p>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ *
+ * @see RenderSession#moveChild(Object, Object, int, Map, IAnimationListener)
+ */
+ public Result moveChild(final ViewGroup newParentView, final View childView, final int index,
+ Map<String, String> layoutParamsMap, final IAnimationListener listener) {
+ checkLock();
+
+ invalidateRenderingSize();
+
+ LayoutParams layoutParams = null;
+ if (layoutParamsMap != null) {
+ // need to create a new LayoutParams object for the new parent.
+ layoutParams = newParentView.generateLayoutParams(
+ new BridgeLayoutParamsMapAttributes(layoutParamsMap));
+ }
+
+ // get the current parent of the view that needs to be moved.
+ final ViewGroup previousParent = (ViewGroup) childView.getParent();
+
+ if (listener != null) {
+ final LayoutParams params = layoutParams;
+
+ // there is no support for animating views across layouts, so in case the new and old
+ // parent views are different we fake the animation through a no animation thread.
+ if (previousParent != newParentView) {
+ new Thread("not animated moveChild") {
+ @Override
+ public void run() {
+ Result result = moveView(previousParent, newParentView, childView, index,
+ params);
+ if (result.isSuccess() == false) {
+ listener.done(result);
+ }
+
+ // ready to do the work, acquire the scene.
+ result = acquire(250);
+ if (result.isSuccess() == false) {
+ listener.done(result);
+ return;
+ }
+
+ try {
+ result = render(false /*freshRender*/);
+ if (result.isSuccess()) {
+ listener.onNewFrame(RenderSessionImpl.this.getSession());
+ }
+ } finally {
+ release();
+ }
+
+ listener.done(result);
+ }
+ }.start();
+ } else {
+ new AnimationThread(this, "moveChild", listener) {
+
+ @Override
+ public Result preAnimation() {
+ // set up the transition for the parent.
+ LayoutTransition transition = new LayoutTransition();
+ previousParent.setLayoutTransition(transition);
+
+ // tweak the animation durations and start delays (to match the duration of
+ // animation playing just before).
+ // Note: Cannot user Animation.setDuration() directly. Have to set it
+ // on the LayoutTransition.
+ transition.setDuration(LayoutTransition.DISAPPEARING, 100);
+ // CHANGE_DISAPPEARING plays after DISAPPEARING
+ transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100);
+
+ transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100);
+
+ transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100);
+ // CHANGE_APPEARING plays after CHANGE_APPEARING
+ transition.setStartDelay(LayoutTransition.APPEARING, 100);
+
+ transition.setDuration(LayoutTransition.APPEARING, 100);
+
+ return moveView(previousParent, newParentView, childView, index, params);
+ }
+
+ @Override
+ public void postAnimation() {
+ previousParent.setLayoutTransition(null);
+ newParentView.setLayoutTransition(null);
+ }
+ }.start();
+ }
+
+ // always return success since the real status will come through the listener.
+ return SUCCESS.createResult(layoutParams);
+ }
+
+ Result result = moveView(previousParent, newParentView, childView, index, layoutParams);
+ if (result.isSuccess() == false) {
+ return result;
+ }
+
+ result = render(false /*freshRender*/);
+ if (layoutParams != null && result.isSuccess()) {
+ result = result.getCopyWithData(layoutParams);
+ }
+
+ return result;
+ }
+
+ /**
+ * Moves a View from its current parent to a new given parent at a new given location, with
+ * an optional new {@link LayoutParams} instance
+ *
+ * @param previousParent the previous parent, still owning the child at the time of the call.
+ * @param newParent the new parent
+ * @param movedView the view to move
+ * @param index the new location in the new parent
+ * @param params an option (can be null) {@link LayoutParams} instance.
+ *
+ * @return a Result with {@link Status#SUCCESS} or
+ * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
+ * adding views.
+ */
+ private Result moveView(ViewGroup previousParent, final ViewGroup newParent,
+ final View movedView, final int index, final LayoutParams params) {
+ try {
+ // check if there is a transition on the previousParent.
+ LayoutTransition previousTransition = previousParent.getLayoutTransition();
+ if (previousTransition != null) {
+ // in this case there is an animation. This means we have to wait for the child's
+ // parent reference to be null'ed out so that we can add it to the new parent.
+ // It is technically removed right before the DISAPPEARING animation is done (if
+ // the animation of this type is not null, otherwise it's after which is impossible
+ // to handle).
+ // Because there is no move animation, if the new parent is the same as the old
+ // parent, we need to wait until the CHANGE_DISAPPEARING animation is done before
+ // adding the child or the child will appear in its new location before the
+ // other children have made room for it.
+
+ // add a listener to the transition to be notified of the actual removal.
+ previousTransition.addTransitionListener(new TransitionListener() {
+ private int mChangeDisappearingCount = 0;
+
+ @Override
+ public void startTransition(LayoutTransition transition, ViewGroup container,
+ View view, int transitionType) {
+ if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
+ mChangeDisappearingCount++;
+ }
+ }
+
+ @Override
+ public void endTransition(LayoutTransition transition, ViewGroup container,
+ View view, int transitionType) {
+ if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
+ mChangeDisappearingCount--;
+ }
+
+ if (transitionType == LayoutTransition.CHANGE_DISAPPEARING &&
+ mChangeDisappearingCount == 0) {
+ // add it to the parentView in the correct location
+ if (params != null) {
+ newParent.addView(movedView, index, params);
+ } else {
+ newParent.addView(movedView, index);
+ }
+ }
+ }
+ });
+
+ // remove the view from the current parent.
+ previousParent.removeView(movedView);
+
+ // and return since adding the view to the new parent is done in the listener.
+ return SUCCESS.createResult();
+ } else {
+ // standard code with no animation. pretty simple.
+ previousParent.removeView(movedView);
+
+ // add it to the parentView in the correct location
+ if (params != null) {
+ newParent.addView(movedView, index, params);
+ } else {
+ newParent.addView(movedView, index);
+ }
+
+ return SUCCESS.createResult();
+ }
+ } catch (UnsupportedOperationException e) {
+ // looks like this is a view class that doesn't support children manipulation!
+ return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
+ }
+ }
+
+ /**
+ * Removes a child from its current parent.
+ * <p>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ *
+ * @see RenderSession#removeChild(Object, IAnimationListener)
+ */
+ public Result removeChild(final View childView, IAnimationListener listener) {
+ checkLock();
+
+ invalidateRenderingSize();
+
+ final ViewGroup parent = (ViewGroup) childView.getParent();
+
+ if (listener != null) {
+ new AnimationThread(this, "moveChild", listener) {
+
+ @Override
+ public Result preAnimation() {
+ parent.setLayoutTransition(new LayoutTransition());
+ return removeView(parent, childView);
+ }
+
+ @Override
+ public void postAnimation() {
+ parent.setLayoutTransition(null);
+ }
+ }.start();
+
+ // always return success since the real status will come through the listener.
+ return SUCCESS.createResult();
+ }
+
+ Result result = removeView(parent, childView);
+ if (result.isSuccess() == false) {
+ return result;
+ }
+
+ return render(false /*freshRender*/);
+ }
+
+ /**
+ * Removes a given view from its current parent.
+ *
+ * @param view the view to remove from its parent
+ *
+ * @return a Result with {@link Status#SUCCESS} or
+ * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
+ * adding views.
+ */
+ private Result removeView(ViewGroup parent, View view) {
+ try {
+ parent.removeView(view);
+ return SUCCESS.createResult();
+ } catch (UnsupportedOperationException e) {
+ // looks like this is a view class that doesn't support children manipulation!
+ return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
+ }
+ }
+
+
+ private void findBackground(RenderResources resources) {
+ if (getParams().isBgColorOverridden() == false) {
+ mWindowBackground = resources.findItemInTheme("windowBackground",
+ true /*isFrameworkAttr*/);
+ if (mWindowBackground != null) {
+ mWindowBackground = resources.resolveResValue(mWindowBackground);
+ }
+ }
+ }
+
+ private boolean hasSoftwareButtons() {
+ return getParams().getHardwareConfig().hasSoftwareButtons();
+ }
+
+ private void findStatusBar(RenderResources resources, DisplayMetrics metrics) {
+ boolean windowFullscreen = getBooleanThemeValue(resources,
+ "windowFullscreen", false /*defaultValue*/);
+
+ if (windowFullscreen == false && mWindowIsFloating == false) {
+ // default value
+ mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT;
+
+ // get the real value
+ ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN,
+ "status_bar_height");
+
+ if (value != null) {
+ TypedValue typedValue = ResourceHelper.getValue("status_bar_height",
+ value.getValue(), true /*requireUnit*/);
+ if (typedValue != null) {
+ // compute the pixel value based on the display metrics
+ mStatusBarSize = (int)typedValue.getDimension(metrics);
+ }
+ }
+ }
+ }
+
+ private void findActionBar(RenderResources resources, DisplayMetrics metrics) {
+ if (mWindowIsFloating) {
+ return;
+ }
+
+ boolean windowActionBar = getBooleanThemeValue(resources,
+ "windowActionBar", true /*defaultValue*/);
+
+ // if there's a value and it's false (default is true)
+ if (windowActionBar) {
+
+ // default size of the window title bar
+ mActionBarSize = DEFAULT_TITLE_BAR_HEIGHT;
+
+ // get value from the theme.
+ ResourceValue value = resources.findItemInTheme("actionBarSize",
+ true /*isFrameworkAttr*/);
+
+ // resolve it
+ value = resources.resolveResValue(value);
+
+ if (value != null) {
+ // get the numerical value, if available
+ TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
+ true /*requireUnit*/);
+ if (typedValue != null) {
+ // compute the pixel value based on the display metrics
+ mActionBarSize = (int)typedValue.getDimension(metrics);
+ }
+ }
+ } else {
+ // action bar overrides title bar so only look for this one if action bar is hidden
+ boolean windowNoTitle = getBooleanThemeValue(resources,
+ "windowNoTitle", false /*defaultValue*/);
+
+ if (windowNoTitle == false) {
+
+ // default size of the window title bar
+ mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT;
+
+ // get value from the theme.
+ ResourceValue value = resources.findItemInTheme("windowTitleSize",
+ true /*isFrameworkAttr*/);
+
+ // resolve it
+ value = resources.resolveResValue(value);
+
+ if (value != null) {
+ // get the numerical value, if available
+ TypedValue typedValue = ResourceHelper.getValue("windowTitleSize",
+ value.getValue(), true /*requireUnit*/);
+ if (typedValue != null) {
+ // compute the pixel value based on the display metrics
+ mTitleBarSize = (int)typedValue.getDimension(metrics);
+ }
+ }
+ }
+
+ }
+ }
+
+ private void findNavigationBar(RenderResources resources, DisplayMetrics metrics) {
+ if (hasSoftwareButtons() && mWindowIsFloating == false) {
+
+ // default value
+ mNavigationBarSize = 48; // ??
+
+ HardwareConfig hardwareConfig = getParams().getHardwareConfig();
+
+ boolean barOnBottom = true;
+
+ if (hardwareConfig.getOrientation() == ScreenOrientation.LANDSCAPE) {
+ // compute the dp of the screen.
+ int shortSize = hardwareConfig.getScreenHeight();
+
+ // compute in dp
+ int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / hardwareConfig.getDensity().getDpiValue();
+
+ if (shortSizeDp < 600) {
+ // 0-599dp: "phone" UI with bar on the side
+ barOnBottom = false;
+ } else {
+ // 600+dp: "tablet" UI with bar on the bottom
+ barOnBottom = true;
+ }
+ }
+
+ if (barOnBottom) {
+ mNavigationBarOrientation = LinearLayout.HORIZONTAL;
+ } else {
+ mNavigationBarOrientation = LinearLayout.VERTICAL;
+ }
+
+ // get the real value
+ ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN,
+ barOnBottom ? "navigation_bar_height" : "navigation_bar_width");
+
+ if (value != null) {
+ TypedValue typedValue = ResourceHelper.getValue("navigation_bar_height",
+ value.getValue(), true /*requireUnit*/);
+ if (typedValue != null) {
+ // compute the pixel value based on the display metrics
+ mNavigationBarSize = (int)typedValue.getDimension(metrics);
+ }
+ }
+ }
+ }
+
+ /**
+ * Looks for a attribute in the current theme. The attribute is in the android
+ * namespace.
+ *
+ * @param resources the render resources
+ * @param name the name of the attribute
+ * @param defaultValue the default value.
+ * @return the value of the attribute or the default one if not found.
+ */
+ private boolean getBooleanThemeValue(RenderResources resources,
+ String name, boolean defaultValue) {
+
+ // get the title bar flag from the current theme.
+ ResourceValue value = resources.findItemInTheme(name, true /*isFrameworkAttr*/);
+
+ // because it may reference something else, we resolve it.
+ value = resources.resolveResValue(value);
+
+ // if there's no value, return the default.
+ if (value == null || value.getValue() == null) {
+ return defaultValue;
+ }
+
+ return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue);
+ }
+
+ /**
+ * Post process on a view hierachy that was just inflated.
+ * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the
+ * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
+ * based on the content of the {@link FrameLayout}.
+ * @param view the root view to process.
+ * @param projectCallback callback to the project.
+ */
+ private void postInflateProcess(View view, IProjectCallback projectCallback)
+ throws PostInflateException {
+ if (view instanceof TabHost) {
+ setupTabHost((TabHost)view, projectCallback);
+ } else if (view instanceof QuickContactBadge) {
+ QuickContactBadge badge = (QuickContactBadge) view;
+ badge.setImageToDefault();
+ } else if (view instanceof AdapterView<?>) {
+ // get the view ID.
+ int id = view.getId();
+
+ BridgeContext context = getContext();
+
+ // get a ResourceReference from the integer ID.
+ ResourceReference listRef = context.resolveId(id);
+
+ if (listRef != null) {
+ SessionParams params = getParams();
+ AdapterBinding binding = params.getAdapterBindings().get(listRef);
+
+ // if there was no adapter binding, trying to get it from the call back.
+ if (binding == null) {
+ binding = params.getProjectCallback().getAdapterBinding(listRef,
+ context.getViewKey(view), view);
+ }
+
+ if (binding != null) {
+
+ if (view instanceof AbsListView) {
+ if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) &&
+ view instanceof ListView) {
+ ListView list = (ListView) view;
+
+ boolean skipCallbackParser = false;
+
+ int count = binding.getHeaderCount();
+ for (int i = 0 ; i < count ; i++) {
+ Pair<View, Boolean> pair = context.inflateView(
+ binding.getHeaderAt(i),
+ list, false /*attachToRoot*/, skipCallbackParser);
+ if (pair.getFirst() != null) {
+ list.addHeaderView(pair.getFirst());
+ }
+
+ skipCallbackParser |= pair.getSecond();
+ }
+
+ count = binding.getFooterCount();
+ for (int i = 0 ; i < count ; i++) {
+ Pair<View, Boolean> pair = context.inflateView(
+ binding.getFooterAt(i),
+ list, false /*attachToRoot*/, skipCallbackParser);
+ if (pair.getFirst() != null) {
+ list.addFooterView(pair.getFirst());
+ }
+
+ skipCallbackParser |= pair.getSecond();
+ }
+ }
+
+ if (view instanceof ExpandableListView) {
+ ((ExpandableListView) view).setAdapter(
+ new FakeExpandableAdapter(
+ listRef, binding, params.getProjectCallback()));
+ } else {
+ ((AbsListView) view).setAdapter(
+ new FakeAdapter(
+ listRef, binding, params.getProjectCallback()));
+ }
+ } else if (view instanceof AbsSpinner) {
+ ((AbsSpinner) view).setAdapter(
+ new FakeAdapter(
+ listRef, binding, params.getProjectCallback()));
+ }
+ }
+ }
+ } else if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup)view;
+ final int count = group.getChildCount();
+ for (int c = 0 ; c < count ; c++) {
+ View child = group.getChildAt(c);
+ postInflateProcess(child, projectCallback);
+ }
+ }
+ }
+
+ /**
+ * Sets up a {@link TabHost} object.
+ * @param tabHost the TabHost to setup.
+ * @param projectCallback The project callback object to access the project R class.
+ * @throws PostInflateException
+ */
+ private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback)
+ throws PostInflateException {
+ // look for the TabWidget, and the FrameLayout. They have their own specific names
+ View v = tabHost.findViewById(android.R.id.tabs);
+
+ if (v == null) {
+ throw new PostInflateException(
+ "TabHost requires a TabWidget with id \"android:id/tabs\".\n");
+ }
+
+ if ((v instanceof TabWidget) == false) {
+ throw new PostInflateException(String.format(
+ "TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
+ "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
+ }
+
+ v = tabHost.findViewById(android.R.id.tabcontent);
+
+ if (v == null) {
+ // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty)
+ throw new PostInflateException(
+ "TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
+ }
+
+ if ((v instanceof FrameLayout) == false) {
+ throw new PostInflateException(String.format(
+ "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
+ "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
+ }
+
+ FrameLayout content = (FrameLayout)v;
+
+ // now process the content of the framelayout and dynamically create tabs for it.
+ final int count = content.getChildCount();
+
+ // this must be called before addTab() so that the TabHost searches its TabWidget
+ // and FrameLayout.
+ tabHost.setup();
+
+ if (count == 0) {
+ // Create a dummy child to get a single tab
+ TabSpec spec = tabHost.newTabSpec("tag").setIndicator("Tab Label",
+ tabHost.getResources().getDrawable(android.R.drawable.ic_menu_info_details))
+ .setContent(new TabHost.TabContentFactory() {
+ @Override
+ public View createTabContent(String tag) {
+ return new LinearLayout(getContext());
+ }
+ });
+ tabHost.addTab(spec);
+ return;
+ } else {
+ // for each child of the framelayout, add a new TabSpec
+ for (int i = 0 ; i < count ; i++) {
+ View child = content.getChildAt(i);
+ String tabSpec = String.format("tab_spec%d", i+1);
+ int id = child.getId();
+ Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id);
+ String name;
+ if (resource != null) {
+ name = resource.getSecond();
+ } else {
+ name = String.format("Tab %d", i+1); // default name if id is unresolved.
+ }
+ tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id));
+ }
+ }
+ }
+
+ private List<ViewInfo> startVisitingViews(View view, int offset, boolean setExtendedInfo) {
+ if (view == null) {
+ return null;
+ }
+
+ // adjust the offset to this view.
+ offset += view.getTop();
+
+ if (view == mContentRoot) {
+ return visitAllChildren(mContentRoot, offset, setExtendedInfo);
+ }
+
+ // otherwise, look for mContentRoot in the children
+ if (view instanceof ViewGroup) {
+ ViewGroup group = ((ViewGroup) view);
+
+ for (int i = 0; i < group.getChildCount(); i++) {
+ List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset,
+ setExtendedInfo);
+ if (list != null) {
+ return list;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Visits a View and its children and generate a {@link ViewInfo} containing the
+ * bounds of all the views.
+ * @param view the root View
+ * @param offset an offset for the view bounds.
+ * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
+ */
+ private ViewInfo visit(View view, int offset, boolean setExtendedInfo) {
+ if (view == null) {
+ return null;
+ }
+
+ ViewInfo result = new ViewInfo(view.getClass().getName(),
+ getContext().getViewKey(view),
+ view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset,
+ view, view.getLayoutParams());
+
+ if (setExtendedInfo) {
+ MarginLayoutParams marginParams = null;
+ LayoutParams params = view.getLayoutParams();
+ if (params instanceof MarginLayoutParams) {
+ marginParams = (MarginLayoutParams) params;
+ }
+ result.setExtendedInfo(view.getBaseline(),
+ marginParams != null ? marginParams.leftMargin : 0,
+ marginParams != null ? marginParams.topMargin : 0,
+ marginParams != null ? marginParams.rightMargin : 0,
+ marginParams != null ? marginParams.bottomMargin : 0);
+ }
+
+ if (view instanceof ViewGroup) {
+ ViewGroup group = ((ViewGroup) view);
+ result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo));
+ }
+
+ return result;
+ }
+
+ /**
+ * Visits all the children of a given ViewGroup generate a list of {@link ViewInfo}
+ * containing the bounds of all the views.
+ * @param view the root View
+ * @param offset an offset for the view bounds.
+ * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
+ */
+ private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
+ boolean setExtendedInfo) {
+ if (viewGroup == null) {
+ return null;
+ }
+
+ List<ViewInfo> children = new ArrayList<ViewInfo>();
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo));
+ }
+ return children;
+ }
+
+
+ private void invalidateRenderingSize() {
+ mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
+ }
+
+ public BufferedImage getImage() {
+ return mImage;
+ }
+
+ public boolean isAlphaChannelImage() {
+ return mIsAlphaChannelImage;
+ }
+
+ public List<ViewInfo> getViewInfos() {
+ return mViewInfoList;
+ }
+
+ public Map<String, String> getDefaultProperties(Object viewObject) {
+ return getContext().getDefaultPropMap(viewObject);
+ }
+
+ public void setScene(RenderSession session) {
+ mScene = session;
+ }
+
+ public RenderSession getSession() {
+ return mScene;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
new file mode 100644
index 0000000..6dcb693
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -0,0 +1,493 @@
+/*
+ * 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.
+ */
+
+package com.android.layoutlib.bridge.impl;
+
+import com.android.ide.common.rendering.api.DensityBasedResourceValue;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.ninepatch.NinePatch;
+import com.android.ninepatch.NinePatchChunk;
+import com.android.resources.Density;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap_Delegate;
+import android.graphics.NinePatch_Delegate;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.util.TypedValue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class to provide various conversion method used in handling android resources.
+ */
+public final class ResourceHelper {
+
+ private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)");
+ private final static float[] sFloatOut = new float[1];
+
+ private final static TypedValue mValue = new TypedValue();
+
+ /**
+ * Returns the color value represented by the given string value
+ * @param value the color value
+ * @return the color as an int
+ * @throw NumberFormatException if the conversion failed.
+ */
+ public static int getColor(String value) {
+ if (value != null) {
+ if (value.startsWith("#") == false) {
+ throw new NumberFormatException(
+ String.format("Color value '%s' must start with #", value));
+ }
+
+ value = value.substring(1);
+
+ // make sure it's not longer than 32bit
+ if (value.length() > 8) {
+ throw new NumberFormatException(String.format(
+ "Color value '%s' is too long. Format is either" +
+ "#AARRGGBB, #RRGGBB, #RGB, or #ARGB",
+ value));
+ }
+
+ if (value.length() == 3) { // RGB format
+ char[] color = new char[8];
+ color[0] = color[1] = 'F';
+ color[2] = color[3] = value.charAt(0);
+ color[4] = color[5] = value.charAt(1);
+ color[6] = color[7] = value.charAt(2);
+ value = new String(color);
+ } else if (value.length() == 4) { // ARGB format
+ char[] color = new char[8];
+ color[0] = color[1] = value.charAt(0);
+ color[2] = color[3] = value.charAt(1);
+ color[4] = color[5] = value.charAt(2);
+ color[6] = color[7] = value.charAt(3);
+ value = new String(color);
+ } else if (value.length() == 6) {
+ value = "FF" + value;
+ }
+
+ // this is a RRGGBB or AARRGGBB value
+
+ // Integer.parseInt will fail to parse strings like "ff191919", so we use
+ // a Long, but cast the result back into an int, since we know that we're only
+ // dealing with 32 bit values.
+ return (int)Long.parseLong(value, 16);
+ }
+
+ throw new NumberFormatException();
+ }
+
+ public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) {
+ String value = resValue.getValue();
+ if (value != null && RenderResources.REFERENCE_NULL.equals(value) == false) {
+ // first check if the value is a file (xml most likely)
+ File f = new File(value);
+ if (f.isFile()) {
+ try {
+ // let the framework inflate the ColorStateList from the XML file, by
+ // providing an XmlPullParser
+ XmlPullParser parser = ParserFactory.create(f);
+
+ BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
+ parser, context, resValue.isFramework());
+ try {
+ return ColorStateList.createFromXml(context.getResources(), blockParser);
+ } finally {
+ blockParser.ensurePopped();
+ }
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to configure parser for " + value, e, null /*data*/);
+ // we'll return null below.
+ } catch (Exception e) {
+ // this is an error and not warning since the file existence is
+ // checked before attempting to parse it.
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed to parse file " + value, e, null /*data*/);
+
+ return null;
+ }
+ } else {
+ // try to load the color state list from an int
+ try {
+ int color = ResourceHelper.getColor(value);
+ return ColorStateList.valueOf(color);
+ } catch (NumberFormatException e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Failed to convert " + value + " into a ColorStateList", e,
+ null /*data*/);
+ return null;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a drawable from the given value.
+ * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable,
+ * or an hexadecimal color
+ * @param context the current context
+ */
+ public static Drawable getDrawable(ResourceValue value, BridgeContext context) {
+ String stringValue = value.getValue();
+ if (RenderResources.REFERENCE_NULL.equals(stringValue)) {
+ return null;
+ }
+
+ String lowerCaseValue = stringValue.toLowerCase();
+
+ Density density = Density.MEDIUM;
+ if (value instanceof DensityBasedResourceValue) {
+ density =
+ ((DensityBasedResourceValue)value).getResourceDensity();
+ }
+
+
+ if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) {
+ File file = new File(stringValue);
+ if (file.isFile()) {
+ try {
+ return getNinePatchDrawable(
+ new FileInputStream(file), density, value.isFramework(),
+ stringValue, context);
+ } catch (IOException e) {
+ // failed to read the file, we'll return null below.
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed lot load " + file.getAbsolutePath(), e, null /*data*/);
+ }
+ }
+
+ return null;
+ } else if (lowerCaseValue.endsWith(".xml")) {
+ // create a block parser for the file
+ File f = new File(stringValue);
+ if (f.isFile()) {
+ try {
+ // let the framework inflate the Drawable from the XML file.
+ XmlPullParser parser = ParserFactory.create(f);
+
+ BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
+ parser, context, value.isFramework());
+ try {
+ return Drawable.createFromXml(context.getResources(), blockParser);
+ } finally {
+ blockParser.ensurePopped();
+ }
+ } catch (Exception e) {
+ // this is an error and not warning since the file existence is checked before
+ // attempting to parse it.
+ Bridge.getLog().error(null, "Failed to parse file " + stringValue,
+ e, null /*data*/);
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ String.format("File %s does not exist (or is not a file)", stringValue),
+ null /*data*/);
+ }
+
+ return null;
+ } else {
+ File bmpFile = new File(stringValue);
+ if (bmpFile.isFile()) {
+ try {
+ Bitmap bitmap = Bridge.getCachedBitmap(stringValue,
+ value.isFramework() ? null : context.getProjectKey());
+
+ if (bitmap == null) {
+ bitmap = Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/,
+ density);
+ Bridge.setCachedBitmap(stringValue, bitmap,
+ value.isFramework() ? null : context.getProjectKey());
+ }
+
+ return new BitmapDrawable(context.getResources(), bitmap);
+ } catch (IOException e) {
+ // we'll return null below
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed lot load " + bmpFile.getAbsolutePath(), e, null /*data*/);
+ }
+ } else {
+ // attempt to get a color from the value
+ try {
+ int color = getColor(stringValue);
+ return new ColorDrawable(color);
+ } catch (NumberFormatException e) {
+ // we'll return null below.
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Failed to convert " + stringValue + " into a drawable", e,
+ null /*data*/);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static Drawable getNinePatchDrawable(InputStream inputStream, Density density,
+ boolean isFramework, String cacheKey, BridgeContext context) throws IOException {
+ // see if we still have both the chunk and the bitmap in the caches
+ NinePatchChunk chunk = Bridge.getCached9Patch(cacheKey,
+ isFramework ? null : context.getProjectKey());
+ Bitmap bitmap = Bridge.getCachedBitmap(cacheKey,
+ isFramework ? null : context.getProjectKey());
+
+ // if either chunk or bitmap is null, then we reload the 9-patch file.
+ if (chunk == null || bitmap == null) {
+ try {
+ NinePatch ninePatch = NinePatch.load(inputStream, true /*is9Patch*/,
+ false /* convert */);
+ if (ninePatch != null) {
+ if (chunk == null) {
+ chunk = ninePatch.getChunk();
+
+ Bridge.setCached9Patch(cacheKey, chunk,
+ isFramework ? null : context.getProjectKey());
+ }
+
+ if (bitmap == null) {
+ bitmap = Bitmap_Delegate.createBitmap(ninePatch.getImage(),
+ false /*isMutable*/,
+ density);
+
+ Bridge.setCachedBitmap(cacheKey, bitmap,
+ isFramework ? null : context.getProjectKey());
+ }
+ }
+ } catch (MalformedURLException e) {
+ // URL is wrong, we'll return null below
+ }
+ }
+
+ if (chunk != null && bitmap != null) {
+ int[] padding = chunk.getPadding();
+ Rect paddingRect = new Rect(padding[0], padding[1], padding[2], padding[3]);
+
+ return new NinePatchDrawable(context.getResources(), bitmap,
+ NinePatch_Delegate.serialize(chunk),
+ paddingRect, null);
+ }
+
+ return null;
+ }
+
+ // ------- TypedValue stuff
+ // This is taken from //device/libs/utils/ResourceTypes.cpp
+
+ private static final class UnitEntry {
+ String name;
+ int type;
+ int unit;
+ float scale;
+
+ UnitEntry(String name, int type, int unit, float scale) {
+ this.name = name;
+ this.type = type;
+ this.unit = unit;
+ this.scale = scale;
+ }
+ }
+
+ private final static UnitEntry[] sUnitNames = new UnitEntry[] {
+ new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f),
+ new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
+ new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
+ new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f),
+ new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f),
+ new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f),
+ new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f),
+ new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100),
+ new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100),
+ };
+
+ /**
+ * Returns the raw value from the given attribute float-type value string.
+ * This object is only valid until the next call on to {@link ResourceHelper}.
+ */
+ public static TypedValue getValue(String attribute, String value, boolean requireUnit) {
+ if (parseFloatAttribute(attribute, value, mValue, requireUnit)) {
+ return mValue;
+ }
+
+ return null;
+ }
+
+ /**
+ * Parse a float attribute and return the parsed value into a given TypedValue.
+ * @param attribute the name of the attribute. Can be null if <var>requireUnit</var> is false.
+ * @param value the string value of the attribute
+ * @param outValue the TypedValue to receive the parsed value
+ * @param requireUnit whether the value is expected to contain a unit.
+ * @return true if success.
+ */
+ public static boolean parseFloatAttribute(String attribute, String value,
+ TypedValue outValue, boolean requireUnit) {
+ assert requireUnit == false || attribute != null;
+
+ // remove the space before and after
+ value = value.trim();
+ int len = value.length();
+
+ if (len <= 0) {
+ return false;
+ }
+
+ // check that there's no non ascii characters.
+ char[] buf = value.toCharArray();
+ for (int i = 0 ; i < len ; i++) {
+ if (buf[i] > 255) {
+ return false;
+ }
+ }
+
+ // check the first character
+ if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.' && buf[0] != '-') {
+ return false;
+ }
+
+ // now look for the string that is after the float...
+ Matcher m = sFloatPattern.matcher(value);
+ if (m.matches()) {
+ String f_str = m.group(1);
+ String end = m.group(2);
+
+ float f;
+ try {
+ f = Float.parseFloat(f_str);
+ } catch (NumberFormatException e) {
+ // this shouldn't happen with the regexp above.
+ return false;
+ }
+
+ if (end.length() > 0 && end.charAt(0) != ' ') {
+ // Might be a unit...
+ if (parseUnit(end, outValue, sFloatOut)) {
+ computeTypedValue(outValue, f, sFloatOut[0]);
+ return true;
+ }
+ return false;
+ }
+
+ // make sure it's only spaces at the end.
+ end = end.trim();
+
+ if (end.length() == 0) {
+ if (outValue != null) {
+ if (requireUnit == false) {
+ outValue.type = TypedValue.TYPE_FLOAT;
+ outValue.data = Float.floatToIntBits(f);
+ } else {
+ // no unit when required? Use dp and out an error.
+ applyUnit(sUnitNames[1], outValue, sFloatOut);
+ computeTypedValue(outValue, f, sFloatOut[0]);
+
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
+ String.format(
+ "Dimension \"%1$s\" in attribute \"%2$s\" is missing unit!",
+ value, attribute),
+ null);
+ }
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static void computeTypedValue(TypedValue outValue, float value, float scale) {
+ value *= scale;
+ boolean neg = value < 0;
+ if (neg) {
+ value = -value;
+ }
+ long bits = (long)(value*(1<<23)+.5f);
+ int radix;
+ int shift;
+ if ((bits&0x7fffff) == 0) {
+ // Always use 23p0 if there is no fraction, just to make
+ // things easier to read.
+ radix = TypedValue.COMPLEX_RADIX_23p0;
+ shift = 23;
+ } else if ((bits&0xffffffffff800000L) == 0) {
+ // Magnitude is zero -- can fit in 0 bits of precision.
+ radix = TypedValue.COMPLEX_RADIX_0p23;
+ shift = 0;
+ } else if ((bits&0xffffffff80000000L) == 0) {
+ // Magnitude can fit in 8 bits of precision.
+ radix = TypedValue.COMPLEX_RADIX_8p15;
+ shift = 8;
+ } else if ((bits&0xffffff8000000000L) == 0) {
+ // Magnitude can fit in 16 bits of precision.
+ radix = TypedValue.COMPLEX_RADIX_16p7;
+ shift = 16;
+ } else {
+ // Magnitude needs entire range, so no fractional part.
+ radix = TypedValue.COMPLEX_RADIX_23p0;
+ shift = 23;
+ }
+ int mantissa = (int)(
+ (bits>>shift) & TypedValue.COMPLEX_MANTISSA_MASK);
+ if (neg) {
+ mantissa = (-mantissa) & TypedValue.COMPLEX_MANTISSA_MASK;
+ }
+ outValue.data |=
+ (radix<<TypedValue.COMPLEX_RADIX_SHIFT)
+ | (mantissa<<TypedValue.COMPLEX_MANTISSA_SHIFT);
+ }
+
+ private static boolean parseUnit(String str, TypedValue outValue, float[] outScale) {
+ str = str.trim();
+
+ for (UnitEntry unit : sUnitNames) {
+ if (unit.name.equals(str)) {
+ applyUnit(unit, outValue, outScale);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static void applyUnit(UnitEntry unit, TypedValue outValue, float[] outScale) {
+ outValue.type = unit.type;
+ outValue.data = unit.unit << TypedValue.COMPLEX_UNIT_SHIFT;
+ outScale[0] = unit.scale;
+ }
+}
+
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java
new file mode 100644
index 0000000..9bd0015
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 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.impl;
+
+import java.util.ArrayList;
+
+/**
+ * Custom Stack implementation on top of an {@link ArrayList} instead of
+ * using {@link java.util.Stack} which is on top of a vector.
+ *
+ * @param <T>
+ */
+public class Stack<T> extends ArrayList<T> {
+
+ private static final long serialVersionUID = 1L;
+
+ public Stack() {
+ super();
+ }
+
+ public Stack(int size) {
+ super(size);
+ }
+
+ /**
+ * Pushes the given object to the stack
+ * @param object the object to push
+ */
+ public void push(T object) {
+ add(object);
+ }
+
+ /**
+ * Remove the object at the top of the stack and returns it.
+ * @return the removed object or null if the stack was empty.
+ */
+ public T pop() {
+ if (size() > 0) {
+ return remove(size() - 1);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the object at the top of the stack.
+ * @return the object at the top or null if the stack is empty.
+ */
+ public T peek() {
+ if (size() > 0) {
+ return get(size() - 1);
+ }
+
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
new file mode 100644
index 0000000..6c998af
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2013 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.impl.binding;
+
+import com.android.ide.common.rendering.api.DataBindingItem;
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.util.Pair;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.Checkable;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * A Helper class to do fake data binding in {@link AdapterView} objects.
+ */
+@SuppressWarnings("deprecation")
+public class AdapterHelper {
+
+ static Pair<View, Boolean> getView(AdapterItem item, AdapterItem parentItem, ViewGroup parent,
+ IProjectCallback callback, ResourceReference adapterRef, boolean skipCallbackParser) {
+ // we don't care about recycling here because we never scroll.
+ DataBindingItem dataBindingItem = item.getDataBindingItem();
+
+ BridgeContext context = RenderAction.getCurrentContext();
+
+ Pair<View, Boolean> pair = context.inflateView(dataBindingItem.getViewReference(),
+ parent, false /*attachToRoot*/, skipCallbackParser);
+
+ View view = pair.getFirst();
+ skipCallbackParser |= pair.getSecond();
+
+ if (view != null) {
+ fillView(context, view, item, parentItem, callback, adapterRef);
+ } else {
+ // create a text view to display an error.
+ TextView tv = new TextView(context);
+ tv.setText("Unable to find layout: " + dataBindingItem.getViewReference().getName());
+ view = tv;
+ }
+
+ return Pair.of(view, skipCallbackParser);
+ }
+
+ private static void fillView(BridgeContext context, View view, AdapterItem item,
+ AdapterItem parentItem, IProjectCallback callback, ResourceReference adapterRef) {
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ final int count = group.getChildCount();
+ for (int i = 0 ; i < count ; i++) {
+ fillView(context, group.getChildAt(i), item, parentItem, callback, adapterRef);
+ }
+ } else {
+ int id = view.getId();
+ if (id != 0) {
+ ResourceReference resolvedRef = context.resolveId(id);
+ if (resolvedRef != null) {
+ int fullPosition = item.getFullPosition();
+ int positionPerType = item.getPositionPerType();
+ int fullParentPosition = parentItem != null ? parentItem.getFullPosition() : 0;
+ int parentPositionPerType = parentItem != null ?
+ parentItem.getPositionPerType() : 0;
+
+ if (view instanceof TextView) {
+ TextView tv = (TextView) view;
+ Object value = callback.getAdapterItemValue(
+ adapterRef, context.getViewKey(view),
+ item.getDataBindingItem().getViewReference(),
+ fullPosition, positionPerType,
+ fullParentPosition, parentPositionPerType,
+ resolvedRef, ViewAttribute.TEXT, tv.getText().toString());
+ if (value != null) {
+ if (value.getClass() != ViewAttribute.TEXT.getAttributeClass()) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
+ "Wrong Adapter Item value class for TEXT. Expected String, got %s",
+ value.getClass().getName()), null);
+ } else {
+ tv.setText((String) value);
+ }
+ }
+ }
+
+ if (view instanceof Checkable) {
+ Checkable cb = (Checkable) view;
+
+ Object value = callback.getAdapterItemValue(
+ adapterRef, context.getViewKey(view),
+ item.getDataBindingItem().getViewReference(),
+ fullPosition, positionPerType,
+ fullParentPosition, parentPositionPerType,
+ resolvedRef, ViewAttribute.IS_CHECKED, cb.isChecked());
+ if (value != null) {
+ if (value.getClass() != ViewAttribute.IS_CHECKED.getAttributeClass()) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
+ "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s",
+ value.getClass().getName()), null);
+ } else {
+ cb.setChecked((Boolean) value);
+ }
+ }
+ }
+
+ if (view instanceof ImageView) {
+ ImageView iv = (ImageView) view;
+
+ Object value = callback.getAdapterItemValue(
+ adapterRef, context.getViewKey(view),
+ item.getDataBindingItem().getViewReference(),
+ fullPosition, positionPerType,
+ fullParentPosition, parentPositionPerType,
+ resolvedRef, ViewAttribute.SRC, iv.getDrawable());
+ if (value != null) {
+ if (value.getClass() != ViewAttribute.SRC.getAttributeClass()) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
+ "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s",
+ value.getClass().getName()), null);
+ } else {
+ // FIXME
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterItem.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterItem.java
new file mode 100644
index 0000000..8e28dba
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterItem.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 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.impl.binding;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.android.ide.common.rendering.api.DataBindingItem;
+
+/**
+ * This is the items provided by the adapter. They are dynamically generated.
+ */
+final class AdapterItem {
+ private final DataBindingItem mItem;
+ private final int mType;
+ private final int mFullPosition;
+ private final int mPositionPerType;
+ private List<AdapterItem> mChildren;
+
+ protected AdapterItem(DataBindingItem item, int type, int fullPosition,
+ int positionPerType) {
+ mItem = item;
+ mType = type;
+ mFullPosition = fullPosition;
+ mPositionPerType = positionPerType;
+ }
+
+ void addChild(AdapterItem child) {
+ if (mChildren == null) {
+ mChildren = new ArrayList<AdapterItem>();
+ }
+
+ mChildren.add(child);
+ }
+
+ List<AdapterItem> getChildren() {
+ if (mChildren != null) {
+ return mChildren;
+ }
+
+ return Collections.emptyList();
+ }
+
+ int getType() {
+ return mType;
+ }
+
+ int getFullPosition() {
+ return mFullPosition;
+ }
+
+ int getPositionPerType() {
+ return mPositionPerType;
+ }
+
+ DataBindingItem getDataBindingItem() {
+ return mItem;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java
new file mode 100644
index 0000000..9a13f5a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2011 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.impl.binding;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.DataBindingItem;
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.util.Pair;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+import android.widget.SpinnerAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fake adapter to do fake data binding in {@link AdapterView} objects for {@link ListAdapter}
+ * and {@link SpinnerAdapter}.
+ *
+ */
+@SuppressWarnings("deprecation")
+public class FakeAdapter extends BaseAdapter {
+
+ // don't use a set because the order is important.
+ private final List<ResourceReference> mTypes = new ArrayList<ResourceReference>();
+ private final IProjectCallback mCallback;
+ private final ResourceReference mAdapterRef;
+ private final List<AdapterItem> mItems = new ArrayList<AdapterItem>();
+ private boolean mSkipCallbackParser = false;
+
+ public FakeAdapter(ResourceReference adapterRef, AdapterBinding binding,
+ IProjectCallback callback) {
+ mAdapterRef = adapterRef;
+ mCallback = callback;
+
+ final int repeatCount = binding.getRepeatCount();
+ final int itemCount = binding.getItemCount();
+
+ // Need an array to count for each type.
+ // This is likely too big, but is the max it can be.
+ int[] typeCount = new int[itemCount];
+
+ // We put several repeating sets.
+ for (int r = 0 ; r < repeatCount ; r++) {
+ // loop on the type of list items, and add however many for each type.
+ for (DataBindingItem dataBindingItem : binding) {
+ ResourceReference viewRef = dataBindingItem.getViewReference();
+ int typeIndex = mTypes.indexOf(viewRef);
+ if (typeIndex == -1) {
+ typeIndex = mTypes.size();
+ mTypes.add(viewRef);
+ }
+
+ int count = dataBindingItem.getCount();
+
+ int index = typeCount[typeIndex];
+ typeCount[typeIndex] += count;
+
+ for (int k = 0 ; k < count ; k++) {
+ mItems.add(new AdapterItem(dataBindingItem, typeIndex, mItems.size(), index++));
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return true;
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mItems.get(position).getType();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // we don't care about recycling here because we never scroll.
+ AdapterItem item = mItems.get(position);
+ Pair<View, Boolean> pair = AdapterHelper.getView(item, null /*parentGroup*/, parent,
+ mCallback, mAdapterRef, mSkipCallbackParser);
+ mSkipCallbackParser = pair.getSecond();
+ return pair.getFirst();
+
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return mTypes.size();
+ }
+
+ // ---- SpinnerAdapter
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ // pass
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java
new file mode 100644
index 0000000..e539579
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2011 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.impl.binding;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.DataBindingItem;
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.util.Pair;
+
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ExpandableListAdapter;
+import android.widget.HeterogeneousExpandableList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SuppressWarnings("deprecation")
+public class FakeExpandableAdapter implements ExpandableListAdapter, HeterogeneousExpandableList {
+
+ private final IProjectCallback mCallback;
+ private final ResourceReference mAdapterRef;
+ private boolean mSkipCallbackParser = false;
+
+ protected final List<AdapterItem> mItems = new ArrayList<AdapterItem>();
+
+ // don't use a set because the order is important.
+ private final List<ResourceReference> mGroupTypes = new ArrayList<ResourceReference>();
+ private final List<ResourceReference> mChildrenTypes = new ArrayList<ResourceReference>();
+
+ public FakeExpandableAdapter(ResourceReference adapterRef, AdapterBinding binding,
+ IProjectCallback callback) {
+ mAdapterRef = adapterRef;
+ mCallback = callback;
+
+ createItems(binding, binding.getItemCount(), binding.getRepeatCount(), mGroupTypes, 1);
+ }
+
+ private void createItems(Iterable<DataBindingItem> iterable, final int itemCount,
+ final int repeatCount, List<ResourceReference> types, int depth) {
+ // Need an array to count for each type.
+ // This is likely too big, but is the max it can be.
+ int[] typeCount = new int[itemCount];
+
+ // we put several repeating sets.
+ for (int r = 0 ; r < repeatCount ; r++) {
+ // loop on the type of list items, and add however many for each type.
+ for (DataBindingItem dataBindingItem : iterable) {
+ ResourceReference viewRef = dataBindingItem.getViewReference();
+ int typeIndex = types.indexOf(viewRef);
+ if (typeIndex == -1) {
+ typeIndex = types.size();
+ types.add(viewRef);
+ }
+
+ List<DataBindingItem> children = dataBindingItem.getChildren();
+ int count = dataBindingItem.getCount();
+
+ // if there are children, we use the count as a repeat count for the children.
+ if (children.size() > 0) {
+ count = 1;
+ }
+
+ int index = typeCount[typeIndex];
+ typeCount[typeIndex] += count;
+
+ for (int k = 0 ; k < count ; k++) {
+ AdapterItem item = new AdapterItem(dataBindingItem, typeIndex, mItems.size(),
+ index++);
+ mItems.add(item);
+
+ if (children.size() > 0) {
+ createItems(dataBindingItem, depth + 1);
+ }
+ }
+ }
+ }
+ }
+
+ private void createItems(DataBindingItem item, int depth) {
+ if (depth == 2) {
+ createItems(item, item.getChildren().size(), item.getCount(), mChildrenTypes, depth);
+ }
+ }
+
+ private AdapterItem getChildItem(int groupPosition, int childPosition) {
+ AdapterItem item = mItems.get(groupPosition);
+
+ List<AdapterItem> children = item.getChildren();
+ return children.get(childPosition);
+ }
+
+ // ---- ExpandableListAdapter
+
+ @Override
+ public int getGroupCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public int getChildrenCount(int groupPosition) {
+ AdapterItem item = mItems.get(groupPosition);
+ return item.getChildren().size();
+ }
+
+ @Override
+ public Object getGroup(int groupPosition) {
+ return mItems.get(groupPosition);
+ }
+
+ @Override
+ public Object getChild(int groupPosition, int childPosition) {
+ return getChildItem(groupPosition, childPosition);
+ }
+
+ @Override
+ public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+ ViewGroup parent) {
+ // we don't care about recycling here because we never scroll.
+ AdapterItem item = mItems.get(groupPosition);
+ Pair<View, Boolean> pair = AdapterHelper.getView(item, null /*parentItem*/, parent,
+ mCallback, mAdapterRef, mSkipCallbackParser);
+ mSkipCallbackParser = pair.getSecond();
+ return pair.getFirst();
+ }
+
+ @Override
+ public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+ View convertView, ViewGroup parent) {
+ // we don't care about recycling here because we never scroll.
+ AdapterItem parentItem = mItems.get(groupPosition);
+ AdapterItem item = getChildItem(groupPosition, childPosition);
+ Pair<View, Boolean> pair = AdapterHelper.getView(item, parentItem, parent, mCallback,
+ mAdapterRef, mSkipCallbackParser);
+ mSkipCallbackParser = pair.getSecond();
+ return pair.getFirst();
+ }
+
+ @Override
+ public long getGroupId(int groupPosition) {
+ return groupPosition;
+ }
+
+ @Override
+ public long getChildId(int groupPosition, int childPosition) {
+ return childPosition;
+ }
+
+ @Override
+ public long getCombinedGroupId(long groupId) {
+ return groupId << 16 | 0x0000FFFF;
+ }
+
+ @Override
+ public long getCombinedChildId(long groupId, long childId) {
+ return groupId << 16 | childId;
+ }
+
+ @Override
+ public boolean isChildSelectable(int groupPosition, int childPosition) {
+ return true;
+ }
+
+ @Override
+ public void onGroupCollapsed(int groupPosition) {
+ // pass
+ }
+
+ @Override
+ public void onGroupExpanded(int groupPosition) {
+ // pass
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ // pass
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ // pass
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return mItems.isEmpty();
+ }
+
+ // ---- HeterogeneousExpandableList
+
+ @Override
+ public int getChildType(int groupPosition, int childPosition) {
+ return getChildItem(groupPosition, childPosition).getType();
+ }
+
+ @Override
+ public int getChildTypeCount() {
+ return mChildrenTypes.size();
+ }
+
+ @Override
+ public int getGroupType(int groupPosition) {
+ return mItems.get(groupPosition).getType();
+ }
+
+ @Override
+ public int getGroupTypeCount() {
+ return mGroupTypes.size();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java
new file mode 100644
index 0000000..82eab85
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2011 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.util;
+
+public class Debug {
+
+ public final static boolean DEBUG = false;
+
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
new file mode 100644
index 0000000..a1fae95
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 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.util;
+
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+import android.util.SparseArray;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DynamicIdMap {
+
+ private final Map<Pair<ResourceType, String>, Integer> mDynamicIds = new HashMap<Pair<ResourceType, String>, Integer>();
+ private final SparseArray<Pair<ResourceType, String>> mRevDynamicIds = new SparseArray<Pair<ResourceType, String>>();
+ private int mDynamicSeed;
+
+ public DynamicIdMap(int seed) {
+ mDynamicSeed = seed;
+ }
+
+ public void reset(int seed) {
+ mDynamicIds.clear();
+ mRevDynamicIds.clear();
+ mDynamicSeed = seed;
+ }
+
+ /**
+ * Returns a dynamic integer for the given resource type/name, creating it if it doesn't
+ * already exist.
+ *
+ * @param type the type of the resource
+ * @param name the name of the resource
+ * @return an integer.
+ */
+ public Integer getId(ResourceType type, String name) {
+ return getId(Pair.of(type, name));
+ }
+
+ /**
+ * Returns a dynamic integer for the given resource type/name, creating it if it doesn't
+ * already exist.
+ *
+ * @param resource the type/name of the resource
+ * @return an integer.
+ */
+ public Integer getId(Pair<ResourceType, String> resource) {
+ Integer value = mDynamicIds.get(resource);
+ if (value == null) {
+ value = Integer.valueOf(++mDynamicSeed);
+ mDynamicIds.put(resource, value);
+ mRevDynamicIds.put(value, resource);
+ }
+
+ return value;
+ }
+
+ public Pair<ResourceType, String> resolveId(int id) {
+ return mRevDynamicIds.get(id);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java
new file mode 100644
index 0000000..4d0c9ce
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2011 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.util;
+
+
+import com.android.internal.util.ArrayUtils;
+
+import android.util.SparseArray;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This is a custom {@link SparseArray} that uses {@link WeakReference} around the objects added
+ * to it. When the array is compacted, not only deleted indices but also empty references
+ * are removed, making the array efficient at removing references that were reclaimed.
+ *
+ * The code is taken from {@link SparseArray} directly and adapted to use weak references.
+ *
+ * Because our usage means that we never actually call {@link #remove(int)} or {@link #delete(int)},
+ * we must manually check if there are reclaimed references to trigger an internal compact step
+ * (which is normally only triggered when an item is manually removed).
+ *
+ * SparseArrays map integers to Objects. Unlike a normal array of Objects,
+ * there can be gaps in the indices. It is intended to be more efficient
+ * than using a HashMap to map Integers to Objects.
+ */
+@SuppressWarnings("unchecked")
+public class SparseWeakArray<E> {
+
+ private static final Object DELETED_REF = new Object();
+ private static final WeakReference<?> DELETED = new WeakReference(DELETED_REF);
+ private boolean mGarbage = false;
+
+ /**
+ * Creates a new SparseArray containing no mappings.
+ */
+ public SparseWeakArray() {
+ this(10);
+ }
+
+ /**
+ * Creates a new SparseArray containing no mappings that will not
+ * require any additional memory allocation to store the specified
+ * number of mappings.
+ */
+ public SparseWeakArray(int initialCapacity) {
+ initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
+
+ mKeys = new int[initialCapacity];
+ mValues = new WeakReference[initialCapacity];
+ mSize = 0;
+ }
+
+ /**
+ * Gets the Object mapped from the specified key, or <code>null</code>
+ * if no such mapping has been made.
+ */
+ public E get(int key) {
+ return get(key, null);
+ }
+
+ /**
+ * Gets the Object mapped from the specified key, or the specified Object
+ * if no such mapping has been made.
+ */
+ public E get(int key, E valueIfKeyNotFound) {
+ int i = binarySearch(mKeys, 0, mSize, key);
+
+ if (i < 0 || mValues[i] == DELETED || mValues[i].get() == null) {
+ return valueIfKeyNotFound;
+ } else {
+ return (E) mValues[i].get();
+ }
+ }
+
+ /**
+ * Removes the mapping from the specified key, if there was any.
+ */
+ public void delete(int key) {
+ int i = binarySearch(mKeys, 0, mSize, key);
+
+ if (i >= 0) {
+ if (mValues[i] != DELETED) {
+ mValues[i] = DELETED;
+ mGarbage = true;
+ }
+ }
+ }
+
+ /**
+ * Alias for {@link #delete(int)}.
+ */
+ public void remove(int key) {
+ delete(key);
+ }
+
+ /**
+ * Removes the mapping at the specified index.
+ */
+ public void removeAt(int index) {
+ if (mValues[index] != DELETED) {
+ mValues[index] = DELETED;
+ mGarbage = true;
+ }
+ }
+
+ private void gc() {
+ int n = mSize;
+ int o = 0;
+ int[] keys = mKeys;
+ WeakReference<?>[] values = mValues;
+
+ for (int i = 0; i < n; i++) {
+ WeakReference<?> val = values[i];
+
+ // Don't keep any non DELETED values, but only the one that still have a valid
+ // reference.
+ if (val != DELETED && val.get() != null) {
+ if (i != o) {
+ keys[o] = keys[i];
+ values[o] = val;
+ }
+
+ o++;
+ }
+ }
+
+ mGarbage = false;
+ mSize = o;
+
+ int newSize = ArrayUtils.idealIntArraySize(mSize);
+ if (newSize < mKeys.length) {
+ int[] nkeys = new int[newSize];
+ WeakReference<?>[] nvalues = new WeakReference[newSize];
+
+ System.arraycopy(mKeys, 0, nkeys, 0, newSize);
+ System.arraycopy(mValues, 0, nvalues, 0, newSize);
+
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * replacing the previous mapping from the specified key if there
+ * was one.
+ */
+ public void put(int key, E value) {
+ int i = binarySearch(mKeys, 0, mSize, key);
+
+ if (i >= 0) {
+ mValues[i] = new WeakReference(value);
+ } else {
+ i = ~i;
+
+ if (i < mSize && (mValues[i] == DELETED || mValues[i].get() == null)) {
+ mKeys[i] = key;
+ mValues[i] = new WeakReference(value);
+ return;
+ }
+
+ if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) {
+ gc();
+
+ // Search again because indices may have changed.
+ i = ~binarySearch(mKeys, 0, mSize, key);
+ }
+
+ if (mSize >= mKeys.length) {
+ int n = ArrayUtils.idealIntArraySize(mSize + 1);
+
+ int[] nkeys = new int[n];
+ WeakReference<?>[] nvalues = new WeakReference[n];
+
+ // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+ System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+ System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+
+ if (mSize - i != 0) {
+ // Log.e("SparseArray", "move " + (mSize - i));
+ System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+ System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+ }
+
+ mKeys[i] = key;
+ mValues[i] = new WeakReference(value);
+ mSize++;
+ }
+ }
+
+ /**
+ * Returns the number of key-value mappings that this SparseArray
+ * currently stores.
+ */
+ public int size() {
+ if (mGarbage) {
+ gc();
+ }
+
+ return mSize;
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the key from the <code>index</code>th key-value mapping that this
+ * SparseArray stores.
+ */
+ public int keyAt(int index) {
+ if (mGarbage) {
+ gc();
+ }
+
+ return mKeys[index];
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the value from the <code>index</code>th key-value mapping that this
+ * SparseArray stores.
+ */
+ public E valueAt(int index) {
+ if (mGarbage) {
+ gc();
+ }
+
+ return (E) mValues[index].get();
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, sets a new
+ * value for the <code>index</code>th key-value mapping that this
+ * SparseArray stores.
+ */
+ public void setValueAt(int index, E value) {
+ if (mGarbage) {
+ gc();
+ }
+
+ mValues[index] = new WeakReference(value);
+ }
+
+ /**
+ * Returns the index for which {@link #keyAt} would return the
+ * specified key, or a negative number if the specified
+ * key is not mapped.
+ */
+ public int indexOfKey(int key) {
+ if (mGarbage) {
+ gc();
+ }
+
+ return binarySearch(mKeys, 0, mSize, key);
+ }
+
+ /**
+ * Returns an index for which {@link #valueAt} would return the
+ * specified key, or a negative number if no keys map to the
+ * specified value.
+ * Beware that this is a linear search, unlike lookups by key,
+ * and that multiple keys can map to the same value and this will
+ * find only one of them.
+ */
+ public int indexOfValue(E value) {
+ if (mGarbage) {
+ gc();
+ }
+
+ for (int i = 0; i < mSize; i++)
+ if (mValues[i].get() == value)
+ return i;
+
+ return -1;
+ }
+
+ /**
+ * Removes all key-value mappings from this SparseArray.
+ */
+ public void clear() {
+ int n = mSize;
+ WeakReference<?>[] values = mValues;
+
+ for (int i = 0; i < n; i++) {
+ values[i] = null;
+ }
+
+ mSize = 0;
+ mGarbage = false;
+ }
+
+ /**
+ * Puts a key/value pair into the array, optimizing for the case where
+ * the key is greater than all existing keys in the array.
+ */
+ public void append(int key, E value) {
+ if (mSize != 0 && key <= mKeys[mSize - 1]) {
+ put(key, value);
+ return;
+ }
+
+ if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) {
+ gc();
+ }
+
+ int pos = mSize;
+ if (pos >= mKeys.length) {
+ int n = ArrayUtils.idealIntArraySize(pos + 1);
+
+ int[] nkeys = new int[n];
+ WeakReference<?>[] nvalues = new WeakReference[n];
+
+ // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+ System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+ System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+
+ mKeys[pos] = key;
+ mValues[pos] = new WeakReference(value);
+ mSize = pos + 1;
+ }
+
+ private boolean hasReclaimedRefs() {
+ for (int i = 0 ; i < mSize ; i++) {
+ if (mValues[i].get() == null) { // DELETED.get() never returns null.
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static int binarySearch(int[] a, int start, int len, int key) {
+ int high = start + len, low = start - 1, guess;
+
+ while (high - low > 1) {
+ guess = (high + low) / 2;
+
+ if (a[guess] < key)
+ low = guess;
+ else
+ high = guess;
+ }
+
+ if (high == start + len)
+ return ~(start + len);
+ else if (a[high] == key)
+ return high;
+ else
+ return ~high;
+ }
+
+ private int[] mKeys;
+ private WeakReference<?>[] mValues;
+ private int mSize;
+}
diff --git a/tools/layoutlib/bridge/src/com/google/android/maps/MapView.java b/tools/layoutlib/bridge/src/com/google/android/maps/MapView.java
new file mode 100644
index 0000000..6d013bb
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/google/android/maps/MapView.java
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+package com.google.android.maps;
+
+import com.android.layoutlib.bridge.MockView;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Mock version of the MapView.
+ * Only non override public methods from the real MapView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ *
+ * TODO: generate automatically.
+ *
+ */
+public class MapView extends MockView {
+
+ /**
+ * Construct a new WebView with a Context object.
+ * @param context A Context object used to access application assets.
+ */
+ public MapView(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Construct a new WebView with layout parameters.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
+ */
+ public MapView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.mapViewStyle);
+ }
+
+ /**
+ * Construct a new WebView with layout parameters and a default style.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
+ * @param defStyle The default style resource ID.
+ */
+ public MapView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ // START FAKE PUBLIC METHODS
+
+ public void displayZoomControls(boolean takeFocus) {
+ }
+
+ public boolean canCoverCenter() {
+ return false;
+ }
+
+ public void preLoad() {
+ }
+
+ public int getZoomLevel() {
+ return 0;
+ }
+
+ public void setSatellite(boolean on) {
+ }
+
+ public boolean isSatellite() {
+ return false;
+ }
+
+ public void setTraffic(boolean on) {
+ }
+
+ public boolean isTraffic() {
+ return false;
+ }
+
+ public void setStreetView(boolean on) {
+ }
+
+ public boolean isStreetView() {
+ return false;
+ }
+
+ public int getLatitudeSpan() {
+ return 0;
+ }
+
+ public int getLongitudeSpan() {
+ return 0;
+ }
+
+ public int getMaxZoomLevel() {
+ return 0;
+ }
+
+ public void onSaveInstanceState(Bundle state) {
+ }
+
+ public void onRestoreInstanceState(Bundle state) {
+ }
+
+ public View getZoomControls() {
+ return null;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java
new file mode 100644
index 0000000..a773d93
--- /dev/null
+++ b/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 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 libcore.icu;
+
+import java.text.FieldPosition;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.ibm.icu.text.DateIntervalFormat;
+import com.ibm.icu.util.DateInterval;
+import com.ibm.icu.util.TimeZone;
+import com.ibm.icu.util.ULocale;
+
+public class DateIntervalFormat_Delegate {
+
+ // ---- delegate manager ----
+ private static final DelegateManager<DateIntervalFormat_Delegate> sManager =
+ new DelegateManager<DateIntervalFormat_Delegate>(DateIntervalFormat_Delegate.class);
+
+ // ---- delegate data ----
+ private DateIntervalFormat mFormat;
+
+
+ // ---- native methods ----
+
+ /*package*/static String formatDateInterval(long address, long fromDate, long toDate) {
+ DateIntervalFormat_Delegate delegate = sManager.getDelegate((int)address);
+ if (delegate == null) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Unable for find native DateIntervalFormat", null);
+ return null;
+ }
+ DateInterval interval = new DateInterval(fromDate, toDate);
+ StringBuffer sb = new StringBuffer();
+ FieldPosition pos = new FieldPosition(0);
+ delegate.mFormat.format(interval, sb, pos);
+ return sb.toString();
+ }
+
+ /*package*/ static long createDateIntervalFormat(String skeleton, String localeName,
+ String tzName) {
+ TimeZone prevDefaultTz = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone(tzName));
+ DateIntervalFormat_Delegate newDelegate = new DateIntervalFormat_Delegate();
+ newDelegate.mFormat =
+ DateIntervalFormat.getInstance(skeleton, new ULocale(localeName));
+ TimeZone.setDefault(prevDefaultTz);
+ return sManager.addNewDelegate(newDelegate);
+ }
+
+ /*package*/ static void destroyDateIntervalFormat(long address) {
+ sManager.removeJavaReferenceFor((int)address);
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
new file mode 100644
index 0000000..06ae804
--- /dev/null
+++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2011 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 libcore.icu;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.ibm.icu.text.DateTimePatternGenerator;
+import com.ibm.icu.util.ULocale;
+
+import java.util.Locale;
+
+/**
+ * Delegate implementing the native methods of libcore.icu.ICU
+ *
+ * Through the layoutlib_create tool, the original native methods of ICU have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class ICU_Delegate {
+
+ // --- Java delegates
+
+ @LayoutlibDelegate
+ /*package*/ static String toLowerCase(String s, String localeName) {
+ return s.toLowerCase();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String toUpperCase(String s, String localeName) {
+ return s.toUpperCase();
+ }
+
+ // --- Native methods accessing ICU's database.
+
+ @LayoutlibDelegate
+ /*package*/ static String getBestDateTimePattern(String skeleton, String localeName) {
+ return DateTimePatternGenerator.getInstance(new ULocale(localeName))
+ .getBestPattern(skeleton);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getCldrVersion() {
+ return "22.1.1"; // TODO: check what the right value should be.
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getIcuVersion() {
+ return "unknown_layoutlib";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getUnicodeVersion() {
+ return "5.2";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String[] getAvailableBreakIteratorLocalesNative() {
+ return new String[0];
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String[] getAvailableCalendarLocalesNative() {
+ return new String[0];
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String[] getAvailableCollatorLocalesNative() {
+ return new String[0];
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String[] getAvailableDateFormatLocalesNative() {
+ return new String[0];
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String[] getAvailableLocalesNative() {
+ return new String[0];
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String[] getAvailableNumberFormatLocalesNative() {
+ return new String[0];
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String[] getAvailableCurrencyCodes() {
+ return new String[0];
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getCurrencyCode(String locale) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getCurrencyDisplayName(String locale, String currencyCode) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int getCurrencyFractionDigits(String currencyCode) {
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getCurrencySymbol(String locale, String currencyCode) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getDisplayCountryNative(String countryCode, String locale) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getDisplayLanguageNative(String languageCode, String locale) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getDisplayVariantNative(String variantCode, String locale) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getISO3CountryNative(String locale) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getISO3LanguageNative(String locale) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String addLikelySubtags(String locale) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String getScript(String locale) {
+ return "";
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String[] getISOLanguagesNative() {
+ return Locale.getISOLanguages();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static String[] getISOCountriesNative() {
+ return Locale.getISOCountries();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean initLocaleDataImpl(String locale, LocaleData result) {
+
+ // Used by Calendar.
+ result.firstDayOfWeek = Integer.valueOf(1);
+ result.minimalDaysInFirstWeek = Integer.valueOf(1);
+
+ // Used by DateFormatSymbols.
+ result.amPm = new String[] { "AM", "PM" };
+ result.eras = new String[] { "BC", "AD" };
+
+ result.longMonthNames = new String[] { "January", "February", "March", "April", "May",
+ "June", "July", "August", "September", "October", "November", "December" };
+ result.shortMonthNames = new String[] { "Jan", "Feb", "Mar", "Apr", "May",
+ "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+ result.longStandAloneMonthNames = result.longMonthNames;
+ result.shortStandAloneMonthNames = result.shortMonthNames;
+
+ // The platform code expects this to begin at index 1, rather than 0. It maps it directly to
+ // the constants from java.util.Calendar.<weekday>
+ result.longWeekdayNames = new String[] {
+ "", "Sunday", "Monday" ,"Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
+ result.shortWeekdayNames = new String[] {
+ "", "Sun", "Mon" ,"Tue", "Wed", "Thu", "Fri", "Sat" };
+ result.tinyWeekdayNames = new String[] {
+ "", "S", "M", "T", "W", "T", "F", "S" };
+
+ result.longStandAloneWeekdayNames = result.longWeekdayNames;
+ result.shortStandAloneWeekdayNames = result.shortWeekdayNames;
+ result.tinyStandAloneWeekdayNames = result.tinyWeekdayNames;
+
+ result.fullTimeFormat = "";
+ result.longTimeFormat = "";
+ result.mediumTimeFormat = "";
+ result.shortTimeFormat = "";
+
+ result.fullDateFormat = "";
+ result.longDateFormat = "";
+ result.mediumDateFormat = "";
+ result.shortDateFormat = "";
+
+ // Used by DecimalFormatSymbols.
+ result.zeroDigit = '0';
+ result.decimalSeparator = '.';
+ result.groupingSeparator = ',';
+ result.patternSeparator = ' ';
+ result.percent = '%';
+ result.perMill = '\u2030';
+ result.monetarySeparator = ' ';
+ result.minusSign = '-';
+ result.exponentSeparator = "e";
+ result.infinity = "\u221E";
+ result.NaN = "NaN";
+ // Also used by Currency.
+ result.currencySymbol = "$";
+ result.internationalCurrencySymbol = "USD";
+
+ // Used by DecimalFormat and NumberFormat.
+ result.numberPattern = "%f";
+ result.integerPattern = "%d";
+ result.currencyPattern = "%s";
+ result.percentPattern = "%f";
+
+ return true;
+ }
+}
diff --git a/tools/layoutlib/bridge/tests/.classpath b/tools/layoutlib/bridge/tests/.classpath
new file mode 100644
index 0000000..2b32e09
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="res"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/layoutlib_bridge"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_PLAT_SRC/dalvik/libcore/xml/src/main/java"/>
+ <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/layoutlib/bridge/tests/.project b/tools/layoutlib/bridge/tests/.project
new file mode 100644
index 0000000..2325eed
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>layoutlib_bridge-tests</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk
new file mode 100644
index 0000000..98cade9
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2011 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_RESOURCE_DIRS := res
+
+LOCAL_MODULE := layoutlib-tests
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := layoutlib kxml2-2.3.0 junit
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/layoutlib/bridge/tests/res/com/android/layoutlib/testdata/layout1.xml b/tools/layoutlib/bridge/tests/res/com/android/layoutlib/testdata/layout1.xml
new file mode 100644
index 0000000..b8fc947
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/com/android/layoutlib/testdata/layout1.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+>
+ <Button
+ android:id="@+id/bouton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="My Button Text"
+ >
+ </Button>
+ <View
+ android:id="@+id/surface"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="2"
+ />
+ <TextView
+ android:id="@+id/status"
+ android:paddingLeft="2dip"
+ android:layout_weight="0"
+ android:background="@drawable/black"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lines="1"
+ android:gravity="center_vertical|center_horizontal"
+ android:text="My TextView Text"
+ />
+</LinearLayout>
diff --git a/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java b/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
new file mode 100644
index 0000000..ec4edac
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+package android.graphics;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ */
+public class Matrix_DelegateTest extends TestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testIdentity() {
+ Matrix m1 = new Matrix();
+
+ assertTrue(m1.isIdentity());
+
+ m1.setValues(new float[] { 1,0,0, 0,1,0, 0,0,1 });
+ assertTrue(m1.isIdentity());
+ }
+
+ public void testCopyConstructor() {
+ Matrix m1 = new Matrix();
+ Matrix m2 = new Matrix(m1);
+
+ float[] v1 = new float[9];
+ float[] v2 = new float[9];
+ m1.getValues(v1);
+ m2.getValues(v2);
+
+ for (int i = 0 ; i < 9; i++) {
+ assertEquals(v1[i], v2[i]);
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
new file mode 100644
index 0000000..d3218db
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2010 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.tools.layoutlib.create.CreateInfo;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests that native delegate classes implement all the required methods.
+ *
+ * This looks at {@link CreateInfo#DELEGATE_CLASS_NATIVES} to get the list of classes that
+ * have their native methods reimplemented through a delegate.
+ *
+ * Since the reimplemented methods are not native anymore, we look for the annotation
+ * {@link LayoutlibDelegate}, and look for a matching method in the delegate (named the same
+ * as the modified class with _Delegate added as a suffix).
+ * If the original native method is not static, then we make sure the delegate method also
+ * include the original class as first parameter (to access "this").
+ *
+ */
+public class TestDelegates extends TestCase {
+
+ public void testNativeDelegates() {
+
+ final String[] classes = CreateInfo.DELEGATE_CLASS_NATIVES;
+ final int count = classes.length;
+ for (int i = 0 ; i < count ; i++) {
+ loadAndCompareClasses(classes[i], classes[i] + "_Delegate");
+ }
+ }
+
+ public void testMethodDelegates() {
+ final String[] methods = CreateInfo.DELEGATE_METHODS;
+ final int count = methods.length;
+ for (int i = 0 ; i < count ; i++) {
+ String methodName = methods[i];
+
+ // extract the class name
+ String className = methodName.substring(0, methodName.indexOf('#'));
+ String targetClassName = className.replace('$', '_') + "_Delegate";
+
+ loadAndCompareClasses(className, targetClassName);
+ }
+ }
+
+ private void loadAndCompareClasses(String originalClassName, String delegateClassName) {
+ // load the classes
+ try {
+ ClassLoader classLoader = TestDelegates.class.getClassLoader();
+ Class<?> originalClass = classLoader.loadClass(originalClassName);
+ Class<?> delegateClass = classLoader.loadClass(delegateClassName);
+
+ compare(originalClass, delegateClass);
+ } catch (ClassNotFoundException e) {
+ fail("Failed to load class: " + e.getMessage());
+ } catch (SecurityException e) {
+ fail("Failed to load class: " + e.getMessage());
+ }
+ }
+
+ private void compare(Class<?> originalClass, Class<?> delegateClass) throws SecurityException {
+ List<Method> checkedDelegateMethods = new ArrayList<Method>();
+
+ // loop on the methods of the original class, and for the ones that are annotated
+ // with @LayoutlibDelegate, look for a matching method in the delegate class.
+ // The annotation is automatically added by layoutlib_create when it replace a method
+ // by a call to a delegate
+ Method[] originalMethods = originalClass.getDeclaredMethods();
+ for (Method originalMethod : originalMethods) {
+ // look for methods that are delegated: they have the LayoutlibDelegate annotation
+ if (originalMethod.getAnnotation(LayoutlibDelegate.class) == null) {
+ continue;
+ }
+
+ // get the signature.
+ Class<?>[] parameters = originalMethod.getParameterTypes();
+
+ // if the method is not static, then the class is added as the first parameter
+ // (for "this")
+ if ((originalMethod.getModifiers() & Modifier.STATIC) == 0) {
+
+ Class<?>[] newParameters = new Class<?>[parameters.length + 1];
+ newParameters[0] = originalClass;
+ System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
+ parameters = newParameters;
+ }
+
+ // if the original class is an inner class that's not static, then
+ // we add this on the enclosing class at the beginning
+ if (originalClass.getEnclosingClass() != null &&
+ (originalClass.getModifiers() & Modifier.STATIC) == 0) {
+ Class<?>[] newParameters = new Class<?>[parameters.length + 1];
+ newParameters[0] = originalClass.getEnclosingClass();
+ System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
+ parameters = newParameters;
+ }
+
+ try {
+ // try to load the method with the given parameter types.
+ Method delegateMethod = delegateClass.getDeclaredMethod(originalMethod.getName(),
+ parameters);
+
+ // check that the method has the annotation
+ assertNotNull(
+ String.format(
+ "Delegate method %1$s for class %2$s does not have the @LayoutlibDelegate annotation",
+ delegateMethod.getName(),
+ originalClass.getName()),
+ delegateMethod.getAnnotation(LayoutlibDelegate.class));
+
+ // check that the method is static
+ assertTrue(
+ String.format(
+ "Delegate method %1$s for class %2$s is not static",
+ delegateMethod.getName(),
+ originalClass.getName()),
+ (delegateMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC);
+
+ // add the method as checked.
+ checkedDelegateMethods.add(delegateMethod);
+ } catch (NoSuchMethodException e) {
+ String name = getMethodName(originalMethod, parameters);
+ fail(String.format("Missing %1$s.%2$s", delegateClass.getName(), name));
+ }
+ }
+
+ // look for dead (delegate) code.
+ // This looks for all methods in the delegate class, and if they have the
+ // @LayoutlibDelegate annotation, make sure they have been previously found as a
+ // match for a method in the original class.
+ // If not, this means the method is a delegate for a method that either doesn't exist
+ // anymore or is not delegated anymore.
+ Method[] delegateMethods = delegateClass.getDeclaredMethods();
+ for (Method delegateMethod : delegateMethods) {
+ // look for methods that are delegates: they have the LayoutlibDelegate annotation
+ if (delegateMethod.getAnnotation(LayoutlibDelegate.class) == null) {
+ continue;
+ }
+
+ assertTrue(
+ String.format(
+ "Delegate method %1$s.%2$s is not used anymore and must be removed",
+ delegateClass.getName(),
+ getMethodName(delegateMethod)),
+ checkedDelegateMethods.contains(delegateMethod));
+ }
+
+ }
+
+ private String getMethodName(Method method) {
+ return getMethodName(method, method.getParameterTypes());
+ }
+
+ private String getMethodName(Method method, Class<?>[] parameters) {
+ // compute a full class name that's long but not too long.
+ StringBuilder sb = new StringBuilder(method.getName() + "(");
+ for (int j = 0; j < parameters.length; j++) {
+ Class<?> theClass = parameters[j];
+ sb.append(theClass.getName());
+ int dimensions = 0;
+ while (theClass.isArray()) {
+ dimensions++;
+ theClass = theClass.getComponentType();
+ }
+ for (int i = 0; i < dimensions; i++) {
+ sb.append("[]");
+ }
+ if (j < (parameters.length - 1)) {
+ sb.append(",");
+ }
+ }
+ sb.append(")");
+
+ return sb.toString();
+ }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
new file mode 100644
index 0000000..865a008
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+package com.android.layoutlib.bridge.android;
+
+import com.android.layoutlib.bridge.impl.ParserFactory;
+
+import org.w3c.dom.Node;
+import org.xmlpull.v1.XmlPullParser;
+
+import junit.framework.TestCase;
+
+public class BridgeXmlBlockParserTest extends TestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testXmlBlockParser() throws Exception {
+
+ XmlPullParser parser = ParserFactory.create(
+ getClass().getResourceAsStream("/com/android/layoutlib/testdata/layout1.xml"),
+ "layout1.xml");
+
+ parser = new BridgeXmlBlockParser(parser, null, false /* platformResourceFlag */);
+
+ assertEquals(XmlPullParser.START_DOCUMENT, parser.next());
+
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+ assertEquals("LinearLayout", parser.getName());
+
+ assertEquals(XmlPullParser.TEXT, parser.next());
+
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+ assertEquals("Button", parser.getName());
+ assertEquals(XmlPullParser.TEXT, parser.next());
+ assertEquals(XmlPullParser.END_TAG, parser.next());
+
+ assertEquals(XmlPullParser.TEXT, parser.next());
+
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+ assertEquals("View", parser.getName());
+ assertEquals(XmlPullParser.END_TAG, parser.next());
+
+ assertEquals(XmlPullParser.TEXT, parser.next());
+
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+ assertEquals("TextView", parser.getName());
+ assertEquals(XmlPullParser.END_TAG, parser.next());
+
+ assertEquals(XmlPullParser.TEXT, parser.next());
+
+ assertEquals(XmlPullParser.END_TAG, parser.next());
+ assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
+ }
+
+ //------------
+
+ /**
+ * Quick'n'dirty debug helper that dumps an XML structure to stdout.
+ */
+ @SuppressWarnings("unused")
+ private void dump(Node node, String prefix) {
+ Node n;
+
+ String[] types = {
+ "unknown",
+ "ELEMENT_NODE",
+ "ATTRIBUTE_NODE",
+ "TEXT_NODE",
+ "CDATA_SECTION_NODE",
+ "ENTITY_REFERENCE_NODE",
+ "ENTITY_NODE",
+ "PROCESSING_INSTRUCTION_NODE",
+ "COMMENT_NODE",
+ "DOCUMENT_NODE",
+ "DOCUMENT_TYPE_NODE",
+ "DOCUMENT_FRAGMENT_NODE",
+ "NOTATION_NODE"
+ };
+
+ String s = String.format("%s<%s> %s %s",
+ prefix,
+ types[node.getNodeType()],
+ node.getNodeName(),
+ node.getNodeValue() == null ? "" : node.getNodeValue().trim());
+
+ System.out.println(s);
+
+ n = node.getFirstChild();
+ if (n != null) {
+ dump(n, prefix + "- ");
+ }
+
+ n = node.getNextSibling();
+ if (n != null) {
+ dump(n, prefix);
+ }
+
+ }
+
+}
diff --git a/tools/layoutlib/create/.classpath b/tools/layoutlib/create/.classpath
new file mode 100644
index 0000000..cd8bb0d
--- /dev/null
+++ b/tools/layoutlib/create/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry excluding="mock_data/" kind="src" path="tests"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_PLAT/prebuilts/tools/common/asm-tools/src-4.0.zip"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/layoutlib/create/.project b/tools/layoutlib/create/.project
new file mode 100644
index 0000000..e100d17
--- /dev/null
+++ b/tools/layoutlib/create/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>layoutlib_create</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/tools/layoutlib/create/.settings/README.txt b/tools/layoutlib/create/.settings/README.txt
new file mode 100644
index 0000000..9120b20
--- /dev/null
+++ b/tools/layoutlib/create/.settings/README.txt
@@ -0,0 +1,2 @@
+Copy this in eclipse project as a .settings folder at the root.
+This ensure proper compilation compliance and warning/error levels.
\ No newline at end of file
diff --git a/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs b/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..5381a0e
--- /dev/null
+++ b/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,93 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.nonnull=com.android.annotations.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.android.annotations.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled
+org.eclipse.jdt.core.compiler.annotation.nullable=com.android.annotations.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk
new file mode 100644
index 0000000..9bd48ab
--- /dev/null
+++ b/tools/layoutlib/create/Android.mk
@@ -0,0 +1,28 @@
+#
+# 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.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+
+LOCAL_JAR_MANIFEST := manifest.txt
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ asm-4.0
+
+LOCAL_MODULE := layoutlib_create
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
new file mode 100644
index 0000000..ef2b185
--- /dev/null
+++ b/tools/layoutlib/create/README.txt
@@ -0,0 +1,271 @@
+# Copyright (C) 2008 The Android Open Source Project
+
+
+- Description -
+---------------
+
+Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor
+to perform layout.
+
+
+- Usage -
+---------
+
+ ./layoutlib_create path/to/android.jar destination.jar
+
+
+- Design Overview -
+-------------------
+
+Layoutlib_create uses the "android.jar" containing all the Java code used by Android
+as generated by the Android build, right before the classes are converted to a DEX format.
+
+The Android JAR can't be used directly in Eclipse:
+- it contains references to native code (which we want to avoid in Eclipse),
+- some classes need to be overridden, for example all the drawing code that is
+ replaced by Java 2D calls in Eclipse.
+- some of the classes that need to be changed are final and/or we need access
+ to their private internal state.
+
+Consequently this tool:
+- parses the input JAR,
+- modifies some of the classes directly using some bytecode manipulation,
+- filters some packages and removes those we don't want in the output JAR,
+- injects some new classes,
+- generates a modified JAR file that is suitable for the Android plugin
+ for Eclipse to perform rendering.
+
+The ASM library is used to do the bytecode modification using its visitor pattern API.
+
+The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the
+configuration is done in the main() method and the CreateInfo structure is expected to
+change with the Android platform as new classes are added, changed or removed.
+
+The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the
+platform, that provides all the necessary missing implementation for rendering graphics
+in Eclipse.
+
+
+
+- Implementation Notes -
+------------------------
+
+The tool works in two phases:
+- first analyze the input jar (AsmAnalyzer class)
+- then generate the output jar (AsmGenerator class),
+
+
+- Analyzer
+----------
+
+The goal of the analyzer is to create a graph of all the classes from the input JAR
+with their dependencies and then only keep the ones we want.
+
+To do that, the analyzer is created with a list of base classes to keep -- everything
+that derives from these is kept. Currently the one such class is android.view.View:
+since we want to render layouts, anything that is sort of a view needs to be kept.
+
+The analyzer is also given a list of class names to keep in the output.
+This is done using shell-like glob patterns that filter on the fully-qualified
+class names, for example "android.*.R**" ("*" does not matches dots whilst "**" does,
+and "." and "$" are interpreted as-is).
+In practice we almost but not quite request the inclusion of full packages.
+
+The analyzer is also given a list of classes to exclude. A fake implementation of these
+classes is injected by the Generator.
+
+With this information, the analyzer parses the input zip to find all the classes.
+All classes deriving from the requested bases classes are kept.
+All classes which name matched the glob pattern are kept.
+The analysis then finds all the dependencies of the classes that are to be kept
+using an ASM visitor on the class, the field types, the method types and annotations types.
+Classes that belong to the current JRE are excluded.
+
+The output of the analyzer is a set of ASM ClassReader instances which are then
+fed to the generator.
+
+
+- Generator
+-----------
+
+The generator is constructed from a CreateInfo struct that acts as a config file
+and lists:
+- the classes to inject in the output JAR -- these classes are directly implemented
+ in layoutlib_create and will be used to interface with the renderer in Eclipse.
+- specific methods to override (see method stubs details below).
+- specific methods for which to delegate calls.
+- specific methods to remove based on their return type.
+- specific classes to rename.
+- specific classes to refactor.
+
+Each of these are specific strategies we use to be able to modify the Android code
+to fit within the Eclipse renderer. These strategies are explained beow.
+
+The core method of the generator is transform(): it takes an input ASM ClassReader
+and modifies it to produce a byte array suitable for the final JAR file.
+
+The first step of the transformation is to implement the method delegates.
+
+The TransformClassAdapter is then used to process the potentially renamed class.
+All protected or private classes are market as public.
+All classes are made non-final.
+Interfaces are left as-is.
+
+If a method has a return type that must be erased, the whole method is skipped.
+Methods are also changed from protected/private to public.
+The code of the methods is then kept as-is, except for native methods which are
+replaced by a stub. Methods that are to be overridden are also replaced by a stub.
+
+Finally fields are also visited and changed from protected/private to public.
+
+The next step of the transformation is changing the name of the class in case
+we requested the class to be renamed. This uses the RenameClassAdapter to also rename
+all inner classes and references in methods and types. Note that other classes are
+not transformed and keep referencing the original name.
+
+The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but
+updates the references in all classes. This is used to update the references of classes
+in the java package that were added in the Dalvik VM but are not a part of the standard
+JVM. The existing classes are modified to update all references to these non-standard
+classes. An alternate implementation of these (com.android.tools.layoutlib.java.*) is
+injected.
+
+The ClassAdapters are chained together to achieve the desired output. (Look at section
+2.2.7 Transformation chains in the asm user guide, link in the References.) The order of
+execution of these is:
+ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] ->
+RefactorClassAdapter -> ClassWriter
+
+- Method stubs
+--------------
+
+As indicated above, all native and overridden methods are replaced by a stub.
+We don't have the code to replace with in layoutlib_create.
+Instead the StubMethodAdapter replaces the code of the method by a call to
+OverrideMethod.invokeX(). When using the final JAR, the bridge can register
+listeners from these overridden method calls based on the method signatures.
+
+The listeners are currently pretty basic: we only pass the signature of the
+method being called, its caller object and a flag indicating whether the
+method was native. We do not currently provide the parameters. The listener
+can however specify the return value of the overridden method.
+
+This strategy is now obsolete and replaced by the method delegates.
+
+
+- Strategies
+------------
+
+We currently have 6 strategies to deal with overriding the rendering code
+and make it run in Eclipse. Most of these strategies are implemented hand-in-hand
+by the bridge (which runs in Eclipse) and the generator.
+
+
+1- Class Injection
+
+This is the easiest: we currently inject the following classes:
+- OverrideMethod and its associated MethodListener and MethodAdapter are used
+ to intercept calls to some specific methods that are stubbed out and change
+ their return value.
+- CreateInfo class, which configured the generator. Not used yet, but could
+ in theory help us track what the generator changed.
+- AutoCloseable and Objects are part of Java 7. To enable us to still run on Java 6, new
+ classes are injected. The implementation for these classes has been taken from
+ Android's libcore (platform/libcore/luni/src/main/java/java/...).
+- Charsets, IntegralToString and UnsafeByteSequence are not part of the standard JAVA VM.
+ They are added to the Dalvik VM for performance reasons. An implementation that is very
+ close to the original (which is at platform/libcore/luni/src/main/java/...) is injected.
+ Since these classees were in part of the java package, where we can't inject classes,
+ all references to these have been updated (See strategy 4- Refactoring Classes).
+
+
+2- Overriding methods
+
+As explained earlier, the creator doesn't have any replacement code for
+methods to override. Instead it removes the original code and replaces it
+by a call to a specific OveriddeMethod.invokeX(). The bridge then registers
+a listener on the method signature and can provide an implementation.
+
+This strategy is now obsolete and replaced by the method delegates.
+See strategy 5 below.
+
+
+3- Renaming classes
+
+This simply changes the name of a class in its definition, as well as all its
+references in internal inner classes and methods.
+Calls from other classes are not modified -- they keep referencing the original
+class name. This allows the bridge to literally replace an implementation.
+
+An example will make this easier: android.graphics.Paint is the main drawing
+class that we need to replace. To do so, the generator renames Paint to _original_Paint.
+Later the bridge provides its own replacement version of Paint which will be used
+by the rest of the Android stack. The replacement version of Paint can still use
+(either by inheritance or delegation) all the original non-native code of _original_Paint
+if it so desires.
+
+Some of the Android classes are basically wrappers over native objects and since
+we don't have the native code in Eclipse, we need to provide a full alternate
+implementation. Sub-classing doesn't work as some native methods are static and
+we don't control object creation.
+
+This won't rename/replace the inner static methods of a given class.
+
+
+4- Refactoring classes
+
+This is very similar to the Renaming classes except that it also updates the reference in
+all classes. This is done for classes which are added to the Dalvik VM for performance
+reasons but are not present in the Standard Java VM. An implementation for these classes
+is also injected.
+
+
+5- Method erasure based on return type
+
+This is mostly an implementation detail of the bridge: in the Paint class
+mentioned above, some inner static classes are used to pass around
+attributes (e.g. FontMetrics, or the Style enum) and all the original implementation
+is native.
+
+In this case we have a strategy that tells the generator that anything returning, for
+example, the inner class Paint$Style in the Paint class should be discarded and the
+bridge will provide its own implementation.
+
+
+6- Method Delegates
+
+This strategy is used to override method implementations.
+Given a method SomeClass.MethodName(), 1 or 2 methods are generated:
+a- A copy of the original method named SomeClass.MethodName_Original().
+ The content is the original method as-is from the reader.
+ This step is omitted if the method is native, since it has no Java implementation.
+b- A brand new implementation of SomeClass.MethodName() which calls to a
+ non-existing static method named SomeClass_Delegate.MethodName().
+ The implementation of this 'delegate' method is done in layoutlib_brigde.
+
+The delegate method is a static method.
+If the original method is non-static, the delegate method receives the original 'this'
+as its first argument. If the original method is an inner non-static method, it also
+receives the inner 'this' as the second argument.
+
+
+
+- References -
+--------------
+
+
+The JVM Specification 2nd edition:
+ http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html
+
+Understanding bytecode:
+ http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/
+
+Bytecode opcode list:
+ http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
+
+ASM user guide:
+ http://download.forge.objectweb.org/asm/asm4-guide.pdf
+
+
+--
+end
diff --git a/tools/layoutlib/create/manifest.txt b/tools/layoutlib/create/manifest.txt
new file mode 100644
index 0000000..238e7f9
--- /dev/null
+++ b/tools/layoutlib/create/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.tools.layoutlib.create.Main
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
new file mode 100644
index 0000000..9a48ea6
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 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.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes a method that has been converted to a delegate by layoutlib_create.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LayoutlibDelegate {
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java
new file mode 100644
index 0000000..0689c92
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 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.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes a parameter or field can be null.
+ * <p/>
+ * When decorating a method call parameter, this denotes the parameter can
+ * legitimately be null and the method will gracefully deal with it. Typically used
+ * on optional parameters.
+ * <p/>
+ * When decorating a method, this denotes the method might legitimately return null.
+ * <p/>
+ * This is a marker annotation and it has no specific attributes.
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface Nullable {
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java
new file mode 100644
index 0000000..e4e016b
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 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.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes that the class, method or field has its visibility relaxed so
+ * that unit tests can access it.
+ * <p/>
+ * The <code>visibility</code> argument can be used to specific what the original
+ * visibility should have been if it had not been made public or package-private for testing.
+ * The default is to consider the element private.
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface VisibleForTesting {
+ /**
+ * Intended visibility if the element had not been made public or package-private for
+ * testing.
+ */
+ enum Visibility {
+ /** The element should be considered protected. */
+ PROTECTED,
+ /** The element should be considered package-private. */
+ PACKAGE,
+ /** The element should be considered private. */
+ PRIVATE
+ }
+
+ /**
+ * Intended visibility if the element had not been made public or package-private for testing.
+ * If not specified, one should assume the element originally intended to be private.
+ */
+ Visibility visibility() default Visibility.PRIVATE;
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
new file mode 100644
index 0000000..b2caa25
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2013 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.tools.layoutlib.create;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+import org.objectweb.asm.signature.SignatureWriter;
+
+/**
+ * Provides the common code for RenameClassAdapter and RefactorClassAdapter. It
+ * goes through the complete class and finds references to other classes. It
+ * then calls {@link #renameInternalType(String)} to convert the className to
+ * the new value, if need be.
+ */
+public abstract class AbstractClassAdapter extends ClassVisitor {
+
+ /**
+ * Returns the new FQCN for the class, if the reference to this class needs
+ * to be updated. Else, it returns the same string.
+ * @param name Old FQCN
+ * @return New FQCN if it needs to be renamed, else the old FQCN
+ */
+ abstract String renameInternalType(String name);
+
+ public AbstractClassAdapter(ClassVisitor cv) {
+ super(Opcodes.ASM4, cv);
+ }
+
+ /**
+ * Renames a type descriptor, e.g. "Lcom.package.MyClass;"
+ * If the type doesn't need to be renamed, returns the input string as-is.
+ */
+ String renameTypeDesc(String desc) {
+ if (desc == null) {
+ return null;
+ }
+
+ return renameType(Type.getType(desc));
+ }
+
+ /**
+ * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
+ * object element, e.g. "[Lcom.package.MyClass;"
+ * If the type doesn't need to be renamed, returns the internal name of the input type.
+ */
+ String renameType(Type type) {
+ if (type == null) {
+ return null;
+ }
+
+ if (type.getSort() == Type.OBJECT) {
+ String in = type.getInternalName();
+ return "L" + renameInternalType(in) + ";";
+ } else if (type.getSort() == Type.ARRAY) {
+ StringBuilder sb = new StringBuilder();
+ for (int n = type.getDimensions(); n > 0; n--) {
+ sb.append('[');
+ }
+ sb.append(renameType(type.getElementType()));
+ return sb.toString();
+ }
+ return type.getDescriptor();
+ }
+
+ /**
+ * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
+ * object element, e.g. "[Lcom.package.MyClass;".
+ * This is like renameType() except that it returns a Type object.
+ * If the type doesn't need to be renamed, returns the input type object.
+ */
+ Type renameTypeAsType(Type type) {
+ if (type == null) {
+ return null;
+ }
+
+ if (type.getSort() == Type.OBJECT) {
+ String in = type.getInternalName();
+ String newIn = renameInternalType(in);
+ if (newIn != in) {
+ return Type.getType("L" + newIn + ";");
+ }
+ } else if (type.getSort() == Type.ARRAY) {
+ StringBuilder sb = new StringBuilder();
+ for (int n = type.getDimensions(); n > 0; n--) {
+ sb.append('[');
+ }
+ sb.append(renameType(type.getElementType()));
+ return Type.getType(sb.toString());
+ }
+ return type;
+ }
+
+ /**
+ * Renames a method descriptor, i.e. applies renameType to all arguments and to the
+ * return value.
+ */
+ String renameMethodDesc(String desc) {
+ if (desc == null) {
+ return null;
+ }
+
+ Type[] args = Type.getArgumentTypes(desc);
+
+ StringBuilder sb = new StringBuilder("(");
+ for (Type arg : args) {
+ String name = renameType(arg);
+ sb.append(name);
+ }
+ sb.append(')');
+
+ Type ret = Type.getReturnType(desc);
+ String name = renameType(ret);
+ sb.append(name);
+
+ return sb.toString();
+ }
+
+
+ /**
+ * Renames the ClassSignature handled by ClassVisitor.visit
+ * or the MethodTypeSignature handled by ClassVisitor.visitMethod.
+ */
+ String renameTypeSignature(String sig) {
+ if (sig == null) {
+ return null;
+ }
+ SignatureReader reader = new SignatureReader(sig);
+ SignatureWriter writer = new SignatureWriter();
+ reader.accept(new RenameSignatureAdapter(writer));
+ sig = writer.toString();
+ return sig;
+ }
+
+
+ /**
+ * Renames the FieldTypeSignature handled by ClassVisitor.visitField
+ * or MethodVisitor.visitLocalVariable.
+ */
+ String renameFieldSignature(String sig) {
+ return renameTypeSignature(sig);
+ }
+
+
+ //----------------------------------
+ // Methods from the ClassAdapter
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ name = renameInternalType(name);
+ superName = renameInternalType(superName);
+ signature = renameTypeSignature(signature);
+ if (interfaces != null) {
+ for (int i = 0; i < interfaces.length; ++i) {
+ interfaces[i] = renameInternalType(interfaces[i]);
+ }
+ }
+
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ name = renameInternalType(name);
+ outerName = renameInternalType(outerName);
+ super.visitInnerClass(name, outerName, innerName, access);
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ super.visitOuterClass(renameInternalType(owner), name, renameTypeDesc(desc));
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ desc = renameMethodDesc(desc);
+ signature = renameTypeSignature(signature);
+ MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+ return new RenameMethodAdapter(mw);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ desc = renameTypeDesc(desc);
+ return super.visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ desc = renameTypeDesc(desc);
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+
+ //----------------------------------
+
+ /**
+ * A method visitor that renames all references from an old class name to a new class name.
+ */
+ public class RenameMethodAdapter extends MethodVisitor {
+
+ /**
+ * Creates a method visitor that renames all references from a given old name to a given new
+ * name. The method visitor will also rename all inner classes.
+ * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
+ */
+ public RenameMethodAdapter(MethodVisitor mv) {
+ super(Opcodes.ASM4, mv);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ desc = renameTypeDesc(desc);
+
+ return super.visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
+ desc = renameTypeDesc(desc);
+
+ return super.visitParameterAnnotation(parameter, desc, visible);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ // The type sometimes turns out to be a type descriptor. We try to detect it and fix.
+ if (type.indexOf(';') > 0) {
+ type = renameTypeDesc(type);
+ } else {
+ type = renameInternalType(type);
+ }
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ owner = renameInternalType(owner);
+ desc = renameTypeDesc(desc);
+
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ // The owner sometimes turns out to be a type descriptor. We try to detect it and fix.
+ if (owner.indexOf(';') > 0) {
+ owner = renameTypeDesc(owner);
+ } else {
+ owner = renameInternalType(owner);
+ }
+ desc = renameMethodDesc(desc);
+
+ super.visitMethodInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ // If cst is a Type, this means the code is trying to pull the .class constant
+ // for this class, so it needs to be renamed too.
+ if (cst instanceof Type) {
+ cst = renameTypeAsType((Type) cst);
+ }
+ super.visitLdcInsn(cst);
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ desc = renameTypeDesc(desc);
+
+ super.visitMultiANewArrayInsn(desc, dims);
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ type = renameInternalType(type);
+
+ super.visitTryCatchBlock(start, end, handler, type);
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ desc = renameTypeDesc(desc);
+ signature = renameFieldSignature(signature);
+
+ super.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+
+ }
+
+ //----------------------------------
+
+ public class RenameSignatureAdapter extends SignatureVisitor {
+
+ private final SignatureVisitor mSv;
+
+ public RenameSignatureAdapter(SignatureVisitor sv) {
+ super(Opcodes.ASM4);
+ mSv = sv;
+ }
+
+ @Override
+ public void visitClassType(String name) {
+ name = renameInternalType(name);
+ mSv.visitClassType(name);
+ }
+
+ @Override
+ public void visitInnerClassType(String name) {
+ name = renameInternalType(name);
+ mSv.visitInnerClassType(name);
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ SignatureVisitor sv = mSv.visitArrayType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitBaseType(char descriptor) {
+ mSv.visitBaseType(descriptor);
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ SignatureVisitor sv = mSv.visitClassBound();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitEnd() {
+ mSv.visitEnd();
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ SignatureVisitor sv = mSv.visitExceptionType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitFormalTypeParameter(String name) {
+ mSv.visitFormalTypeParameter(name);
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ SignatureVisitor sv = mSv.visitInterface();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ SignatureVisitor sv = mSv.visitInterfaceBound();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ SignatureVisitor sv = mSv.visitParameterType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ SignatureVisitor sv = mSv.visitReturnType();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ SignatureVisitor sv = mSv.visitSuperclass();
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitTypeArgument() {
+ mSv.visitTypeArgument();
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ SignatureVisitor sv = mSv.visitTypeArgument(wildcard);
+ return new RenameSignatureAdapter(sv);
+ }
+
+ @Override
+ public void visitTypeVariable(String name) {
+ mSv.visitTypeVariable(name);
+ }
+
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
new file mode 100644
index 0000000..1572a40
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -0,0 +1,854 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Analyzes the input JAR using the ASM java bytecode manipulation library
+ * to list the desired classes and their dependencies.
+ */
+public class AsmAnalyzer {
+
+ // Note: a bunch of stuff has package-level access for unit tests. Consider it private.
+
+ /** Output logger. */
+ private final Log mLog;
+ /** The input source JAR to parse. */
+ private final List<String> mOsSourceJar;
+ /** The generator to fill with the class list and dependency list. */
+ private final AsmGenerator mGen;
+ /** Keep all classes that derive from these one (these included). */
+ private final String[] mDeriveFrom;
+ /** Glob patterns of classes to keep, e.g. "com.foo.*" */
+ private final String[] mIncludeGlobs;
+ /** The set of classes to exclude.*/
+ private final Set<String> mExcludedClasses;
+
+ /**
+ * Creates a new analyzer.
+ *
+ * @param log The log output.
+ * @param osJarPath The input source JARs to parse.
+ * @param gen The generator to fill with the class list and dependency list.
+ * @param deriveFrom Keep all classes that derive from these one (these included).
+ * @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*"
+ * ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
+ */
+ public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
+ String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses) {
+ mLog = log;
+ mGen = gen;
+ mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
+ mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
+ mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
+ mExcludedClasses = excludeClasses;
+ }
+
+ /**
+ * Starts the analysis using parameters from the constructor.
+ * Fills the generator with classes & dependencies found.
+ */
+ public void analyze() throws IOException, LogAbortException {
+ Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar);
+ mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
+ mOsSourceJar.size() > 1 ? "s" : "");
+
+ Map<String, ClassReader> found = findIncludes(zipClasses);
+ Map<String, ClassReader> deps = findDeps(zipClasses, found);
+
+ if (mGen != null) {
+ mGen.setKeep(found);
+ mGen.setDeps(deps);
+ }
+ }
+
+ /**
+ * Parses a JAR file and returns a list of all classes founds using a map
+ * class name => ASM ClassReader. Class names are in the form "android.view.View".
+ */
+ Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
+ TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
+
+ for (String jarPath : jarPathList) {
+ ZipFile zip = new ZipFile(jarPath);
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ ZipEntry entry;
+ while (entries.hasMoreElements()) {
+ entry = entries.nextElement();
+ if (entry.getName().endsWith(".class")) {
+ ClassReader cr = new ClassReader(zip.getInputStream(entry));
+ String className = classReaderToClassName(cr);
+ classes.put(className, cr);
+ }
+ }
+ }
+
+ return classes;
+ }
+
+ /**
+ * Utility that returns the fully qualified binary class name for a ClassReader.
+ * E.g. it returns something like android.view.View.
+ */
+ static String classReaderToClassName(ClassReader classReader) {
+ if (classReader == null) {
+ return null;
+ } else {
+ return classReader.getClassName().replace('/', '.');
+ }
+ }
+
+ /**
+ * Utility that returns the fully qualified binary class name from a path-like FQCN.
+ * E.g. it returns android.view.View from android/view/View.
+ */
+ static String internalToBinaryClassName(String className) {
+ if (className == null) {
+ return null;
+ } else {
+ return className.replace('/', '.');
+ }
+ }
+
+ /**
+ * Process the "includes" arrays.
+ * <p/>
+ * This updates the in_out_found map.
+ */
+ Map<String, ClassReader> findIncludes(Map<String, ClassReader> zipClasses)
+ throws LogAbortException {
+ TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+
+ mLog.debug("Find classes to include.");
+
+ for (String s : mIncludeGlobs) {
+ findGlobs(s, zipClasses, found);
+ }
+ for (String s : mDeriveFrom) {
+ findClassesDerivingFrom(s, zipClasses, found);
+ }
+
+ return found;
+ }
+
+
+ /**
+ * Uses ASM to find the class reader for the given FQCN class name.
+ * If found, insert it in the in_out_found map.
+ * Returns the class reader object.
+ */
+ ClassReader findClass(String className, Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inOutFound) throws LogAbortException {
+ ClassReader classReader = zipClasses.get(className);
+ if (classReader == null) {
+ throw new LogAbortException("Class %s not found by ASM in %s",
+ className, mOsSourceJar);
+ }
+
+ inOutFound.put(className, classReader);
+ return classReader;
+ }
+
+ /**
+ * Insert in the inOutFound map all classes found in zipClasses that match the
+ * given glob pattern.
+ * <p/>
+ * The glob pattern is not a regexp. It only accepts the "*" keyword to mean
+ * "anything but a period". The "." and "$" characters match themselves.
+ * The "**" keyword means everything including ".".
+ * <p/>
+ * Examples:
+ * <ul>
+ * <li>com.foo.* matches all classes in the package com.foo but NOT sub-packages.
+ * <li>com.foo*.*$Event matches all internal Event classes in a com.foo*.* class.
+ * </ul>
+ */
+ void findGlobs(String globPattern, Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inOutFound) throws LogAbortException {
+ // transforms the glob pattern in a regexp:
+ // - escape "." with "\."
+ // - replace "*" by "[^.]*"
+ // - escape "$" with "\$"
+ // - add end-of-line match $
+ globPattern = globPattern.replaceAll("\\$", "\\\\\\$");
+ globPattern = globPattern.replaceAll("\\.", "\\\\.");
+ // prevent ** from being altered by the next rule, then process the * rule and finally
+ // the real ** rule (which is now @)
+ globPattern = globPattern.replaceAll("\\*\\*", "@");
+ globPattern = globPattern.replaceAll("\\*", "[^.]*");
+ globPattern = globPattern.replaceAll("@", ".*");
+ globPattern += "$";
+
+ Pattern regexp = Pattern.compile(globPattern);
+
+ for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
+ String class_name = entry.getKey();
+ if (regexp.matcher(class_name).matches()) {
+ findClass(class_name, zipClasses, inOutFound);
+ }
+ }
+ }
+
+ /**
+ * Checks all the classes defined in the JarClassName instance and uses BCEL to
+ * determine if they are derived from the given FQCN super class name.
+ * Inserts the super class and all the class objects found in the map.
+ */
+ void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inOutFound) throws LogAbortException {
+ findClass(super_name, zipClasses, inOutFound);
+
+ for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
+ String className = entry.getKey();
+ if (super_name.equals(className)) {
+ continue;
+ }
+ ClassReader classReader = entry.getValue();
+ ClassReader parent_cr = classReader;
+ while (parent_cr != null) {
+ String parent_name = internalToBinaryClassName(parent_cr.getSuperName());
+ if (parent_name == null) {
+ // not found
+ break;
+ } else if (super_name.equals(parent_name)) {
+ inOutFound.put(className, classReader);
+ break;
+ }
+ parent_cr = zipClasses.get(parent_name);
+ }
+ }
+ }
+
+ /**
+ * Instantiates a new DependencyVisitor. Useful for unit tests.
+ */
+ DependencyVisitor getVisitor(Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inKeep,
+ Map<String, ClassReader> outKeep,
+ Map<String, ClassReader> inDeps,
+ Map<String, ClassReader> outDeps) {
+ return new DependencyVisitor(zipClasses, inKeep, outKeep, inDeps, outDeps);
+ }
+
+ /**
+ * Finds all dependencies for all classes in keepClasses which are also
+ * listed in zipClasses. Returns a map of all the dependencies found.
+ */
+ Map<String, ClassReader> findDeps(Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inOutKeepClasses) {
+
+ TreeMap<String, ClassReader> deps = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> new_deps = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> temp = new TreeMap<String, ClassReader>();
+
+ DependencyVisitor visitor = getVisitor(zipClasses,
+ inOutKeepClasses, new_keep,
+ deps, new_deps);
+
+ for (ClassReader cr : inOutKeepClasses.values()) {
+ cr.accept(visitor, 0 /* flags */);
+ }
+
+ while (new_deps.size() > 0 || new_keep.size() > 0) {
+ deps.putAll(new_deps);
+ inOutKeepClasses.putAll(new_keep);
+
+ temp.clear();
+ temp.putAll(new_deps);
+ temp.putAll(new_keep);
+ new_deps.clear();
+ new_keep.clear();
+ mLog.debug("Found %1$d to keep, %2$d dependencies.",
+ inOutKeepClasses.size(), deps.size());
+
+ for (ClassReader cr : temp.values()) {
+ cr.accept(visitor, 0 /* flags */);
+ }
+ }
+
+ mLog.info("Found %1$d classes to keep, %2$d class dependencies.",
+ inOutKeepClasses.size(), deps.size());
+
+ return deps;
+ }
+
+
+
+ // ----------------------------------
+
+ /**
+ * Visitor to collect all the type dependencies from a class.
+ */
+ public class DependencyVisitor extends ClassVisitor {
+
+ /** All classes found in the source JAR. */
+ private final Map<String, ClassReader> mZipClasses;
+ /** Classes from which dependencies are to be found. */
+ private final Map<String, ClassReader> mInKeep;
+ /** Dependencies already known. */
+ private final Map<String, ClassReader> mInDeps;
+ /** New dependencies found by this visitor. */
+ private final Map<String, ClassReader> mOutDeps;
+ /** New classes to keep as-is found by this visitor. */
+ private final Map<String, ClassReader> mOutKeep;
+
+ /**
+ * Creates a new visitor that will find all the dependencies for the visited class.
+ * Types which are already in the zipClasses, keepClasses or inDeps are not marked.
+ * New dependencies are marked in outDeps.
+ *
+ * @param zipClasses All classes found in the source JAR.
+ * @param inKeep Classes from which dependencies are to be found.
+ * @param inDeps Dependencies already known.
+ * @param outDeps New dependencies found by this visitor.
+ */
+ public DependencyVisitor(Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inKeep,
+ Map<String, ClassReader> outKeep,
+ Map<String,ClassReader> inDeps,
+ Map<String,ClassReader> outDeps) {
+ super(Opcodes.ASM4);
+ mZipClasses = zipClasses;
+ mInKeep = inKeep;
+ mOutKeep = outKeep;
+ mInDeps = inDeps;
+ mOutDeps = outDeps;
+ }
+
+ /**
+ * Considers the given class name as a dependency.
+ * If it does, add to the mOutDeps map.
+ */
+ public void considerName(String className) {
+ if (className == null) {
+ return;
+ }
+
+ className = internalToBinaryClassName(className);
+
+ // exclude classes that have already been found or are marked to be excluded
+ if (mInKeep.containsKey(className) ||
+ mOutKeep.containsKey(className) ||
+ mInDeps.containsKey(className) ||
+ mOutDeps.containsKey(className) ||
+ mExcludedClasses.contains(getBaseName(className))) {
+ return;
+ }
+
+ // exclude classes that are not part of the JAR file being examined
+ ClassReader cr = mZipClasses.get(className);
+ if (cr == null) {
+ return;
+ }
+
+ try {
+ // exclude classes that are part of the default JRE (the one executing this program)
+ if (getClass().getClassLoader().loadClass(className) != null) {
+ return;
+ }
+ } catch (ClassNotFoundException e) {
+ // ignore
+ }
+
+ // accept this class:
+ // - android classes are added to dependencies
+ // - non-android classes are added to the list of classes to keep as-is (they don't need
+ // to be stubbed).
+ if (className.indexOf("android") >= 0) { // TODO make configurable
+ mOutDeps.put(className, cr);
+ } else {
+ mOutKeep.put(className, cr);
+ }
+ }
+
+ /**
+ * Considers this array of names using considerName().
+ */
+ public void considerNames(String[] classNames) {
+ if (classNames != null) {
+ for (String className : classNames) {
+ considerName(className);
+ }
+ }
+ }
+
+ /**
+ * Considers this signature or type signature by invoking the {@link SignatureVisitor}
+ * on it.
+ */
+ public void considerSignature(String signature) {
+ if (signature != null) {
+ SignatureReader sr = new SignatureReader(signature);
+ // SignatureReader.accept will call accessType so we don't really have
+ // to differentiate where the signature comes from.
+ sr.accept(new MySignatureVisitor());
+ }
+ }
+
+ /**
+ * Considers this {@link Type}. For arrays, the element type is considered.
+ * If the type is an object, it's internal name is considered.
+ */
+ public void considerType(Type t) {
+ if (t != null) {
+ if (t.getSort() == Type.ARRAY) {
+ t = t.getElementType();
+ }
+ if (t.getSort() == Type.OBJECT) {
+ considerName(t.getInternalName());
+ }
+ }
+ }
+
+ /**
+ * Considers a descriptor string. The descriptor is converted to a {@link Type}
+ * and then considerType() is invoked.
+ */
+ public void considerDesc(String desc) {
+ if (desc != null) {
+ try {
+ Type t = Type.getType(desc);
+ considerType(t);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // ignore, not a valid type.
+ }
+ }
+ }
+
+ private String getBaseName(String className) {
+ int pos = className.indexOf('$');
+ if (pos > 0) {
+ return className.substring(0, pos);
+ }
+ return className;
+ }
+
+ // ---------------------------------------------------
+ // --- ClassVisitor, FieldVisitor
+ // ---------------------------------------------------
+
+ // Visits a class header
+ @Override
+ public void visit(int version, int access, String name,
+ String signature, String superName, String[] interfaces) {
+ // signature is the signature of this class. May be null if the class is not a generic
+ // one, and does not extend or implement generic classes or interfaces.
+
+ if (signature != null) {
+ considerSignature(signature);
+ }
+
+ // superName is the internal of name of the super class (see getInternalName).
+ // For interfaces, the super class is Object. May be null but only for the Object class.
+ considerName(superName);
+
+ // interfaces is the internal names of the class's interfaces (see getInternalName).
+ // May be null.
+ considerNames(interfaces);
+ }
+
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // pass
+ }
+
+ // Visits the end of a class
+ @Override
+ public void visitEnd() {
+ // pass
+ }
+
+ private class MyFieldVisitor extends FieldVisitor {
+
+ public MyFieldVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // pass
+ }
+
+ // Visits the end of a class
+ @Override
+ public void visitEnd() {
+ // pass
+ }
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ // desc is the field's descriptor (see Type).
+ considerDesc(desc);
+
+ // signature is the field's signature. May be null if the field's type does not use
+ // generic types.
+ considerSignature(signature);
+
+ return new MyFieldVisitor();
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ // name is the internal name of an inner class (see getInternalName).
+ considerName(name);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ // desc is the method's descriptor (see Type).
+ considerDesc(desc);
+ // signature is the method's signature. May be null if the method parameters, return
+ // type and exceptions do not use generic types.
+ considerSignature(signature);
+
+ return new MyMethodVisitor();
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ // pass
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ // pass
+ }
+
+
+ // ---------------------------------------------------
+ // --- MethodVisitor
+ // ---------------------------------------------------
+
+ private class MyMethodVisitor extends MethodVisitor {
+
+ public MyMethodVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitCode() {
+ // pass
+ }
+
+ // field instruction
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ // name is the field's name.
+ considerName(name);
+ // desc is the field's descriptor (see Type).
+ considerDesc(desc);
+ }
+
+ @Override
+ public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) {
+ // pass
+ }
+
+ @Override
+ public void visitIincInsn(int var, int increment) {
+ // pass -- an IINC instruction
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ // pass -- a zero operand instruction
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ // pass -- a single int operand instruction
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ // pass -- a jump instruction
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ // pass -- a label target
+ }
+
+ // instruction to load a constant from the stack
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (cst instanceof Type) {
+ considerType((Type) cst);
+ }
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ // pass
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc,
+ String signature, Label start, Label end, int index) {
+ // desc is the type descriptor of this local variable.
+ considerDesc(desc);
+ // signature is the type signature of this local variable. May be null if the local
+ // variable type does not use generic types.
+ considerSignature(signature);
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ // pass -- a lookup switch instruction
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ // pass
+ }
+
+ // instruction that invokes a method
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+
+ // owner is the internal name of the method's owner class
+ considerName(owner);
+ // desc is the method's descriptor (see Type).
+ considerDesc(desc);
+ }
+
+ // instruction multianewarray, whatever that is
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+
+ // desc an array type descriptor.
+ considerDesc(desc);
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
+ // pass -- table switch instruction
+
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ // type is the internal name of the type of exceptions handled by the handler,
+ // or null to catch any exceptions (for "finally" blocks).
+ considerName(type);
+ }
+
+ // type instruction
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ // type is the operand of the instruction to be visited. This operand must be the
+ // internal name of an object or array class.
+ considerName(type);
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ // pass -- local variable instruction
+ }
+ }
+
+ private class MySignatureVisitor extends SignatureVisitor {
+
+ public MySignatureVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ // ---------------------------------------------------
+ // --- SignatureVisitor
+ // ---------------------------------------------------
+
+ private String mCurrentSignatureClass = null;
+
+ // Starts the visit of a signature corresponding to a class or interface type
+ @Override
+ public void visitClassType(String name) {
+ mCurrentSignatureClass = name;
+ considerName(name);
+ }
+
+ // Visits an inner class
+ @Override
+ public void visitInnerClassType(String name) {
+ if (mCurrentSignatureClass != null) {
+ mCurrentSignatureClass += "$" + name;
+ considerName(mCurrentSignatureClass);
+ }
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitBaseType(char descriptor) {
+ // pass -- a primitive type, ignored
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitFormalTypeParameter(String name) {
+ // pass
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitTypeVariable(String name) {
+ // pass
+ }
+
+ @Override
+ public void visitTypeArgument() {
+ // pass
+ }
+ }
+
+
+ // ---------------------------------------------------
+ // --- AnnotationVisitor
+ // ---------------------------------------------------
+
+ private class MyAnnotationVisitor extends AnnotationVisitor {
+
+ public MyAnnotationVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ // Visits a primitive value of an annotation
+ @Override
+ public void visit(String name, Object value) {
+ // value is the actual value, whose type must be Byte, Boolean, Character, Short,
+ // Integer, Long, Float, Double, String or Type
+ if (value instanceof Type) {
+ considerType((Type) value);
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ // desc is the class descriptor of the nested annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitEnum(String name, String desc, String value) {
+ // desc is the class descriptor of the enumeration class.
+ considerDesc(desc);
+ }
+ }
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
new file mode 100644
index 0000000..b102561
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -0,0 +1,384 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+/**
+ * Class that generates a new JAR from a list of classes, some of which are to be kept as-is
+ * and some of which are to be stubbed partially or totally.
+ */
+public class AsmGenerator {
+
+ /** Output logger. */
+ private final Log mLog;
+ /** The path of the destination JAR to create. */
+ private final String mOsDestJar;
+ /** List of classes to inject in the final JAR from _this_ archive. */
+ private final Class<?>[] mInjectClasses;
+ /** The set of methods to stub out. */
+ private final Set<String> mStubMethods;
+ /** All classes to output as-is, except if they have native methods. */
+ private Map<String, ClassReader> mKeep;
+ /** All dependencies that must be completely stubbed. */
+ private Map<String, ClassReader> mDeps;
+ /** Counter of number of classes renamed during transform. */
+ private int mRenameCount;
+ /** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */
+ private final HashMap<String, String> mRenameClasses;
+ /** FQCN Names of "old" classes that were NOT renamed. This starts with the full list of
+ * old-FQCN to rename and they get erased as they get renamed. At the end, classes still
+ * left here are not in the code base anymore and thus were not renamed. */
+ private HashSet<String> mClassesNotRenamed;
+ /** A map { FQCN => set { list of return types to delete from the FQCN } }. */
+ private HashMap<String, Set<String>> mDeleteReturns;
+ /** A map { FQCN => set { method names } } of methods to rewrite as delegates.
+ * The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
+ private final HashMap<String, Set<String>> mDelegateMethods;
+ /** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN.
+ * map old-FQCN => new-FQCN */
+ private final HashMap<String, String> mRefactorClasses;
+
+ /**
+ * Creates a new generator that can generate the output JAR with the stubbed classes.
+ *
+ * @param log Output logger.
+ * @param osDestJar The path of the destination JAR to create.
+ * @param createInfo Creation parameters. Must not be null.
+ */
+ public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
+ mLog = log;
+ mOsDestJar = osDestJar;
+ mInjectClasses = createInfo.getInjectedClasses();
+ mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
+
+ // Create the map/set of methods to change to delegates
+ mDelegateMethods = new HashMap<String, Set<String>>();
+ for (String signature : createInfo.getDelegateMethods()) {
+ int pos = signature.indexOf('#');
+ if (pos <= 0 || pos >= signature.length() - 1) {
+ continue;
+ }
+ String className = binaryToInternalClassName(signature.substring(0, pos));
+ String methodName = signature.substring(pos + 1);
+ Set<String> methods = mDelegateMethods.get(className);
+ if (methods == null) {
+ methods = new HashSet<String>();
+ mDelegateMethods.put(className, methods);
+ }
+ methods.add(methodName);
+ }
+ for (String className : createInfo.getDelegateClassNatives()) {
+ className = binaryToInternalClassName(className);
+ Set<String> methods = mDelegateMethods.get(className);
+ if (methods == null) {
+ methods = new HashSet<String>();
+ mDelegateMethods.put(className, methods);
+ }
+ methods.add(DelegateClassAdapter.ALL_NATIVES);
+ }
+
+ // Create the map of classes to rename.
+ mRenameClasses = new HashMap<String, String>();
+ mClassesNotRenamed = new HashSet<String>();
+ String[] renameClasses = createInfo.getRenamedClasses();
+ int n = renameClasses.length;
+ for (int i = 0; i < n; i += 2) {
+ assert i + 1 < n;
+ // The ASM class names uses "/" separators, whereas regular FQCN use "."
+ String oldFqcn = binaryToInternalClassName(renameClasses[i]);
+ String newFqcn = binaryToInternalClassName(renameClasses[i + 1]);
+ mRenameClasses.put(oldFqcn, newFqcn);
+ mClassesNotRenamed.add(oldFqcn);
+ }
+
+ // Create a map of classes to be refactored.
+ mRefactorClasses = new HashMap<String, String>();
+ String[] refactorClasses = createInfo.getJavaPkgClasses();
+ n = refactorClasses.length;
+ for (int i = 0; i < n; i += 2) {
+ assert i + 1 < n;
+ String oldFqcn = binaryToInternalClassName(refactorClasses[i]);
+ String newFqcn = binaryToInternalClassName(refactorClasses[i + 1]);
+ mRefactorClasses.put(oldFqcn, newFqcn);;
+ }
+
+ // create the map of renamed class -> return type of method to delete.
+ mDeleteReturns = new HashMap<String, Set<String>>();
+ String[] deleteReturns = createInfo.getDeleteReturns();
+ Set<String> returnTypes = null;
+ String renamedClass = null;
+ for (String className : deleteReturns) {
+ // if we reach the end of a section, add it to the main map
+ if (className == null) {
+ if (returnTypes != null) {
+ mDeleteReturns.put(renamedClass, returnTypes);
+ }
+
+ renamedClass = null;
+ continue;
+ }
+
+ // if the renamed class is null, this is the beginning of a section
+ if (renamedClass == null) {
+ renamedClass = binaryToInternalClassName(className);
+ continue;
+ }
+
+ // just a standard return type, we add it to the list.
+ if (returnTypes == null) {
+ returnTypes = new HashSet<String>();
+ }
+ returnTypes.add(binaryToInternalClassName(className));
+ }
+ }
+
+ /**
+ * Returns the list of classes that have not been renamed yet.
+ * <p/>
+ * The names are "internal class names" rather than FQCN, i.e. they use "/" instead "."
+ * as package separators.
+ */
+ public Set<String> getClassesNotRenamed() {
+ return mClassesNotRenamed;
+ }
+
+ /**
+ * Utility that returns the internal ASM class name from a fully qualified binary class
+ * name. E.g. it returns android/view/View from android.view.View.
+ */
+ String binaryToInternalClassName(String className) {
+ if (className == null) {
+ return null;
+ } else {
+ return className.replace('.', '/');
+ }
+ }
+
+ /** Sets the map of classes to output as-is, except if they have native methods */
+ public void setKeep(Map<String, ClassReader> keep) {
+ mKeep = keep;
+ }
+
+ /** Sets the map of dependencies that must be completely stubbed */
+ public void setDeps(Map<String, ClassReader> deps) {
+ mDeps = deps;
+ }
+
+ /** Gets the map of classes to output as-is, except if they have native methods */
+ public Map<String, ClassReader> getKeep() {
+ return mKeep;
+ }
+
+ /** Gets the map of dependencies that must be completely stubbed */
+ public Map<String, ClassReader> getDeps() {
+ return mDeps;
+ }
+
+ /** Generates the final JAR */
+ public void generate() throws FileNotFoundException, IOException {
+ TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
+
+ for (Class<?> clazz : mInjectClasses) {
+ String name = classToEntryPath(clazz);
+ InputStream is = ClassLoader.getSystemResourceAsStream(name);
+ ClassReader cr = new ClassReader(is);
+ byte[] b = transform(cr, true /* stubNativesOnly */);
+ name = classNameToEntryPath(transformName(cr.getClassName()));
+ all.put(name, b);
+ }
+
+ for (Entry<String, ClassReader> entry : mDeps.entrySet()) {
+ ClassReader cr = entry.getValue();
+ byte[] b = transform(cr, true /* stubNativesOnly */);
+ String name = classNameToEntryPath(transformName(cr.getClassName()));
+ all.put(name, b);
+ }
+
+ for (Entry<String, ClassReader> entry : mKeep.entrySet()) {
+ ClassReader cr = entry.getValue();
+ byte[] b = transform(cr, true /* stubNativesOnly */);
+ String name = classNameToEntryPath(transformName(cr.getClassName()));
+ all.put(name, b);
+ }
+
+ mLog.info("# deps classes: %d", mDeps.size());
+ mLog.info("# keep classes: %d", mKeep.size());
+ mLog.info("# renamed : %d", mRenameCount);
+
+ createJar(new FileOutputStream(mOsDestJar), all);
+ mLog.info("Created JAR file %s", mOsDestJar);
+ }
+
+ /**
+ * Writes the JAR file.
+ *
+ * @param outStream The file output stream were to write the JAR.
+ * @param all The map of all classes to output.
+ * @throws IOException if an I/O error has occurred
+ */
+ void createJar(FileOutputStream outStream, Map<String,byte[]> all) throws IOException {
+ JarOutputStream jar = new JarOutputStream(outStream);
+ for (Entry<String, byte[]> entry : all.entrySet()) {
+ String name = entry.getKey();
+ JarEntry jar_entry = new JarEntry(name);
+ jar.putNextEntry(jar_entry);
+ jar.write(entry.getValue());
+ jar.closeEntry();
+ }
+ jar.flush();
+ jar.close();
+ }
+
+ /**
+ * Utility method that converts a fully qualified java name into a JAR entry path
+ * e.g. for the input "android.view.View" it returns "android/view/View.class"
+ */
+ String classNameToEntryPath(String className) {
+ return className.replaceAll("\\.", "/").concat(".class");
+ }
+
+ /**
+ * Utility method to get the JAR entry path from a Class name.
+ * e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
+ */
+ private String classToEntryPath(Class<?> clazz) {
+ String name = "";
+ Class<?> parent;
+ while ((parent = clazz.getEnclosingClass()) != null) {
+ name = "$" + clazz.getSimpleName() + name;
+ clazz = parent;
+ }
+ return classNameToEntryPath(clazz.getCanonicalName() + name);
+ }
+
+ /**
+ * Transforms a class.
+ * <p/>
+ * There are 3 kind of transformations:
+ *
+ * 1- For "mock" dependencies classes, we want to remove all code from methods and replace
+ * by a stub. Native methods must be implemented with this stub too. Abstract methods are
+ * left intact. Modified classes must be overridable (non-private, non-final).
+ * Native methods must be made non-final, non-private.
+ *
+ * 2- For "keep" classes, we want to rewrite all native methods as indicated above.
+ * If a class has native methods, it must also be made non-private, non-final.
+ *
+ * Note that unfortunately static methods cannot be changed to non-static (since static and
+ * non-static are invoked differently.)
+ */
+ byte[] transform(ClassReader cr, boolean stubNativesOnly) {
+
+ boolean hasNativeMethods = hasNativeMethods(cr);
+
+ // Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass)
+ String className = cr.getClassName();
+
+ String newName = transformName(className);
+ // transformName returns its input argument if there's no need to rename the class
+ if (newName != className) {
+ mRenameCount++;
+ // This class is being renamed, so remove it from the list of classes not renamed.
+ mClassesNotRenamed.remove(className);
+ }
+
+ mLog.debug("Transform %s%s%s%s", className,
+ newName == className ? "" : " (renamed to " + newName + ")",
+ hasNativeMethods ? " -- has natives" : "",
+ stubNativesOnly ? " -- stub natives only" : "");
+
+ // Rewrite the new class from scratch, without reusing the constant pool from the
+ // original class reader.
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+
+ ClassVisitor cv = new RefactorClassAdapter(cw, mRefactorClasses);
+ if (newName != className) {
+ cv = new RenameClassAdapter(cv, className, newName);
+ }
+
+ cv = new TransformClassAdapter(mLog, mStubMethods,
+ mDeleteReturns.get(className),
+ newName, cv,
+ stubNativesOnly, stubNativesOnly || hasNativeMethods);
+
+ Set<String> delegateMethods = mDelegateMethods.get(className);
+ if (delegateMethods != null && !delegateMethods.isEmpty()) {
+ // If delegateMethods only contains one entry ALL_NATIVES and the class is
+ // known to have no native methods, just skip this step.
+ if (hasNativeMethods ||
+ !(delegateMethods.size() == 1 &&
+ delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) {
+ cv = new DelegateClassAdapter(mLog, cv, className, delegateMethods);
+ }
+ }
+
+ cr.accept(cv, 0 /* flags */);
+ return cw.toByteArray();
+ }
+
+ /**
+ * Should this class be renamed, this returns the new name. Otherwise it returns the
+ * original name.
+ *
+ * @param className The internal ASM name of the class that may have to be renamed
+ * @return A new transformed name or the original input argument.
+ */
+ String transformName(String className) {
+ String newName = mRenameClasses.get(className);
+ if (newName != null) {
+ return newName;
+ }
+ int pos = className.indexOf('$');
+ if (pos > 0) {
+ // Is this an inner class of a renamed class?
+ String base = className.substring(0, pos);
+ newName = mRenameClasses.get(base);
+ if (newName != null) {
+ return newName + className.substring(pos);
+ }
+ }
+
+ return className;
+ }
+
+ /**
+ * Returns true if a class has any native methods.
+ */
+ boolean hasNativeMethods(ClassReader cr) {
+ ClassHasNativeVisitor cv = new ClassHasNativeVisitor();
+ cr.accept(cv, 0 /* flags */);
+ return cv.hasNativeMethods();
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
new file mode 100644
index 0000000..2c955fd
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Indicates if a class contains any native methods.
+ */
+public class ClassHasNativeVisitor extends ClassVisitor {
+ public ClassHasNativeVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ private boolean mHasNativeMethods = false;
+
+ public boolean hasNativeMethods() {
+ return mHasNativeMethods;
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected void setHasNativeMethods(boolean hasNativeMethods, String methodName) {
+ mHasNativeMethods = hasNativeMethods;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ // pass
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // pass
+ }
+
+ @Override
+ public void visitEnd() {
+ // pass
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ // pass
+ return null;
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName,
+ String innerName, int access) {
+ // pass
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ if ((access & Opcodes.ACC_NATIVE) != 0) {
+ setHasNativeMethods(true, name);
+ }
+ return null;
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ // pass
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ // pass
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
new file mode 100644
index 0000000..f6779e3
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -0,0 +1,243 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.tools.layoutlib.java.AutoCloseable;
+import com.android.tools.layoutlib.java.Charsets;
+import com.android.tools.layoutlib.java.IntegralToString;
+import com.android.tools.layoutlib.java.Objects;
+import com.android.tools.layoutlib.java.UnsafeByteSequence;
+
+/**
+ * Describes the work to be done by {@link AsmGenerator}.
+ */
+public final class CreateInfo implements ICreateInfo {
+
+ /**
+ * Returns the list of class from layoutlib_create to inject in layoutlib.
+ * The list can be empty but must not be null.
+ */
+ @Override
+ public Class<?>[] getInjectedClasses() {
+ return INJECTED_CLASSES;
+ }
+
+ /**
+ * Returns the list of methods to rewrite as delegates.
+ * The list can be empty but must not be null.
+ */
+ @Override
+ public String[] getDelegateMethods() {
+ return DELEGATE_METHODS;
+ }
+
+ /**
+ * Returns the list of classes on which to delegate all native methods.
+ * The list can be empty but must not be null.
+ */
+ @Override
+ public String[] getDelegateClassNatives() {
+ return DELEGATE_CLASS_NATIVES;
+ }
+
+ /**
+ * Returns The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * The list can be empty but must not be null.
+ * <p/>
+ * This usage is deprecated. Please use method 'delegates' instead.
+ */
+ @Override
+ public String[] getOverriddenMethods() {
+ return OVERRIDDEN_METHODS;
+ }
+
+ /**
+ * Returns the list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ * The list can be empty but must not be null.
+ */
+ @Override
+ public String[] getRenamedClasses() {
+ return RENAMED_CLASSES;
+ }
+
+ /**
+ * Returns the list of classes for which the methods returning them should be deleted.
+ * The array contains a list of null terminated section starting with the name of the class
+ * to rename in which the methods are deleted, followed by a list of return types identifying
+ * the methods to delete.
+ * The list can be empty but must not be null.
+ */
+ @Override
+ public String[] getDeleteReturns() {
+ return DELETE_RETURNS;
+ }
+
+ /**
+ * Returns the list of classes to refactor, must be an even list: the binary FQCN of class to
+ * replace followed by the new FQCN. All references to the old class should be updated to the
+ * new class. The list can be empty but must not be null.
+ */
+ @Override
+ public String[] getJavaPkgClasses() {
+ return JAVA_PKG_CLASSES;
+ }
+ //-----
+
+ /**
+ * The list of class from layoutlib_create to inject in layoutlib.
+ */
+ private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
+ OverrideMethod.class,
+ MethodListener.class,
+ MethodAdapter.class,
+ ICreateInfo.class,
+ CreateInfo.class,
+ LayoutlibDelegate.class,
+ /* Java package classes */
+ AutoCloseable.class,
+ Objects.class,
+ IntegralToString.class,
+ UnsafeByteSequence.class,
+ Charsets.class,
+ };
+
+ /**
+ * The list of methods to rewrite as delegates.
+ */
+ public final static String[] DELEGATE_METHODS = new String[] {
+ "android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
+ "android.content.res.Resources$Theme#obtainStyledAttributes",
+ "android.content.res.Resources$Theme#resolveAttribute",
+ "android.content.res.TypedArray#getValueAt",
+ "android.graphics.BitmapFactory#finishDecode",
+ "android.os.Handler#sendMessageAtTime",
+ "android.os.HandlerThread#run",
+ "android.os.Build#getString",
+ "android.text.format.DateFormat#is24HourFormat",
+ "android.text.format.Time#format1",
+ "android.view.Choreographer#getRefreshRate",
+ "android.view.Display#updateDisplayInfoLocked",
+ "android.view.LayoutInflater#rInflate",
+ "android.view.LayoutInflater#parseInclude",
+ "android.view.View#isInEditMode",
+ "android.view.ViewRootImpl#isInTouchMode",
+ "android.view.WindowManagerGlobal#getWindowManagerService",
+ "android.view.inputmethod.InputMethodManager#getInstance",
+ "com.android.internal.util.XmlUtils#convertValueToInt",
+ "com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
+ };
+
+ /**
+ * The list of classes on which to delegate all native methods.
+ */
+ public final static String[] DELEGATE_CLASS_NATIVES = new String[] {
+ "android.animation.PropertyValuesHolder",
+ "android.graphics.AvoidXfermode",
+ "android.graphics.Bitmap",
+ "android.graphics.BitmapFactory",
+ "android.graphics.BitmapShader",
+ "android.graphics.BlurMaskFilter",
+ "android.graphics.Canvas",
+ "android.graphics.ColorFilter",
+ "android.graphics.ColorMatrixColorFilter",
+ "android.graphics.ComposePathEffect",
+ "android.graphics.ComposeShader",
+ "android.graphics.CornerPathEffect",
+ "android.graphics.DashPathEffect",
+ "android.graphics.DiscretePathEffect",
+ "android.graphics.DrawFilter",
+ "android.graphics.EmbossMaskFilter",
+ "android.graphics.LayerRasterizer",
+ "android.graphics.LightingColorFilter",
+ "android.graphics.LinearGradient",
+ "android.graphics.MaskFilter",
+ "android.graphics.Matrix",
+ "android.graphics.NinePatch",
+ "android.graphics.Paint",
+ "android.graphics.PaintFlagsDrawFilter",
+ "android.graphics.Path",
+ "android.graphics.PathDashPathEffect",
+ "android.graphics.PathEffect",
+ "android.graphics.PixelXorXfermode",
+ "android.graphics.PorterDuffColorFilter",
+ "android.graphics.PorterDuffXfermode",
+ "android.graphics.RadialGradient",
+ "android.graphics.Rasterizer",
+ "android.graphics.Region",
+ "android.graphics.Shader",
+ "android.graphics.SumPathEffect",
+ "android.graphics.SweepGradient",
+ "android.graphics.Typeface",
+ "android.graphics.Xfermode",
+ "android.os.SystemClock",
+ "android.text.AndroidBidi",
+ "android.util.FloatMath",
+ "android.view.Display",
+ "libcore.icu.DateIntervalFormat",
+ "libcore.icu.ICU",
+ };
+
+ /**
+ * The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * This usage is deprecated. Please use method 'delegates' instead.
+ */
+ private final static String[] OVERRIDDEN_METHODS = new String[] {
+ };
+
+ /**
+ * The list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ */
+ private final static String[] RENAMED_CLASSES =
+ new String[] {
+ "android.os.ServiceManager", "android.os._Original_ServiceManager",
+ "android.util.LruCache", "android.util._Original_LruCache",
+ "android.view.SurfaceView", "android.view._Original_SurfaceView",
+ "android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager",
+ "android.webkit.WebView", "android.webkit._Original_WebView",
+ "com.android.internal.policy.PolicyManager", "com.android.internal.policy._Original_PolicyManager",
+ };
+
+ /**
+ * The list of class references to update, must be an even list: the binary
+ * FQCN of class to replace followed by the new FQCN. The classes to
+ * replace are to be excluded from the output.
+ */
+ private final static String[] JAVA_PKG_CLASSES =
+ new String[] {
+ "java.lang.AutoCloseable", "com.android.tools.layoutlib.java.AutoCloseable",
+ "java.util.Objects", "com.android.tools.layoutlib.java.Objects",
+ "java.nio.charset.Charsets", "com.android.tools.layoutlib.java.Charsets",
+ "java.lang.IntegralToString", "com.android.tools.layoutlib.java.IntegralToString",
+ "java.lang.UnsafeByteSequence", "com.android.tools.layoutlib.java.UnsafeByteSequence",
+ };
+
+ /**
+ * List of classes for which the methods returning them should be deleted.
+ * The array contains a list of null terminated section starting with the name of the class
+ * to rename in which the methods are deleted, followed by a list of return types identifying
+ * the methods to delete.
+ */
+ private final static String[] DELETE_RETURNS =
+ new String[] {
+ null }; // separator, for next class/methods list.
+}
+
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
new file mode 100644
index 0000000..927be97
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2010 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.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Set;
+
+/**
+ * A {@link DelegateClassAdapter} can transform some methods from a class into
+ * delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ */
+public class DelegateClassAdapter extends ClassVisitor {
+
+ /** Suffix added to original methods. */
+ private static final String ORIGINAL_SUFFIX = "_Original";
+ private static String CONSTRUCTOR = "<init>";
+ private static String CLASS_INIT = "<clinit>";
+
+ public final static String ALL_NATIVES = "<<all_natives>>";
+
+ private final String mClassName;
+ private final Set<String> mDelegateMethods;
+ private final Log mLog;
+
+ /**
+ * Creates a new {@link DelegateClassAdapter} that can transform some methods
+ * from a class into delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ *
+ * @param log The logger object. Must not be null.
+ * @param cv the class visitor to which this adapter must delegate calls.
+ * @param className The internal class name of the class to visit,
+ * e.g. <code>com/android/SomeClass$InnerClass</code>.
+ * @param delegateMethods The set of method names to modify and/or the
+ * special constant {@link #ALL_NATIVES} to convert all native methods.
+ */
+ public DelegateClassAdapter(Log log,
+ ClassVisitor cv,
+ String className,
+ Set<String> delegateMethods) {
+ super(Opcodes.ASM4, cv);
+ mLog = log;
+ mClassName = className;
+ mDelegateMethods = delegateMethods;
+ }
+
+ //----------------------------------
+ // Methods from the ClassAdapter
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+
+ boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
+
+ boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
+ mDelegateMethods.contains(name);
+
+ if (!useDelegate) {
+ // Not creating a delegate for this method, pass it as-is from the reader
+ // to the writer.
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+
+ if (useDelegate) {
+ if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) {
+ // We don't currently support generating delegates for constructors.
+ throw new UnsupportedOperationException(
+ String.format(
+ "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$
+ mClassName, name, desc));
+ }
+ }
+
+ if (isNative) {
+ // Remove native flag
+ access = access & ~Opcodes.ACC_NATIVE;
+ MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
+
+ DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
+ mLog, null /*mwOriginal*/, mwDelegate, mClassName, name, desc, isStatic);
+
+ // A native has no code to visit, so we need to generate it directly.
+ a.generateDelegateCode();
+
+ return mwDelegate;
+ }
+
+ // Given a non-native SomeClass.MethodName(), we want to generate 2 methods:
+ // - A copy of the original method named SomeClass.MethodName_Original().
+ // The content is the original method as-is from the reader.
+ // - A brand new implementation of SomeClass.MethodName() which calls to a
+ // non-existing method named SomeClass_Delegate.MethodName().
+ // The implementation of this 'delegate' method is done in layoutlib_brigde.
+
+ int accessDelegate = access;
+ // change access to public for the original one
+ if (Main.sOptions.generatePublicAccess) {
+ access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
+ access |= Opcodes.ACC_PUBLIC;
+ }
+
+ MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX,
+ desc, signature, exceptions);
+ MethodVisitor mwDelegate = super.visitMethod(accessDelegate, name,
+ desc, signature, exceptions);
+
+ DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
+ mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic);
+ return a;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java
new file mode 100644
index 0000000..0000b22
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2010 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.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.ArrayList;
+
+/**
+ * This method adapter generates delegate methods.
+ * <p/>
+ * Given a method {@code SomeClass.MethodName()}, this generates 1 or 2 methods:
+ * <ul>
+ * <li> A copy of the original method named {@code SomeClass.MethodName_Original()}.
+ * The content is the original method as-is from the reader.
+ * This step is omitted if the method is native, since it has no Java implementation.
+ * <li> A brand new implementation of {@code SomeClass.MethodName()} which calls to a
+ * non-existing method named {@code SomeClass_Delegate.MethodName()}.
+ * The implementation of this 'delegate' method is done in layoutlib_brigde.
+ * </ul>
+ * A method visitor is generally constructed to generate a single method; however
+ * here we might want to generate one or two depending on the context. To achieve
+ * that, the visitor here generates the 'original' method and acts as a no-op if
+ * no such method exists (e.g. when the original is a native method).
+ * The delegate method is generated after the {@code visitEnd} of the original method
+ * or by having the class adapter <em>directly</em> call {@link #generateDelegateCode()}
+ * for native methods.
+ * <p/>
+ * When generating the 'delegate', the implementation generates a call to a class
+ * class named <code><className>_Delegate</code> with static methods matching
+ * the methods to be overridden here. The methods have the same return type.
+ * The argument type list is the same except the "this" reference is passed first
+ * for non-static methods.
+ * <p/>
+ * A new annotation is added to these 'delegate' methods so that we can easily find them
+ * for automated testing.
+ * <p/>
+ * This class isn't intended to be generic or reusable.
+ * It is called by {@link DelegateClassAdapter}, which takes care of properly initializing
+ * the two method writers for the original and the delegate class, as needed, with their
+ * expected names.
+ * <p/>
+ * The class adapter also takes care of calling {@link #generateDelegateCode()} directly for
+ * a native and use the visitor pattern for non-natives.
+ * Note that native methods have, by definition, no code so there's nothing a visitor
+ * can visit.
+ * <p/>
+ * Instances of this class are not re-usable.
+ * The class adapter creates a new instance for each method.
+ */
+class DelegateMethodAdapter2 extends MethodVisitor {
+
+ /** Suffix added to delegate classes. */
+ public static final String DELEGATE_SUFFIX = "_Delegate";
+
+ /** The parent method writer to copy of the original method.
+ * Null when dealing with a native original method. */
+ private MethodVisitor mOrgWriter;
+ /** The parent method writer to generate the delegating method. Never null. */
+ private MethodVisitor mDelWriter;
+ /** The original method descriptor (return type + argument types.) */
+ private String mDesc;
+ /** True if the original method is static. */
+ private final boolean mIsStatic;
+ /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
+ private final String mClassName;
+ /** The method name. */
+ private final String mMethodName;
+ /** Logger object. */
+ private final Log mLog;
+
+ /** Array used to capture the first line number information from the original method
+ * and duplicate it in the delegate. */
+ private Object[] mDelegateLineNumber;
+
+ /**
+ * Creates a new {@link DelegateMethodAdapter2} that will transform this method
+ * into a delegate call.
+ * <p/>
+ * See {@link DelegateMethodAdapter2} for more details.
+ *
+ * @param log The logger object. Must not be null.
+ * @param mvOriginal The parent method writer to copy of the original method.
+ * Must be {@code null} when dealing with a native original method.
+ * @param mvDelegate The parent method writer to generate the delegating method.
+ * Must never be null.
+ * @param className The internal class name of the class to visit,
+ * e.g. <code>com/android/SomeClass$InnerClass</code>.
+ * @param methodName The simple name of the method.
+ * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
+ * {@link Type#getArgumentTypes(String)})
+ * @param isStatic True if the method is declared static.
+ */
+ public DelegateMethodAdapter2(Log log,
+ MethodVisitor mvOriginal,
+ MethodVisitor mvDelegate,
+ String className,
+ String methodName,
+ String desc,
+ boolean isStatic) {
+ super(Opcodes.ASM4);
+ mLog = log;
+ mOrgWriter = mvOriginal;
+ mDelWriter = mvDelegate;
+ mClassName = className;
+ mMethodName = methodName;
+ mDesc = desc;
+ mIsStatic = isStatic;
+ }
+
+ /**
+ * Generates the new code for the method.
+ * <p/>
+ * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
+ * (since they have no code to visit).
+ * <p/>
+ * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
+ * return this instance of {@link DelegateMethodAdapter2} and let the normal visitor pattern
+ * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
+ * this method will be invoked from {@link MethodVisitor#visitEnd()}.
+ */
+ public void generateDelegateCode() {
+ /*
+ * The goal is to generate a call to a static delegate method.
+ * If this method is non-static, the first parameter will be 'this'.
+ * All the parameters must be passed and then the eventual return type returned.
+ *
+ * Example, let's say we have a method such as
+ * public void myMethod(int a, Object b, ArrayList<String> c) { ... }
+ *
+ * We'll want to create a body that calls a delegate method like this:
+ * TheClass_Delegate.myMethod(this, a, b, c);
+ *
+ * If the method is non-static and the class name is an inner class (e.g. has $ in its
+ * last segment), we want to push the 'this' of the outer class first:
+ * OuterClass_InnerClass_Delegate.myMethod(
+ * OuterClass.this,
+ * OuterClass$InnerClass.this,
+ * a, b, c);
+ *
+ * Only one level of inner class is supported right now, for simplicity and because
+ * we don't need more.
+ *
+ * The generated class name is the current class name with "_Delegate" appended to it.
+ * One thing to realize is that we don't care about generics -- since generic types
+ * are erased at build time, they have no influence on the method name being called.
+ */
+
+ // Add our annotation
+ AnnotationVisitor aw = mDelWriter.visitAnnotation(
+ Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
+ true); // visible at runtime
+ if (aw != null) {
+ aw.visitEnd();
+ }
+
+ mDelWriter.visitCode();
+
+ if (mDelegateLineNumber != null) {
+ Object[] p = mDelegateLineNumber;
+ mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]);
+ }
+
+ ArrayList<Type> paramTypes = new ArrayList<Type>();
+ String delegateClassName = mClassName + DELEGATE_SUFFIX;
+ boolean pushedArg0 = false;
+ int maxStack = 0;
+
+ // Check if the last segment of the class name has inner an class.
+ // Right now we only support one level of inner classes.
+ Type outerType = null;
+ int slash = mClassName.lastIndexOf('/');
+ int dol = mClassName.lastIndexOf('$');
+ if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) {
+ String outerClass = mClassName.substring(0, dol);
+ outerType = Type.getObjectType(outerClass);
+
+ // Change a delegate class name to "com/foo/Outer_Inner_Delegate"
+ delegateClassName = delegateClassName.replace('$', '_');
+ }
+
+ // For an instance method (e.g. non-static), push the 'this' preceded
+ // by the 'this' of any outer class, if any.
+ if (!mIsStatic) {
+
+ if (outerType != null) {
+ // The first-level inner class has a package-protected member called 'this$0'
+ // that points to the outer class.
+
+ // Push this.getField("this$0") on the call stack.
+ mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
+ mDelWriter.visitFieldInsn(Opcodes.GETFIELD,
+ mClassName, // class where the field is defined
+ "this$0", // field name
+ outerType.getDescriptor()); // type of the field
+ maxStack++;
+ paramTypes.add(outerType);
+
+ }
+
+ // Push "this" for the instance method, which is always ALOAD 0
+ mDelWriter.visitVarInsn(Opcodes.ALOAD, 0);
+ maxStack++;
+ pushedArg0 = true;
+ paramTypes.add(Type.getObjectType(mClassName));
+ }
+
+ // Push all other arguments. Start at arg 1 if we already pushed 'this' above.
+ Type[] argTypes = Type.getArgumentTypes(mDesc);
+ int maxLocals = pushedArg0 ? 1 : 0;
+ for (Type t : argTypes) {
+ int size = t.getSize();
+ mDelWriter.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals);
+ maxLocals += size;
+ maxStack += size;
+ paramTypes.add(t);
+ }
+
+ // Construct the descriptor of the delegate based on the parameters
+ // we pushed on the call stack. The return type remains unchanged.
+ String desc = Type.getMethodDescriptor(
+ Type.getReturnType(mDesc),
+ paramTypes.toArray(new Type[paramTypes.size()]));
+
+ // Invoke the static delegate
+ mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC,
+ delegateClassName,
+ mMethodName,
+ desc);
+
+ Type returnType = Type.getReturnType(mDesc);
+ mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+
+ mDelWriter.visitMaxs(maxStack, maxLocals);
+ mDelWriter.visitEnd();
+
+ // For debugging now. Maybe we should collect these and store them in
+ // a text file for helping create the delegates. We could also compare
+ // the text file to a golden and break the build on unsupported changes
+ // or regressions. Even better we could fancy-print something that looks
+ // like the expected Java method declaration.
+ mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
+ }
+
+ /* Pass down to visitor writer. In this implementation, either do nothing. */
+ @Override
+ public void visitCode() {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitCode();
+ }
+ }
+
+ /*
+ * visitMaxs is called just before visitEnd if there was any code to rewrite.
+ */
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitMaxs(maxStack, maxLocals);
+ }
+ }
+
+ /** End of visiting. Generate the delegating code. */
+ @Override
+ public void visitEnd() {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitEnd();
+ }
+ generateDelegateCode();
+ }
+
+ /* Writes all annotation from the original method. */
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (mOrgWriter != null) {
+ return mOrgWriter.visitAnnotation(desc, visible);
+ } else {
+ return null;
+ }
+ }
+
+ /* Writes all annotation default values from the original method. */
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ if (mOrgWriter != null) {
+ return mOrgWriter.visitAnnotationDefault();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ if (mOrgWriter != null) {
+ return mOrgWriter.visitParameterAnnotation(parameter, desc, visible);
+ } else {
+ return null;
+ }
+ }
+
+ /* Writes all attributes from the original method. */
+ @Override
+ public void visitAttribute(Attribute attr) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitAttribute(attr);
+ }
+ }
+
+ /*
+ * Only writes the first line number present in the original code so that source
+ * viewers can direct to the correct method, even if the content doesn't match.
+ */
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ // Capture the first line values for the new delegate method
+ if (mDelegateLineNumber == null) {
+ mDelegateLineNumber = new Object[] { line, start };
+ }
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLineNumber(line, start);
+ }
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitInsn(opcode);
+ }
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLabel(label);
+ }
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitTryCatchBlock(start, end, handler, type);
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitMethodInsn(opcode, owner, name, desc);
+ }
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitFieldInsn(opcode, owner, name, desc);
+ }
+ }
+
+ @Override
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitFrame(type, nLocal, local, nStack, stack);
+ }
+ }
+
+ @Override
+ public void visitIincInsn(int var, int increment) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitIincInsn(var, increment);
+ }
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitIntInsn(opcode, operand);
+ }
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitJumpInsn(opcode, label);
+ }
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLdcInsn(cst);
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLookupSwitchInsn(dflt, keys, labels);
+ }
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitMultiANewArrayInsn(desc, dims);
+ }
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitTypeInsn(opcode, type);
+ }
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitVarInsn(opcode, var);
+ }
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
new file mode 100644
index 0000000..c988c70
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
@@ -0,0 +1,787 @@
+/*
+ * Copyright (C) 2012 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.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Analyzes the input JAR using the ASM java bytecode manipulation library
+ * to list the classes and their dependencies. A "dependency" is a class
+ * used by another class.
+ */
+public class DependencyFinder {
+
+ // Note: a bunch of stuff has package-level access for unit tests. Consider it private.
+
+ /** Output logger. */
+ private final Log mLog;
+
+ /**
+ * Creates a new analyzer.
+ *
+ * @param log The log output.
+ */
+ public DependencyFinder(Log log) {
+ mLog = log;
+ }
+
+ /**
+ * Starts the analysis using parameters from the constructor.
+ *
+ * @param osJarPath The input source JARs to parse.
+ * @return A pair: [0]: map { class FQCN => set of FQCN class dependencies }.
+ * [1]: map { missing class FQCN => set of FQCN class that uses it. }
+ */
+ public List<Map<String, Set<String>>> findDeps(List<String> osJarPath) throws IOException {
+
+ Map<String, ClassReader> zipClasses = parseZip(osJarPath);
+ mLog.info("Found %d classes in input JAR%s.",
+ zipClasses.size(),
+ osJarPath.size() > 1 ? "s" : "");
+
+ Map<String, Set<String>> deps = findClassesDeps(zipClasses);
+
+ Map<String, Set<String>> missing = findMissingClasses(deps, zipClasses.keySet());
+
+ List<Map<String, Set<String>>> result = new ArrayList<Map<String,Set<String>>>(2);
+ result.add(deps);
+ result.add(missing);
+ return result;
+ }
+
+ /**
+ * Prints dependencies to the current logger, found stuff and missing stuff.
+ */
+ public void printAllDeps(List<Map<String, Set<String>>> result) {
+ assert result.size() == 2;
+ Map<String, Set<String>> deps = result.get(0);
+ Map<String, Set<String>> missing = result.get(1);
+
+ // Print all dependences found in the format:
+ // +Found: <FQCN from zip>
+ // uses: FQCN
+
+ mLog.info("++++++ %d Entries found in source JARs", deps.size());
+ mLog.info("");
+
+ for (Entry<String, Set<String>> entry : deps.entrySet()) {
+ mLog.info( "+Found : %s", entry.getKey());
+ for (String dep : entry.getValue()) {
+ mLog.info(" uses: %s", dep);
+ }
+
+ mLog.info("");
+ }
+
+
+ // Now print all missing dependences in the format:
+ // -Missing <FQCN>:
+ // used by: <FQCN>
+
+ mLog.info("");
+ mLog.info("------ %d Entries missing from source JARs", missing.size());
+ mLog.info("");
+
+ for (Entry<String, Set<String>> entry : missing.entrySet()) {
+ mLog.info( "-Missing : %s", entry.getKey());
+ for (String dep : entry.getValue()) {
+ mLog.info(" used by: %s", dep);
+ }
+
+ mLog.info("");
+ }
+ }
+
+ /**
+ * Prints only a summary of the missing dependencies to the current logger.
+ */
+ public void printMissingDeps(List<Map<String, Set<String>>> result) {
+ assert result.size() == 2;
+ @SuppressWarnings("unused") Map<String, Set<String>> deps = result.get(0);
+ Map<String, Set<String>> missing = result.get(1);
+
+ for (String fqcn : missing.keySet()) {
+ mLog.info("%s", fqcn);
+ }
+ }
+
+ // ----------------
+
+ /**
+ * Parses a JAR file and returns a list of all classes founds using a map
+ * class name => ASM ClassReader. Class names are in the form "android.view.View".
+ */
+ Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
+ TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
+
+ for (String jarPath : jarPathList) {
+ ZipFile zip = new ZipFile(jarPath);
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ ZipEntry entry;
+ while (entries.hasMoreElements()) {
+ entry = entries.nextElement();
+ if (entry.getName().endsWith(".class")) {
+ ClassReader cr = new ClassReader(zip.getInputStream(entry));
+ String className = classReaderToClassName(cr);
+ classes.put(className, cr);
+ }
+ }
+ }
+
+ return classes;
+ }
+
+ /**
+ * Utility that returns the fully qualified binary class name for a ClassReader.
+ * E.g. it returns something like android.view.View.
+ */
+ static String classReaderToClassName(ClassReader classReader) {
+ if (classReader == null) {
+ return null;
+ } else {
+ return classReader.getClassName().replace('/', '.');
+ }
+ }
+
+ /**
+ * Utility that returns the fully qualified binary class name from a path-like FQCN.
+ * E.g. it returns android.view.View from android/view/View.
+ */
+ static String internalToBinaryClassName(String className) {
+ if (className == null) {
+ return null;
+ } else {
+ return className.replace('/', '.');
+ }
+ }
+
+ /**
+ * Finds all dependencies for all classes in keepClasses which are also
+ * listed in zipClasses. Returns a map of all the dependencies found.
+ */
+ Map<String, Set<String>> findClassesDeps(Map<String, ClassReader> zipClasses) {
+
+ // The dependencies that we'll collect.
+ // It's a map Class name => uses class names.
+ Map<String, Set<String>> dependencyMap = new TreeMap<String, Set<String>>();
+
+ DependencyVisitor visitor = getVisitor();
+
+ int count = 0;
+ try {
+ for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
+ String name = entry.getKey();
+
+ TreeSet<String> set = new TreeSet<String>();
+ dependencyMap.put(name, set);
+ visitor.setDependencySet(set);
+
+ ClassReader cr = entry.getValue();
+ cr.accept(visitor, 0 /* flags */);
+
+ visitor.setDependencySet(null);
+
+ mLog.debugNoln("Visited %d classes\r", ++count);
+ }
+ } finally {
+ mLog.debugNoln("\n");
+ }
+
+ return dependencyMap;
+ }
+
+ /**
+ * Computes which classes FQCN were found as dependencies that are NOT listed
+ * in the original JAR classes.
+ *
+ * @param deps The map { FQCN => dependencies[] } returned by {@link #findClassesDeps(Map)}.
+ * @param zipClasses The set of all classes FQCN found in the JAR files.
+ * @return A map { FQCN not found in the zipClasses => classes using it }
+ */
+ private Map<String, Set<String>> findMissingClasses(
+ Map<String, Set<String>> deps,
+ Set<String> zipClasses) {
+ Map<String, Set<String>> missing = new TreeMap<String, Set<String>>();
+
+ for (Entry<String, Set<String>> entry : deps.entrySet()) {
+ String name = entry.getKey();
+
+ for (String dep : entry.getValue()) {
+ if (!zipClasses.contains(dep)) {
+ // This dependency doesn't exist in the zip classes.
+ Set<String> set = missing.get(dep);
+ if (set == null) {
+ set = new TreeSet<String>();
+ missing.put(dep, set);
+ }
+ set.add(name);
+ }
+ }
+
+ }
+
+ return missing;
+ }
+
+
+ // ----------------------------------
+
+ /**
+ * Instantiates a new DependencyVisitor. Useful for unit tests.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ DependencyVisitor getVisitor() {
+ return new DependencyVisitor();
+ }
+
+ /**
+ * Visitor to collect all the type dependencies from a class.
+ */
+ public class DependencyVisitor extends ClassVisitor {
+
+ private Set<String> mCurrentDepSet;
+
+ /**
+ * Creates a new visitor that will find all the dependencies for the visited class.
+ */
+ public DependencyVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ /**
+ * Sets the {@link Set} where to record direct dependencies for this class.
+ * This will change before each {@link ClassReader#accept(ClassVisitor, int)} call.
+ */
+ public void setDependencySet(Set<String> set) {
+ mCurrentDepSet = set;
+ }
+
+ /**
+ * Considers the given class name as a dependency.
+ */
+ public void considerName(String className) {
+ if (className == null) {
+ return;
+ }
+
+ className = internalToBinaryClassName(className);
+
+ try {
+ // exclude classes that are part of the default JRE (the one executing this program)
+ if (getClass().getClassLoader().loadClass(className) != null) {
+ return;
+ }
+ } catch (ClassNotFoundException e) {
+ // ignore
+ }
+
+ // Add it to the dependency set for the currently visited class, as needed.
+ assert mCurrentDepSet != null;
+ if (mCurrentDepSet != null) {
+ mCurrentDepSet.add(className);
+ }
+ }
+
+ /**
+ * Considers this array of names using considerName().
+ */
+ public void considerNames(String[] classNames) {
+ if (classNames != null) {
+ for (String className : classNames) {
+ considerName(className);
+ }
+ }
+ }
+
+ /**
+ * Considers this signature or type signature by invoking the {@link SignatureVisitor}
+ * on it.
+ */
+ public void considerSignature(String signature) {
+ if (signature != null) {
+ SignatureReader sr = new SignatureReader(signature);
+ // SignatureReader.accept will call accessType so we don't really have
+ // to differentiate where the signature comes from.
+ sr.accept(new MySignatureVisitor());
+ }
+ }
+
+ /**
+ * Considers this {@link Type}. For arrays, the element type is considered.
+ * If the type is an object, it's internal name is considered.
+ */
+ public void considerType(Type t) {
+ if (t != null) {
+ if (t.getSort() == Type.ARRAY) {
+ t = t.getElementType();
+ }
+ if (t.getSort() == Type.OBJECT) {
+ considerName(t.getInternalName());
+ }
+ }
+ }
+
+ /**
+ * Considers a descriptor string. The descriptor is converted to a {@link Type}
+ * and then considerType() is invoked.
+ */
+ public boolean considerDesc(String desc) {
+ if (desc != null) {
+ try {
+ if (desc.length() > 0 && desc.charAt(0) == '(') {
+ // This is a method descriptor with arguments and a return type.
+ Type t = Type.getReturnType(desc);
+ considerType(t);
+
+ for (Type arg : Type.getArgumentTypes(desc)) {
+ considerType(arg);
+ }
+
+ } else {
+ Type t = Type.getType(desc);
+ considerType(t);
+ }
+ return true;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // ignore, not a valid type.
+ }
+ }
+ return false;
+ }
+
+
+ // ---------------------------------------------------
+ // --- ClassVisitor, FieldVisitor
+ // ---------------------------------------------------
+
+ // Visits a class header
+ @Override
+ public void visit(int version, int access, String name,
+ String signature, String superName, String[] interfaces) {
+ // signature is the signature of this class. May be null if the class is not a generic
+ // one, and does not extend or implement generic classes or interfaces.
+
+ if (signature != null) {
+ considerSignature(signature);
+ }
+
+ // superName is the internal of name of the super class (see getInternalName).
+ // For interfaces, the super class is Object. May be null but only for the Object class.
+ considerName(superName);
+
+ // interfaces is the internal names of the class's interfaces (see getInternalName).
+ // May be null.
+ considerNames(interfaces);
+ }
+
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // pass
+ }
+
+ // Visits the end of a class
+ @Override
+ public void visitEnd() {
+ // pass
+ }
+
+ private class MyFieldVisitor extends FieldVisitor {
+
+ public MyFieldVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // pass
+ }
+
+ // Visits the end of a class
+ @Override
+ public void visitEnd() {
+ // pass
+ }
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ // desc is the field's descriptor (see Type).
+ considerDesc(desc);
+
+ // signature is the field's signature. May be null if the field's type does not use
+ // generic types.
+ considerSignature(signature);
+
+ return new MyFieldVisitor();
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ // name is the internal name of an inner class (see getInternalName).
+ // Note: outerName/innerName seems to be null when we're reading the
+ // _Original_ClassName classes generated by layoutlib_create.
+ if (outerName != null) {
+ considerName(name);
+ }
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ // desc is the method's descriptor (see Type).
+ considerDesc(desc);
+ // signature is the method's signature. May be null if the method parameters, return
+ // type and exceptions do not use generic types.
+ considerSignature(signature);
+
+ return new MyMethodVisitor();
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ // pass
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ // pass
+ }
+
+
+ // ---------------------------------------------------
+ // --- MethodVisitor
+ // ---------------------------------------------------
+
+ private class MyMethodVisitor extends MethodVisitor {
+
+ public MyMethodVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitCode() {
+ // pass
+ }
+
+ // field instruction
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ // name is the field's name.
+ // desc is the field's descriptor (see Type).
+ considerDesc(desc);
+ }
+
+ @Override
+ public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) {
+ // pass
+ }
+
+ @Override
+ public void visitIincInsn(int var, int increment) {
+ // pass -- an IINC instruction
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ // pass -- a zero operand instruction
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ // pass -- a single int operand instruction
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ // pass -- a jump instruction
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ // pass -- a label target
+ }
+
+ // instruction to load a constant from the stack
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (cst instanceof Type) {
+ considerType((Type) cst);
+ }
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ // pass
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc,
+ String signature, Label start, Label end, int index) {
+ // desc is the type descriptor of this local variable.
+ considerDesc(desc);
+ // signature is the type signature of this local variable. May be null if the local
+ // variable type does not use generic types.
+ considerSignature(signature);
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ // pass -- a lookup switch instruction
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ // pass
+ }
+
+ // instruction that invokes a method
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+
+ // owner is the internal name of the method's owner class
+ if (!considerDesc(owner) && owner.indexOf('/') != -1) {
+ considerName(owner);
+ }
+ // desc is the method's descriptor (see Type).
+ considerDesc(desc);
+ }
+
+ // instruction multianewarray, whatever that is
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+
+ // desc an array type descriptor.
+ considerDesc(desc);
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ // desc is the class descriptor of the annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ // pass -- table switch instruction
+
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ // type is the internal name of the type of exceptions handled by the handler,
+ // or null to catch any exceptions (for "finally" blocks).
+ considerName(type);
+ }
+
+ // type instruction
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ // type is the operand of the instruction to be visited. This operand must be the
+ // internal name of an object or array class.
+ considerName(type);
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ // pass -- local variable instruction
+ }
+ }
+
+ private class MySignatureVisitor extends SignatureVisitor {
+
+ public MySignatureVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ // ---------------------------------------------------
+ // --- SignatureVisitor
+ // ---------------------------------------------------
+
+ private String mCurrentSignatureClass = null;
+
+ // Starts the visit of a signature corresponding to a class or interface type
+ @Override
+ public void visitClassType(String name) {
+ mCurrentSignatureClass = name;
+ considerName(name);
+ }
+
+ // Visits an inner class
+ @Override
+ public void visitInnerClassType(String name) {
+ if (mCurrentSignatureClass != null) {
+ mCurrentSignatureClass += "$" + name;
+ considerName(mCurrentSignatureClass);
+ }
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitBaseType(char descriptor) {
+ // pass -- a primitive type, ignored
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitFormalTypeParameter(String name) {
+ // pass
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ return new MySignatureVisitor();
+ }
+
+ @Override
+ public void visitTypeVariable(String name) {
+ // pass
+ }
+
+ @Override
+ public void visitTypeArgument() {
+ // pass
+ }
+ }
+
+
+ // ---------------------------------------------------
+ // --- AnnotationVisitor
+ // ---------------------------------------------------
+
+ private class MyAnnotationVisitor extends AnnotationVisitor {
+
+ public MyAnnotationVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ // Visits a primitive value of an annotation
+ @Override
+ public void visit(String name, Object value) {
+ // value is the actual value, whose type must be Byte, Boolean, Character, Short,
+ // Integer, Long, Float, Double, String or Type
+ if (value instanceof Type) {
+ considerType((Type) value);
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ // desc is the class descriptor of the nested annotation class.
+ considerDesc(desc);
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return new MyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitEnum(String name, String desc, String value) {
+ // desc is the class descriptor of the enumeration class.
+ considerDesc(desc);
+ }
+ }
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
new file mode 100644
index 0000000..9387814
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 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.tools.layoutlib.create;
+
+/**
+ * Interface describing the work to be done by {@link AsmGenerator}.
+ */
+public interface ICreateInfo {
+
+ /**
+ * Returns the list of class from layoutlib_create to inject in layoutlib.
+ * The list can be empty but must not be null.
+ */
+ public abstract Class<?>[] getInjectedClasses();
+
+ /**
+ * Returns the list of methods to rewrite as delegates.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDelegateMethods();
+
+ /**
+ * Returns the list of classes on which to delegate all native methods.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDelegateClassNatives();
+
+ /**
+ * Returns The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getOverriddenMethods();
+
+ /**
+ * Returns the list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getRenamedClasses();
+
+ /**
+ * Returns the list of classes for which the methods returning them should be deleted.
+ * The array contains a list of null terminated section starting with the name of the class
+ * to rename in which the methods are deleted, followed by a list of return types identifying
+ * the methods to delete.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDeleteReturns();
+
+ /**
+ * Returns the list of classes to refactor, must be an even list: the
+ * binary FQCN of class to replace followed by the new FQCN. All references
+ * to the old class should be updated to the new class.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getJavaPkgClasses();
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java
new file mode 100644
index 0000000..c3ba591
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+public class Log {
+
+ private boolean mVerbose = false;
+
+ public void setVerbose(boolean verbose) {
+ mVerbose = verbose;
+ }
+
+ public void debug(String format, Object... args) {
+ if (mVerbose) {
+ info(format, args);
+ }
+ }
+
+ /** Similar to debug() but doesn't do a \n automatically. */
+ public void debugNoln(String format, Object... args) {
+ if (mVerbose) {
+ String s = String.format(format, args);
+ System.out.print(s);
+ }
+ }
+
+ public void info(String format, Object... args) {
+ String s = String.format(format, args);
+ outPrintln(s);
+ }
+
+ public void error(String format, Object... args) {
+ String s = String.format(format, args);
+ errPrintln(s);
+ }
+
+ public void exception(Throwable t, String format, Object... args) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ pw.flush();
+ error(format + "\n" + sw.toString(), args);
+ }
+
+ /** for unit testing */
+ protected void errPrintln(String msg) {
+ System.err.println(msg);
+ }
+
+ /** for unit testing */
+ protected void outPrintln(String msg) {
+ System.out.println(msg);
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java
new file mode 100644
index 0000000..dc4b4a7
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+public class LogAbortException extends Exception {
+
+ private final String mFormat;
+ private final Object[] mArgs;
+
+ public LogAbortException(String format, Object... args) {
+ mFormat = format;
+ mArgs = args;
+ }
+
+ public void error(Log log) {
+ log.error(mFormat, mArgs);
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
new file mode 100644
index 0000000..ba23048
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -0,0 +1,226 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Entry point for the layoutlib_create tool.
+ * <p/>
+ * The tool does not currently rely on any external configuration file.
+ * Instead the configuration is mostly done via the {@link CreateInfo} class.
+ * <p/>
+ * For a complete description of the tool and its implementation, please refer to
+ * the "README.txt" file at the root of this project.
+ * <p/>
+ * For a quick test, invoke this as follows:
+ * <pre>
+ * $ make layoutlib
+ * </pre>
+ * which does:
+ * <pre>
+ * $ make layoutlib_create <bunch of framework jars>
+ * $ java -jar out/host/linux-x86/framework/layoutlib_create.jar \
+ * out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar \
+ * out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar \
+ * out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
+ * </pre>
+ */
+public class Main {
+
+ public static class Options {
+ public boolean generatePublicAccess = true;
+ public boolean listAllDeps = false;
+ public boolean listOnlyMissingDeps = false;
+ }
+
+ public static final Options sOptions = new Options();
+
+ public static void main(String[] args) {
+
+ Log log = new Log();
+
+ ArrayList<String> osJarPath = new ArrayList<String>();
+ String[] osDestJar = { null };
+
+ if (!processArgs(log, args, osJarPath, osDestJar)) {
+ log.error("Usage: layoutlib_create [-v] [-p] output.jar input.jar ...");
+ log.error("Usage: layoutlib_create [-v] [--list-deps|--missing-deps] input.jar ...");
+ System.exit(1);
+ }
+
+ if (sOptions.listAllDeps || sOptions.listOnlyMissingDeps) {
+ System.exit(listDeps(osJarPath, log));
+
+ } else {
+ System.exit(createLayoutLib(osDestJar[0], osJarPath, log));
+ }
+
+
+ System.exit(1);
+ }
+
+ private static int createLayoutLib(String osDestJar, ArrayList<String> osJarPath, Log log) {
+ log.info("Output: %1$s", osDestJar);
+ for (String path : osJarPath) {
+ log.info("Input : %1$s", path);
+ }
+
+ try {
+ CreateInfo info = new CreateInfo();
+ Set<String> excludeClasses = getExcludedClasses(info);
+ AsmGenerator agen = new AsmGenerator(log, osDestJar, info);
+
+ AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
+ new String[] { // derived from
+ "android.view.View",
+ "android.app.Fragment"
+ },
+ new String[] { // include classes
+ "android.*", // for android.R
+ "android.util.*",
+ "com.android.internal.util.*",
+ "android.view.*",
+ "android.widget.*",
+ "com.android.internal.widget.*",
+ "android.text.**",
+ "android.graphics.*",
+ "android.graphics.drawable.*",
+ "android.content.*",
+ "android.content.res.*",
+ "org.apache.harmony.xml.*",
+ "com.android.internal.R**",
+ "android.pim.*", // for datepicker
+ "android.os.*", // for android.os.Handler
+ "android.database.ContentObserver", // for Digital clock
+ },
+ excludeClasses);
+ aa.analyze();
+ agen.generate();
+
+ // Throw an error if any class failed to get renamed by the generator
+ //
+ // IMPORTANT: if you're building the platform and you get this error message,
+ // it means the renameClasses[] array in AsmGenerator needs to be updated: some
+ // class should have been renamed but it was not found in the input JAR files.
+ Set<String> notRenamed = agen.getClassesNotRenamed();
+ if (notRenamed.size() > 0) {
+ // (80-column guide below for error formatting)
+ // 01234567890123456789012345678901234567890123456789012345678901234567890123456789
+ log.error(
+ "ERROR when running layoutlib_create: the following classes are referenced\n" +
+ "by tools/layoutlib/create but were not actually found in the input JAR files.\n" +
+ "This may be due to some platform classes having been renamed.");
+ for (String fqcn : notRenamed) {
+ log.error("- Class not found: %s", fqcn.replace('/', '.'));
+ }
+ for (String path : osJarPath) {
+ log.info("- Input JAR : %1$s", path);
+ }
+ return 1;
+ }
+
+ return 0;
+ } catch (IOException e) {
+ log.exception(e, "Failed to load jar");
+ } catch (LogAbortException e) {
+ e.error(log);
+ }
+
+ return 1;
+ }
+
+ private static Set<String> getExcludedClasses(CreateInfo info) {
+ String[] refactoredClasses = info.getJavaPkgClasses();
+ Set<String> excludedClasses = new HashSet<String>(refactoredClasses.length);
+ for (int i = 0; i < refactoredClasses.length; i+=2) {
+ excludedClasses.add(refactoredClasses[i]);
+ }
+ return excludedClasses;
+
+ }
+
+ private static int listDeps(ArrayList<String> osJarPath, Log log) {
+ DependencyFinder df = new DependencyFinder(log);
+ try {
+ List<Map<String, Set<String>>> result = df.findDeps(osJarPath);
+ if (sOptions.listAllDeps) {
+ df.printAllDeps(result);
+ } else if (sOptions.listOnlyMissingDeps) {
+ df.printMissingDeps(result);
+ }
+ } catch (IOException e) {
+ log.exception(e, "Failed to load jar");
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns true if args where properly parsed.
+ * Returns false if program should exit with command-line usage.
+ * <p/>
+ * Note: the String[0] is an output parameter wrapped in an array, since there is no
+ * "out" parameter support.
+ */
+ private static boolean processArgs(Log log, String[] args,
+ ArrayList<String> osJarPath, String[] osDestJar) {
+ boolean needs_dest = true;
+ for (int i = 0; i < args.length; i++) {
+ String s = args[i];
+ if (s.equals("-v")) {
+ log.setVerbose(true);
+ } else if (s.equals("-p")) {
+ sOptions.generatePublicAccess = false;
+ } else if (s.equals("--list-deps")) {
+ sOptions.listAllDeps = true;
+ needs_dest = false;
+ } else if (s.equals("--missing-deps")) {
+ sOptions.listOnlyMissingDeps = true;
+ needs_dest = false;
+ } else if (!s.startsWith("-")) {
+ if (needs_dest && osDestJar[0] == null) {
+ osDestJar[0] = s;
+ } else {
+ osJarPath.add(s);
+ }
+ } else {
+ log.error("Unknow argument: %s", s);
+ return false;
+ }
+ }
+
+ if (osJarPath.isEmpty()) {
+ log.error("Missing parameter: path to input jar");
+ return false;
+ }
+ if (needs_dest && osDestJar[0] == null) {
+ log.error("Missing parameter: path to output jar");
+ return false;
+ }
+
+ sOptions.generatePublicAccess = false;
+
+ return true;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java
new file mode 100644
index 0000000..7d1e4cf
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+
+/**
+ * An adapter to make it easier to use {@link MethodListener}.
+ * <p/>
+ * The adapter calls the void {@link #onInvokeV(String, boolean, Object)} listener
+ * for all types (I, L, F, D and A), returning 0 or null as appropriate.
+ */
+public class MethodAdapter implements MethodListener {
+ /**
+ * A stub method is being invoked.
+ * <p/>
+ * Known limitation: caller arguments are not available.
+ *
+ * @param signature The signature of the method being invoked, composed of the
+ * binary class name followed by the method descriptor (aka argument
+ * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
+ * @param isNative True if the method was a native method.
+ * @param caller The calling object. Null for static methods, "this" for instance methods.
+ */
+ @Override
+ public void onInvokeV(String signature, boolean isNative, Object caller) {
+ }
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns an integer or similar.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return an integer, or a boolean, or a short or a byte.
+ */
+ @Override
+ public int onInvokeI(String signature, boolean isNative, Object caller) {
+ onInvokeV(signature, isNative, caller);
+ return 0;
+ }
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a long.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a long.
+ */
+ @Override
+ public long onInvokeL(String signature, boolean isNative, Object caller) {
+ onInvokeV(signature, isNative, caller);
+ return 0;
+ }
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a float.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a float.
+ */
+ @Override
+ public float onInvokeF(String signature, boolean isNative, Object caller) {
+ onInvokeV(signature, isNative, caller);
+ return 0;
+ }
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a double.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a double.
+ */
+ @Override
+ public double onInvokeD(String signature, boolean isNative, Object caller) {
+ onInvokeV(signature, isNative, caller);
+ return 0;
+ }
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns an object.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return an object.
+ */
+ @Override
+ public Object onInvokeA(String signature, boolean isNative, Object caller) {
+ onInvokeV(signature, isNative, caller);
+ return null;
+ }
+}
+
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
new file mode 100644
index 0000000..6fc2b24
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+
+/**
+ * Interface to allow a method invocation to be listened upon.
+ * <p/>
+ * This is used by {@link OverrideMethod} to register a listener for methods that
+ * have been stubbed by the {@link AsmGenerator}. At runtime the stub will call either a
+ * default global listener or a specific listener based on the method signature.
+ */
+public interface MethodListener {
+ /**
+ * A stub method is being invoked.
+ * <p/>
+ * Known limitation: caller arguments are not available.
+ *
+ * @param signature The signature of the method being invoked, composed of the
+ * binary class name followed by the method descriptor (aka argument
+ * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
+ * @param isNative True if the method was a native method.
+ * @param caller The calling object. Null for static methods, "this" for instance methods.
+ */
+ public void onInvokeV(String signature, boolean isNative, Object caller);
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns an integer or similar.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return an integer, or a boolean, or a short or a byte.
+ */
+ public int onInvokeI(String signature, boolean isNative, Object caller);
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a long.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a long.
+ */
+ public long onInvokeL(String signature, boolean isNative, Object caller);
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a float.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a float.
+ */
+ public float onInvokeF(String signature, boolean isNative, Object caller);
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns a double.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return a double.
+ */
+ public double onInvokeD(String signature, boolean isNative, Object caller);
+
+ /**
+ * Same as {@link #onInvokeV(String, boolean, Object)} but returns an object.
+ * @see #onInvokeV(String, boolean, Object)
+ * @return an object.
+ */
+ public Object onInvokeA(String signature, boolean isNative, Object caller);
+}
+
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
new file mode 100644
index 0000000..a6aff99
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import java.util.HashMap;
+
+/**
+ * Allows stub methods from LayoutLib to be overriden at runtime.
+ * <p/>
+ * Implementation note: all types required by this class(inner/outer classes & interfaces)
+ * must be referenced by the injectClass argument to {@link AsmGenerator} in Main.java;
+ * Otherwise they won't be accessible in layoutlib.jar at runtime.
+ */
+public final class OverrideMethod {
+
+ /** Map of method overridden. */
+ private static HashMap<String, MethodListener> sMethods = new HashMap<String, MethodListener>();
+ /** Default listener for all method not listed in sMethods. Nothing if null. */
+ private static MethodListener sDefaultListener = null;
+
+ /**
+ * Sets the default listener for all methods not specifically handled.
+ * Null means to do nothing.
+ */
+ public static void setDefaultListener(MethodListener listener) {
+ sDefaultListener = listener;
+ }
+
+ /**
+ * Defines or reset a listener for the given method signature.
+ *
+ * @param signature The signature of the method being invoked, composed of the
+ * binary class name followed by the method descriptor (aka argument
+ * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V"
+ * @param listener The new listener. Removes it if null.
+ */
+ public static void setMethodListener(String signature, MethodListener listener) {
+ if (listener == null) {
+ sMethods.remove(signature);
+ } else {
+ sMethods.put(signature, listener);
+ }
+ }
+
+ /**
+ * Invokes the specific listener for the given signature or the default one if defined.
+ * <p/>
+ * This version invokes the method listener for the void return type.
+ * <p/>
+ * Note: this is not intended to be used by the LayoutLib Bridge. It is intended to be called
+ * by the stubbed methods generated by the LayoutLib_create tool.
+ *
+ * @param signature The signature of the method being invoked, composed of the
+ * binary class name followed by the method descriptor (aka argument
+ * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
+ * @param isNative True if the method was a native method.
+ * @param caller The calling object. Null for static methods, "this" for instance methods.
+ */
+ public static void invokeV(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ i.onInvokeV(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ sDefaultListener.onInvokeV(signature, isNative, caller);
+ }
+ }
+
+ /**
+ * Invokes the specific listener for the int return type.
+ * @see #invokeV(String, boolean, Object)
+ */
+ public static int invokeI(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ return i.onInvokeI(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ return sDefaultListener.onInvokeI(signature, isNative, caller);
+ }
+ return 0;
+ }
+
+ /**
+ * Invokes the specific listener for the long return type.
+ * @see #invokeV(String, boolean, Object)
+ */
+ public static long invokeL(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ return i.onInvokeL(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ return sDefaultListener.onInvokeL(signature, isNative, caller);
+ }
+ return 0;
+ }
+
+ /**
+ * Invokes the specific listener for the float return type.
+ * @see #invokeV(String, boolean, Object)
+ */
+ public static float invokeF(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ return i.onInvokeF(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ return sDefaultListener.onInvokeF(signature, isNative, caller);
+ }
+ return 0;
+ }
+
+ /**
+ * Invokes the specific listener for the double return type.
+ * @see #invokeV(String, boolean, Object)
+ */
+ public static double invokeD(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ return i.onInvokeD(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ return sDefaultListener.onInvokeD(signature, isNative, caller);
+ }
+ return 0;
+ }
+
+ /**
+ * Invokes the specific listener for the object return type.
+ * @see #invokeV(String, boolean, Object)
+ */
+ public static Object invokeA(String signature, boolean isNative, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ return i.onInvokeA(signature, isNative, caller);
+ } else if (sDefaultListener != null) {
+ return sDefaultListener.onInvokeA(signature, isNative, caller);
+ }
+ return null;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java
new file mode 100644
index 0000000..91161f5
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 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.tools.layoutlib.create;
+
+import java.util.HashMap;
+
+import org.objectweb.asm.ClassVisitor;
+
+public class RefactorClassAdapter extends AbstractClassAdapter {
+
+ private final HashMap<String, String> mRefactorClasses;
+
+ RefactorClassAdapter(ClassVisitor cv, HashMap<String, String> refactorClasses) {
+ super(cv);
+ mRefactorClasses = refactorClasses;
+ }
+
+ @Override
+ protected String renameInternalType(String oldClassName) {
+ if (oldClassName != null) {
+ String newName = mRefactorClasses.get(oldClassName);
+ if (newName != null) {
+ return newName;
+ }
+ int pos = oldClassName.indexOf('$');
+ if (pos > 0) {
+ newName = mRefactorClasses.get(oldClassName.substring(0, pos));
+ if (newName != null) {
+ return newName + oldClassName.substring(pos);
+ }
+ }
+ }
+ return oldClassName;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java
new file mode 100644
index 0000000..661074c
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassVisitor;
+
+/**
+ * This class visitor renames a class from a given old name to a given new name.
+ * The class visitor will also rename all inner classes and references in the methods.
+ * <p/>
+ *
+ * For inner classes, this handles only the case where the outer class name changes.
+ * The inner class name should remain the same.
+ */
+public class RenameClassAdapter extends AbstractClassAdapter {
+
+
+ private final String mOldName;
+ private final String mNewName;
+ private String mOldBase;
+ private String mNewBase;
+
+ /**
+ * Creates a class visitor that renames a class from a given old name to a given new name.
+ * The class visitor will also rename all inner classes and references in the methods.
+ * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
+ */
+ public RenameClassAdapter(ClassVisitor cv, String oldName, String newName) {
+ super(cv);
+ mOldBase = mOldName = oldName;
+ mNewBase = mNewName = newName;
+
+ int pos = mOldName.indexOf('$');
+ if (pos > 0) {
+ mOldBase = mOldName.substring(0, pos);
+ }
+ pos = mNewName.indexOf('$');
+ if (pos > 0) {
+ mNewBase = mNewName.substring(0, pos);
+ }
+
+ assert (mOldBase == null && mNewBase == null) || (mOldBase != null && mNewBase != null);
+ }
+
+ /**
+ * Renames an internal type name, e.g. "com.package.MyClass".
+ * If the type doesn't need to be renamed, returns the input string as-is.
+ * <p/>
+ * The internal type of some of the MethodVisitor turns out to be a type
+ descriptor sometimes so descriptors are renamed too.
+ */
+ @Override
+ protected String renameInternalType(String type) {
+ if (type == null) {
+ return null;
+ }
+
+ if (type.equals(mOldName)) {
+ return mNewName;
+ }
+
+ if (mOldBase != mOldName && type.equals(mOldBase)) {
+ return mNewBase;
+ }
+
+ int pos = type.indexOf('$');
+ if (pos == mOldBase.length() && type.startsWith(mOldBase)) {
+ return mNewBase + type.substring(pos);
+ }
+ return type;
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
new file mode 100644
index 0000000..51e7535
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
@@ -0,0 +1,376 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * This method adapter rewrites a method by discarding the original code and generating
+ * a stub depending on the return type. Original annotations are passed along unchanged.
+ */
+class StubMethodAdapter extends MethodVisitor {
+
+ private static String CONSTRUCTOR = "<init>";
+ private static String CLASS_INIT = "<clinit>";
+
+ /** The parent method writer */
+ private MethodVisitor mParentVisitor;
+ /** The method return type. Can be null. */
+ private Type mReturnType;
+ /** Message to be printed by stub methods. */
+ private String mInvokeSignature;
+ /** Flag to output the first line number. */
+ private boolean mOutputFirstLineNumber = true;
+ /** Flag that is true when implementing a constructor, to accept all original
+ * code calling the original super constructor. */
+ private boolean mIsInitMethod = false;
+
+ private boolean mMessageGenerated;
+ private final boolean mIsStatic;
+ private final boolean mIsNative;
+
+ public StubMethodAdapter(MethodVisitor mv, String methodName, Type returnType,
+ String invokeSignature, boolean isStatic, boolean isNative) {
+ super(Opcodes.ASM4);
+ mParentVisitor = mv;
+ mReturnType = returnType;
+ mInvokeSignature = invokeSignature;
+ mIsStatic = isStatic;
+ mIsNative = isNative;
+
+ if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
+ mIsInitMethod = true;
+ }
+ }
+
+ private void generateInvoke() {
+ /* Generates the code:
+ * OverrideMethod.invoke("signature", mIsNative ? true : false, null or this);
+ */
+ mParentVisitor.visitLdcInsn(mInvokeSignature);
+ // push true or false
+ mParentVisitor.visitInsn(mIsNative ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
+ // push null or this
+ if (mIsStatic) {
+ mParentVisitor.visitInsn(Opcodes.ACONST_NULL);
+ } else {
+ mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+ }
+
+ int sort = mReturnType != null ? mReturnType.getSort() : Type.VOID;
+ switch(sort) {
+ case Type.VOID:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeV",
+ "(Ljava/lang/String;ZLjava/lang/Object;)V");
+ mParentVisitor.visitInsn(Opcodes.RETURN);
+ break;
+ case Type.BOOLEAN:
+ case Type.CHAR:
+ case Type.BYTE:
+ case Type.SHORT:
+ case Type.INT:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeI",
+ "(Ljava/lang/String;ZLjava/lang/Object;)I");
+ switch(sort) {
+ case Type.BOOLEAN:
+ Label l1 = new Label();
+ mParentVisitor.visitJumpInsn(Opcodes.IFEQ, l1);
+ mParentVisitor.visitInsn(Opcodes.ICONST_1);
+ mParentVisitor.visitInsn(Opcodes.IRETURN);
+ mParentVisitor.visitLabel(l1);
+ mParentVisitor.visitInsn(Opcodes.ICONST_0);
+ break;
+ case Type.CHAR:
+ mParentVisitor.visitInsn(Opcodes.I2C);
+ break;
+ case Type.BYTE:
+ mParentVisitor.visitInsn(Opcodes.I2B);
+ break;
+ case Type.SHORT:
+ mParentVisitor.visitInsn(Opcodes.I2S);
+ break;
+ }
+ mParentVisitor.visitInsn(Opcodes.IRETURN);
+ break;
+ case Type.LONG:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeL",
+ "(Ljava/lang/String;ZLjava/lang/Object;)J");
+ mParentVisitor.visitInsn(Opcodes.LRETURN);
+ break;
+ case Type.FLOAT:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeF",
+ "(Ljava/lang/String;ZLjava/lang/Object;)F");
+ mParentVisitor.visitInsn(Opcodes.FRETURN);
+ break;
+ case Type.DOUBLE:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeD",
+ "(Ljava/lang/String;ZLjava/lang/Object;)D");
+ mParentVisitor.visitInsn(Opcodes.DRETURN);
+ break;
+ case Type.ARRAY:
+ case Type.OBJECT:
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invokeA",
+ "(Ljava/lang/String;ZLjava/lang/Object;)Ljava/lang/Object;");
+ mParentVisitor.visitTypeInsn(Opcodes.CHECKCAST, mReturnType.getInternalName());
+ mParentVisitor.visitInsn(Opcodes.ARETURN);
+ break;
+ }
+
+ }
+
+ private void generatePop() {
+ /* Pops the stack, depending on the return type.
+ */
+ switch(mReturnType != null ? mReturnType.getSort() : Type.VOID) {
+ case Type.VOID:
+ break;
+ case Type.BOOLEAN:
+ case Type.CHAR:
+ case Type.BYTE:
+ case Type.SHORT:
+ case Type.INT:
+ case Type.FLOAT:
+ case Type.ARRAY:
+ case Type.OBJECT:
+ mParentVisitor.visitInsn(Opcodes.POP);
+ break;
+ case Type.LONG:
+ case Type.DOUBLE:
+ mParentVisitor.visitInsn(Opcodes.POP2);
+ break;
+ }
+ }
+
+ /* Pass down to visitor writer. In this implementation, either do nothing. */
+ @Override
+ public void visitCode() {
+ mParentVisitor.visitCode();
+ }
+
+ /*
+ * visitMaxs is called just before visitEnd if there was any code to rewrite.
+ * For non-constructor, generate the messaging code and the return statement
+ * if it hasn't been done before.
+ */
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ if (!mIsInitMethod && !mMessageGenerated) {
+ generateInvoke();
+ mMessageGenerated = true;
+ }
+ mParentVisitor.visitMaxs(maxStack, maxLocals);
+ }
+
+ /**
+ * End of visiting.
+ * For non-constructor, generate the messaging code and the return statement
+ * if it hasn't been done before.
+ */
+ @Override
+ public void visitEnd() {
+ if (!mIsInitMethod && !mMessageGenerated) {
+ generateInvoke();
+ mMessageGenerated = true;
+ mParentVisitor.visitMaxs(1, 1);
+ }
+ mParentVisitor.visitEnd();
+ }
+
+ /* Writes all annotation from the original method. */
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return mParentVisitor.visitAnnotation(desc, visible);
+ }
+
+ /* Writes all annotation default values from the original method. */
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return mParentVisitor.visitAnnotationDefault();
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
+ }
+
+ /* Writes all attributes from the original method. */
+ @Override
+ public void visitAttribute(Attribute attr) {
+ mParentVisitor.visitAttribute(attr);
+ }
+
+ /*
+ * Only writes the first line number present in the original code so that source
+ * viewers can direct to the correct method, even if the content doesn't match.
+ */
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ if (mIsInitMethod || mOutputFirstLineNumber) {
+ mParentVisitor.visitLineNumber(line, start);
+ mOutputFirstLineNumber = false;
+ }
+ }
+
+ /**
+ * For non-constructor, rewrite existing "return" instructions to write the message.
+ */
+ @Override
+ public void visitInsn(int opcode) {
+ if (mIsInitMethod) {
+ switch (opcode) {
+ case Opcodes.RETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.DRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.IRETURN:
+ case Opcodes.LRETURN:
+ // Pop the last word from the stack since invoke will generate its own return.
+ generatePop();
+ generateInvoke();
+ mMessageGenerated = true;
+ //$FALL-THROUGH$
+ default:
+ mParentVisitor.visitInsn(opcode);
+ }
+ }
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitLabel(label);
+ }
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitTryCatchBlock(start, end, handler, type);
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitMethodInsn(opcode, owner, name, desc);
+ }
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitFieldInsn(opcode, owner, name, desc);
+ }
+ }
+
+ @Override
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitFrame(type, nLocal, local, nStack, stack);
+ }
+ }
+
+ @Override
+ public void visitIincInsn(int var, int increment) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitIincInsn(var, increment);
+ }
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitIntInsn(opcode, operand);
+ }
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitJumpInsn(opcode, label);
+ }
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitLdcInsn(cst);
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitLookupSwitchInsn(dflt, keys, labels);
+ }
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitMultiANewArrayInsn(desc, dims);
+ }
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitTypeInsn(opcode, type);
+ }
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ if (mIsInitMethod) {
+ mParentVisitor.visitVarInsn(opcode, var);
+ }
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
new file mode 100644
index 0000000..d45a183
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Set;
+
+/**
+ * Class adapter that can stub some or all of the methods of the class.
+ */
+class TransformClassAdapter extends ClassVisitor {
+
+ /** True if all methods should be stubbed, false if only native ones must be stubbed. */
+ private final boolean mStubAll;
+ /** True if the class is an interface. */
+ private boolean mIsInterface;
+ private final String mClassName;
+ private final Log mLog;
+ private final Set<String> mStubMethods;
+ private Set<String> mDeleteReturns;
+
+ /**
+ * Creates a new class adapter that will stub some or all methods.
+ * @param logger
+ * @param stubMethods list of method signatures to always stub out
+ * @param deleteReturns list of types that trigger the deletion of methods returning them.
+ * @param className The name of the class being modified
+ * @param cv The parent class writer visitor
+ * @param stubNativesOnly True if only native methods should be stubbed. False if all
+ * methods should be stubbed.
+ * @param hasNative True if the method has natives, in which case its access should be
+ * changed.
+ */
+ public TransformClassAdapter(Log logger, Set<String> stubMethods,
+ Set<String> deleteReturns, String className, ClassVisitor cv,
+ boolean stubNativesOnly, boolean hasNative) {
+ super(Opcodes.ASM4, cv);
+ mLog = logger;
+ mStubMethods = stubMethods;
+ mClassName = className;
+ mStubAll = !stubNativesOnly;
+ mIsInterface = false;
+ mDeleteReturns = deleteReturns;
+ }
+
+ /* Visits the class header. */
+ @Override
+ public void visit(int version, int access, String name,
+ String signature, String superName, String[] interfaces) {
+
+ // This class might be being renamed.
+ name = mClassName;
+
+ // remove protected or private and set as public
+ if (Main.sOptions.generatePublicAccess) {
+ access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
+ access |= Opcodes.ACC_PUBLIC;
+ }
+ // remove final
+ access = access & ~Opcodes.ACC_FINAL;
+ // note: leave abstract classes as such
+ // don't try to implement stub for interfaces
+
+ mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ /* Visits the header of an inner class. */
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ // remove protected or private and set as public
+ if (Main.sOptions.generatePublicAccess) {
+ access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
+ access |= Opcodes.ACC_PUBLIC;
+ }
+ // remove final
+ access = access & ~Opcodes.ACC_FINAL;
+ // note: leave abstract classes as such
+ // don't try to implement stub for interfaces
+
+ super.visitInnerClass(name, outerName, innerName, access);
+ }
+
+ /* Visits a method. */
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+
+ if (mDeleteReturns != null) {
+ Type t = Type.getReturnType(desc);
+ if (t.getSort() == Type.OBJECT) {
+ String returnType = t.getInternalName();
+ if (returnType != null) {
+ if (mDeleteReturns.contains(returnType)) {
+ return null;
+ }
+ }
+ }
+ }
+
+ String methodSignature = mClassName.replace('/', '.') + "#" + name;
+
+ // change access to public
+ if (Main.sOptions.generatePublicAccess) {
+ access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
+ access |= Opcodes.ACC_PUBLIC;
+ }
+
+ // remove final
+ access = access & ~Opcodes.ACC_FINAL;
+
+ // stub this method if they are all to be stubbed or if it is a native method
+ // and don't try to stub interfaces nor abstract non-native methods.
+ if (!mIsInterface &&
+ ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != Opcodes.ACC_ABSTRACT) &&
+ (mStubAll ||
+ (access & Opcodes.ACC_NATIVE) != 0) ||
+ mStubMethods.contains(methodSignature)) {
+
+ boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
+
+ // remove abstract, final and native
+ access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
+
+ String invokeSignature = methodSignature + desc;
+ mLog.debug(" Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
+
+ MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+ return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature,
+ isStatic, isNative);
+
+ } else {
+ mLog.debug(" Keep: %s %s", name, desc);
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+ }
+
+ /* Visits a field. Makes it public. */
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ // change access to public
+ if (Main.sOptions.generatePublicAccess) {
+ access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
+ access |= Opcodes.ACC_PUBLIC;
+ }
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ /**
+ * Extracts the return {@link Type} of this descriptor.
+ */
+ Type returnType(String desc) {
+ if (desc != null) {
+ try {
+ return Type.getReturnType(desc);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // ignore, not a valid type.
+ }
+ }
+ return null;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
new file mode 100644
index 0000000..ed2c128
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 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.tools.layoutlib.java;
+
+/**
+ * Defines the same interface as the java.lang.AutoCloseable which was added in
+ * Java 7. This hack makes it possible to run the Android code which uses Java 7
+ * features (API 18 and beyond) to run on Java 6.
+ * <p/>
+ * Extracted from API level 18, file:
+ * platform/libcore/luni/src/main/java/java/lang/AutoCloseable.java
+ */
+public interface AutoCloseable {
+ /**
+ * Closes the object and release any system resources it holds.
+ */
+ void close() throws Exception; }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Charsets.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Charsets.java
new file mode 100644
index 0000000..f73b06b
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Charsets.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2013 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.tools.layoutlib.java;
+
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+
+/**
+ * Defines the same class as the java.nio.charset.Charsets which was added in
+ * Dalvik VM. This hack, provides a replacement for that class which can't be
+ * loaded in the standard JVM since it's in the java package and standard JVM
+ * doesn't have it. An implementation of the native methods in the original
+ * class has been added.
+ * <p/>
+ * Extracted from API level 18, file:
+ * platform/libcore/luni/src/main/java/java/nio/charset/Charsets
+ */
+public final class Charsets {
+ /**
+ * A cheap and type-safe constant for the ISO-8859-1 Charset.
+ */
+ public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
+
+ /**
+ * A cheap and type-safe constant for the US-ASCII Charset.
+ */
+ public static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+ /**
+ * A cheap and type-safe constant for the UTF-8 Charset.
+ */
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ /**
+ * Returns a new byte array containing the bytes corresponding to the given characters,
+ * encoded in US-ASCII. Unrepresentable characters are replaced by (byte) '?'.
+ */
+ public static byte[] toAsciiBytes(char[] chars, int offset, int length) {
+ CharBuffer cb = CharBuffer.allocate(length);
+ cb.put(chars, offset, length);
+ return US_ASCII.encode(cb).array();
+ }
+
+ /**
+ * Returns a new byte array containing the bytes corresponding to the given characters,
+ * encoded in ISO-8859-1. Unrepresentable characters are replaced by (byte) '?'.
+ */
+ public static byte[] toIsoLatin1Bytes(char[] chars, int offset, int length) {
+ CharBuffer cb = CharBuffer.allocate(length);
+ cb.put(chars, offset, length);
+ return ISO_8859_1.encode(cb).array();
+ }
+
+ /**
+ * Returns a new byte array containing the bytes corresponding to the given characters,
+ * encoded in UTF-8. All characters are representable in UTF-8.
+ */
+ public static byte[] toUtf8Bytes(char[] chars, int offset, int length) {
+ CharBuffer cb = CharBuffer.allocate(length);
+ cb.put(chars, offset, length);
+ return UTF_8.encode(cb).array();
+ }
+
+ /**
+ * Returns a new byte array containing the bytes corresponding to the given characters,
+ * encoded in UTF-16BE. All characters are representable in UTF-16BE.
+ */
+ public static byte[] toBigEndianUtf16Bytes(char[] chars, int offset, int length) {
+ byte[] result = new byte[length * 2];
+ int end = offset + length;
+ int resultIndex = 0;
+ for (int i = offset; i < end; ++i) {
+ char ch = chars[i];
+ result[resultIndex++] = (byte) (ch >> 8);
+ result[resultIndex++] = (byte) ch;
+ }
+ return result;
+ }
+
+ /**
+ * Decodes the given US-ASCII bytes into the given char[]. Equivalent to but faster than:
+ *
+ * for (int i = 0; i < count; ++i) {
+ * char ch = (char) (data[start++] & 0xff);
+ * value[i] = (ch <= 0x7f) ? ch : REPLACEMENT_CHAR;
+ * }
+ */
+ public static void asciiBytesToChars(byte[] bytes, int offset, int length, char[] chars) {
+ if (bytes == null || chars == null) {
+ return;
+ }
+ final char REPLACEMENT_CHAR = (char)0xffd;
+ int start = offset;
+ for (int i = 0; i < length; ++i) {
+ char ch = (char) (bytes[start++] & 0xff);
+ chars[i] = (ch <= 0x7f) ? ch : REPLACEMENT_CHAR;
+ }
+ }
+
+ /**
+ * Decodes the given ISO-8859-1 bytes into the given char[]. Equivalent to but faster than:
+ *
+ * for (int i = 0; i < count; ++i) {
+ * value[i] = (char) (data[start++] & 0xff);
+ * }
+ */
+ public static void isoLatin1BytesToChars(byte[] bytes, int offset, int length, char[] chars) {
+ if (bytes == null || chars == null) {
+ return;
+ }
+ int start = offset;
+ for (int i = 0; i < length; ++i) {
+ chars[i] = (char) (bytes[start++] & 0xff);
+ }
+ }
+
+ private Charsets() {
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/IntegralToString.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/IntegralToString.java
new file mode 100644
index 0000000..e6dd00a
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/IntegralToString.java
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2013 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.tools.layoutlib.java;
+
+/**
+ * Defines the same class as the java.lang.IntegralToString which was added in
+ * Dalvik VM. This hack, provides a replacement for that class which can't be
+ * loaded in the standard JVM since it's in the java package and standard JVM
+ * doesn't have it. Since it's no longer in java.lang, access to package
+ * private methods and classes has been replaced by the closes matching public
+ * implementation.
+ * <p/>
+ * Extracted from API level 18, file:
+ * platform/libcore/luni/src/main/java/java/lang/IntegralToString.java
+ */
+public final class IntegralToString {
+ /**
+ * When appending to an AbstractStringBuilder, this thread-local char[] lets us avoid
+ * allocation of a temporary array. (We can't write straight into the AbstractStringBuilder
+ * because it's almost as expensive to work out the exact length of the result as it is to
+ * do the formatting. We could try being conservative and "delete"-ing the unused space
+ * afterwards, but then we'd need to duplicate convertInt and convertLong rather than share
+ * the code.)
+ */
+ private static final ThreadLocal<char[]> BUFFER = new ThreadLocal<char[]>() {
+ @Override protected char[] initialValue() {
+ return new char[20]; // Maximum length of a base-10 long.
+ }
+ };
+
+ /**
+ * These tables are used to special-case toString computation for
+ * small values. This serves three purposes: it reduces memory usage;
+ * it increases performance for small values; and it decreases the
+ * number of comparisons required to do the length computation.
+ * Elements of this table are lazily initialized on first use.
+ * No locking is necessary, i.e., we use the non-volatile, racy
+ * single-check idiom.
+ */
+ private static final String[] SMALL_NONNEGATIVE_VALUES = new String[100];
+ private static final String[] SMALL_NEGATIVE_VALUES = new String[100];
+
+ /** TENS[i] contains the tens digit of the number i, 0 <= i <= 99. */
+ private static final char[] TENS = {
+ '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
+ '1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
+ '2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
+ '3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
+ '4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
+ '5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
+ '6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
+ '7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
+ '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
+ '9', '9', '9', '9', '9', '9', '9', '9', '9', '9'
+ };
+
+ /** Ones [i] contains the tens digit of the number i, 0 <= i <= 99. */
+ private static final char[] ONES = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ };
+
+ /**
+ * Table for MOD / DIV 10 computation described in Section 10-21
+ * of Hank Warren's "Hacker's Delight" online addendum.
+ * http://www.hackersdelight.org/divcMore.pdf
+ */
+ private static final char[] MOD_10_TABLE = {
+ 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9, 0
+ };
+
+ /**
+ * The digits for every supported radix.
+ */
+ private static final char[] DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z'
+ };
+
+ private static final char[] UPPER_CASE_DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z'
+ };
+
+ private IntegralToString() {
+ }
+
+ /**
+ * Equivalent to Integer.toString(i, radix).
+ */
+ public static String intToString(int i, int radix) {
+ if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
+ radix = 10;
+ }
+ if (radix == 10) {
+ return intToString(i);
+ }
+
+ /*
+ * If i is positive, negate it. This is the opposite of what one might
+ * expect. It is necessary because the range of the negative values is
+ * strictly larger than that of the positive values: there is no
+ * positive value corresponding to Integer.MIN_VALUE.
+ */
+ boolean negative = false;
+ if (i < 0) {
+ negative = true;
+ } else {
+ i = -i;
+ }
+
+ int bufLen = radix < 8 ? 33 : 12; // Max chars in result (conservative)
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ int q = i / radix;
+ buf[--cursor] = DIGITS[radix * q - i];
+ i = q;
+ } while (i != 0);
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ /**
+ * Equivalent to Integer.toString(i).
+ */
+ public static String intToString(int i) {
+ return convertInt(null, i);
+ }
+
+ /**
+ * Equivalent to sb.append(Integer.toString(i)).
+ */
+ public static void appendInt(StringBuilder sb, int i) {
+ convertInt(sb, i);
+ }
+
+ /**
+ * Returns the string representation of i and leaves sb alone if sb is null.
+ * Returns null and appends the string representation of i to sb if sb is non-null.
+ */
+ private static String convertInt(StringBuilder sb, int i) {
+ boolean negative = false;
+ String quickResult = null;
+ if (i < 0) {
+ negative = true;
+ i = -i;
+ if (i < 100) {
+ if (i < 0) {
+ // If -n is still negative, n is Integer.MIN_VALUE
+ quickResult = "-2147483648";
+ } else {
+ quickResult = SMALL_NEGATIVE_VALUES[i];
+ if (quickResult == null) {
+ SMALL_NEGATIVE_VALUES[i] = quickResult =
+ i < 10 ? stringOf('-', ONES[i]) : stringOf('-', TENS[i], ONES[i]);
+ }
+ }
+ }
+ } else {
+ if (i < 100) {
+ quickResult = SMALL_NONNEGATIVE_VALUES[i];
+ if (quickResult == null) {
+ SMALL_NONNEGATIVE_VALUES[i] = quickResult =
+ i < 10 ? stringOf(ONES[i]) : stringOf(TENS[i], ONES[i]);
+ }
+ }
+ }
+ if (quickResult != null) {
+ if (sb != null) {
+ sb.append(quickResult);
+ return null;
+ }
+ return quickResult;
+ }
+
+ int bufLen = 11; // Max number of chars in result
+ char[] buf = (sb != null) ? BUFFER.get() : new char[bufLen];
+ int cursor = bufLen;
+
+ // Calculate digits two-at-a-time till remaining digits fit in 16 bits
+ while (i >= (1 << 16)) {
+ // Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8
+ int q = (int) ((0x51EB851FL * i) >>> 37);
+ int r = i - 100*q;
+ buf[--cursor] = ONES[r];
+ buf[--cursor] = TENS[r];
+ i = q;
+ }
+
+ // Calculate remaining digits one-at-a-time for performance
+ while (i != 0) {
+ // Compute q = n/10 and r = n % 10 as per "Hacker's Delight" 10-8
+ int q = (0xCCCD * i) >>> 19;
+ int r = i - 10*q;
+ buf[--cursor] = DIGITS[r];
+ i = q;
+ }
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+
+ if (sb != null) {
+ sb.append(buf, cursor, bufLen - cursor);
+ return null;
+ } else {
+ return new String(buf, cursor, bufLen - cursor);
+ }
+ }
+
+ /**
+ * Equivalent to Long.toString(v, radix).
+ */
+ public static String longToString(long v, int radix) {
+ int i = (int) v;
+ if (i == v) {
+ return intToString(i, radix);
+ }
+
+ if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
+ radix = 10;
+ }
+ if (radix == 10) {
+ return longToString(v);
+ }
+
+ /*
+ * If v is positive, negate it. This is the opposite of what one might
+ * expect. It is necessary because the range of the negative values is
+ * strictly larger than that of the positive values: there is no
+ * positive value corresponding to Integer.MIN_VALUE.
+ */
+ boolean negative = false;
+ if (v < 0) {
+ negative = true;
+ } else {
+ v = -v;
+ }
+
+ int bufLen = radix < 8 ? 65 : 23; // Max chars in result (conservative)
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ long q = v / radix;
+ buf[--cursor] = DIGITS[(int) (radix * q - v)];
+ v = q;
+ } while (v != 0);
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ /**
+ * Equivalent to Long.toString(l).
+ */
+ public static String longToString(long l) {
+ return convertLong(null, l);
+ }
+
+ /**
+ * Equivalent to sb.append(Long.toString(l)).
+ */
+ public static void appendLong(StringBuilder sb, long l) {
+ convertLong(sb, l);
+ }
+
+ /**
+ * Returns the string representation of n and leaves sb alone if sb is null.
+ * Returns null and appends the string representation of n to sb if sb is non-null.
+ */
+ private static String convertLong(StringBuilder sb, long n) {
+ int i = (int) n;
+ if (i == n) {
+ return convertInt(sb, i);
+ }
+
+ boolean negative = (n < 0);
+ if (negative) {
+ n = -n;
+ if (n < 0) {
+ // If -n is still negative, n is Long.MIN_VALUE
+ String quickResult = "-9223372036854775808";
+ if (sb != null) {
+ sb.append(quickResult);
+ return null;
+ }
+ return quickResult;
+ }
+ }
+
+ int bufLen = 20; // Maximum number of chars in result
+ char[] buf = (sb != null) ? BUFFER.get() : new char[bufLen];
+
+ int low = (int) (n % 1000000000); // Extract low-order 9 digits
+ int cursor = intIntoCharArray(buf, bufLen, low);
+
+ // Zero-pad Low order part to 9 digits
+ while (cursor != (bufLen - 9)) {
+ buf[--cursor] = '0';
+ }
+
+ /*
+ * The remaining digits are (n - low) / 1,000,000,000. This
+ * "exact division" is done as per the online addendum to Hank Warren's
+ * "Hacker's Delight" 10-20, http://www.hackersdelight.org/divcMore.pdf
+ */
+ n = ((n - low) >>> 9) * 0x8E47CE423A2E9C6DL;
+
+ /*
+ * If the remaining digits fit in an int, emit them using a
+ * single call to intIntoCharArray. Otherwise, strip off the
+ * low-order digit, put it in buf, and then call intIntoCharArray
+ * on the remaining digits (which now fit in an int).
+ */
+ if ((n & (-1L << 32)) == 0) {
+ cursor = intIntoCharArray(buf, cursor, (int) n);
+ } else {
+ /*
+ * Set midDigit to n % 10
+ */
+ int lo32 = (int) n;
+ int hi32 = (int) (n >>> 32);
+
+ // midDigit = ((unsigned) low32) % 10, per "Hacker's Delight" 10-21
+ int midDigit = MOD_10_TABLE[(0x19999999 * lo32 + (lo32 >>> 1) + (lo32 >>> 3)) >>> 28];
+
+ // Adjust midDigit for hi32. (assert hi32 == 1 || hi32 == 2)
+ midDigit -= hi32 << 2; // 1L << 32 == -4 MOD 10
+ if (midDigit < 0) {
+ midDigit += 10;
+ }
+ buf[--cursor] = DIGITS[midDigit];
+
+ // Exact division as per Warren 10-20
+ int rest = ((int) ((n - midDigit) >>> 1)) * 0xCCCCCCCD;
+ cursor = intIntoCharArray(buf, cursor, rest);
+ }
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+ if (sb != null) {
+ sb.append(buf, cursor, bufLen - cursor);
+ return null;
+ } else {
+ return new String(buf, cursor, bufLen - cursor);
+ }
+ }
+
+ /**
+ * Inserts the unsigned decimal integer represented by n into the specified
+ * character array starting at position cursor. Returns the index after
+ * the last character inserted (i.e., the value to pass in as cursor the
+ * next time this method is called). Note that n is interpreted as a large
+ * positive integer (not a negative integer) if its sign bit is set.
+ */
+ private static int intIntoCharArray(char[] buf, int cursor, int n) {
+ // Calculate digits two-at-a-time till remaining digits fit in 16 bits
+ while ((n & 0xffff0000) != 0) {
+ /*
+ * Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8.
+ * This computation is slightly different from the corresponding
+ * computation in intToString: the shifts before and after
+ * multiply can't be combined, as that would yield the wrong result
+ * if n's sign bit were set.
+ */
+ int q = (int) ((0x51EB851FL * (n >>> 2)) >>> 35);
+ int r = n - 100*q;
+ buf[--cursor] = ONES[r];
+ buf[--cursor] = TENS[r];
+ n = q;
+ }
+
+ // Calculate remaining digits one-at-a-time for performance
+ while (n != 0) {
+ // Compute q = n / 10 and r = n % 10 as per "Hacker's Delight" 10-8
+ int q = (0xCCCD * n) >>> 19;
+ int r = n - 10*q;
+ buf[--cursor] = DIGITS[r];
+ n = q;
+ }
+ return cursor;
+ }
+
+ public static String intToBinaryString(int i) {
+ int bufLen = 32; // Max number of binary digits in an int
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[i & 1];
+ } while ((i >>>= 1) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String longToBinaryString(long v) {
+ int i = (int) v;
+ if (v >= 0 && i == v) {
+ return intToBinaryString(i);
+ }
+
+ int bufLen = 64; // Max number of binary digits in a long
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[((int) v) & 1];
+ } while ((v >>>= 1) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) {
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ sb.append(digits[(b >> 4) & 0xf]);
+ sb.append(digits[b & 0xf]);
+ return sb;
+ }
+
+ public static String byteToHexString(byte b, boolean upperCase) {
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ char[] buf = new char[2]; // We always want two digits.
+ buf[0] = digits[(b >> 4) & 0xf];
+ buf[1] = digits[b & 0xf];
+ return new String(buf, 0, 2);
+ }
+
+ public static String bytesToHexString(byte[] bytes, boolean upperCase) {
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ char[] buf = new char[bytes.length * 2];
+ int c = 0;
+ for (byte b : bytes) {
+ buf[c++] = digits[(b >> 4) & 0xf];
+ buf[c++] = digits[b & 0xf];
+ }
+ return new String(buf);
+ }
+
+ public static String intToHexString(int i, boolean upperCase, int minWidth) {
+ int bufLen = 8; // Max number of hex digits in an int
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ do {
+ buf[--cursor] = digits[i & 0xf];
+ } while ((i >>>= 4) != 0 || (bufLen - cursor < minWidth));
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String longToHexString(long v) {
+ int i = (int) v;
+ if (v >= 0 && i == v) {
+ return intToHexString(i, false, 0);
+ }
+
+ int bufLen = 16; // Max number of hex digits in a long
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[((int) v) & 0xF];
+ } while ((v >>>= 4) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String intToOctalString(int i) {
+ int bufLen = 11; // Max number of octal digits in an int
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[i & 7];
+ } while ((i >>>= 3) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String longToOctalString(long v) {
+ int i = (int) v;
+ if (v >= 0 && i == v) {
+ return intToOctalString(i);
+ }
+ int bufLen = 22; // Max number of octal digits in a long
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[((int) v) & 7];
+ } while ((v >>>= 3) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ private static String stringOf(char... args) {
+ return new String(args, 0, args.length);
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Objects.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Objects.java
new file mode 100644
index 0000000..eb1ef72
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Objects.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2013 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.tools.layoutlib.java;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Defines the same class as the java.util.Objects which is added in Java 7.
+ * This hack makes it possible to run the Android code which uses Java 7 features
+ * (API 18 and beyond) to run on Java 6.
+ * <p/>
+ * Extracted from API level 19, file:
+ * platform/libcore/luni/src/main/java/java/util/Objects.java
+ */
+public final class Objects {
+ private Objects() {}
+
+ /**
+ * Returns 0 if {@code a == b}, or {@code c.compare(a, b)} otherwise.
+ * That is, this makes {@code c} null-safe.
+ */
+ public static <T> int compare(T a, T b, Comparator<? super T> c) {
+ if (a == b) {
+ return 0;
+ }
+ return c.compare(a, b);
+ }
+
+ /**
+ * Returns true if both arguments are null,
+ * the result of {@link Arrays#equals} if both arguments are primitive arrays,
+ * the result of {@link Arrays#deepEquals} if both arguments are arrays of reference types,
+ * and the result of {@link #equals} otherwise.
+ */
+ public static boolean deepEquals(Object a, Object b) {
+ if (a == null || b == null) {
+ return a == b;
+ } else if (a instanceof Object[] && b instanceof Object[]) {
+ return Arrays.deepEquals((Object[]) a, (Object[]) b);
+ } else if (a instanceof boolean[] && b instanceof boolean[]) {
+ return Arrays.equals((boolean[]) a, (boolean[]) b);
+ } else if (a instanceof byte[] && b instanceof byte[]) {
+ return Arrays.equals((byte[]) a, (byte[]) b);
+ } else if (a instanceof char[] && b instanceof char[]) {
+ return Arrays.equals((char[]) a, (char[]) b);
+ } else if (a instanceof double[] && b instanceof double[]) {
+ return Arrays.equals((double[]) a, (double[]) b);
+ } else if (a instanceof float[] && b instanceof float[]) {
+ return Arrays.equals((float[]) a, (float[]) b);
+ } else if (a instanceof int[] && b instanceof int[]) {
+ return Arrays.equals((int[]) a, (int[]) b);
+ } else if (a instanceof long[] && b instanceof long[]) {
+ return Arrays.equals((long[]) a, (long[]) b);
+ } else if (a instanceof short[] && b instanceof short[]) {
+ return Arrays.equals((short[]) a, (short[]) b);
+ }
+ return a.equals(b);
+ }
+
+ /**
+ * Null-safe equivalent of {@code a.equals(b)}.
+ */
+ public static boolean equals(Object a, Object b) {
+ return (a == null) ? (b == null) : a.equals(b);
+ }
+
+ /**
+ * Convenience wrapper for {@link Arrays#hashCode}, adding varargs.
+ * This can be used to compute a hash code for an object's fields as follows:
+ * {@code Objects.hash(a, b, c)}.
+ */
+ public static int hash(Object... values) {
+ return Arrays.hashCode(values);
+ }
+
+ /**
+ * Returns 0 for null or {@code o.hashCode()}.
+ */
+ public static int hashCode(Object o) {
+ return (o == null) ? 0 : o.hashCode();
+ }
+
+ /**
+ * Returns {@code o} if non-null, or throws {@code NullPointerException}.
+ */
+ public static <T> T requireNonNull(T o) {
+ if (o == null) {
+ throw new NullPointerException();
+ }
+ return o;
+ }
+
+ /**
+ * Returns {@code o} if non-null, or throws {@code NullPointerException}
+ * with the given detail message.
+ */
+ public static <T> T requireNonNull(T o, String message) {
+ if (o == null) {
+ throw new NullPointerException(message);
+ }
+ return o;
+ }
+
+ /**
+ * Returns "null" for null or {@code o.toString()}.
+ */
+ public static String toString(Object o) {
+ return (o == null) ? "null" : o.toString();
+ }
+
+ /**
+ * Returns {@code nullString} for null or {@code o.toString()}.
+ */
+ public static String toString(Object o, String nullString) {
+ return (o == null) ? nullString : o.toString();
+ }
+}
\ No newline at end of file
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java
new file mode 100644
index 0000000..0e09080
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 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.tools.layoutlib.java;
+
+import java.nio.charset.Charset;
+
+/**
+ * Defines the same class as the java.lang.UnsafeByteSequence which was added in
+ * Dalvik VM. This hack, provides a replacement for that class which can't be
+ * loaded in the standard JVM since it's in the java package and standard JVM
+ * doesn't have it.
+ * <p/>
+ * Extracted from API level 18, file:
+ * platform/libcore/luni/src/main/java/java/lang/UnsafeByteSequence.java
+ */
+public class UnsafeByteSequence {
+ private byte[] bytes;
+ private int count;
+
+ public UnsafeByteSequence(int initialCapacity) {
+ this.bytes = new byte[initialCapacity];
+ }
+
+ public int size() {
+ return count;
+ }
+
+ /**
+ * Moves the write pointer back to the beginning of the sequence,
+ * but without resizing or reallocating the buffer.
+ */
+ public void rewind() {
+ count = 0;
+ }
+
+ public void write(byte[] buffer, int offset, int length) {
+ if (count + length >= bytes.length) {
+ byte[] newBytes = new byte[(count + length) * 2];
+ System.arraycopy(bytes, 0, newBytes, 0, count);
+ bytes = newBytes;
+ }
+ System.arraycopy(buffer, offset, bytes, count, length);
+ count += length;
+ }
+
+ public void write(int b) {
+ if (count == bytes.length) {
+ byte[] newBytes = new byte[count * 2];
+ System.arraycopy(bytes, 0, newBytes, 0, count);
+ bytes = newBytes;
+ }
+ bytes[count++] = (byte) b;
+ }
+
+ public byte[] toByteArray() {
+ if (count == bytes.length) {
+ return bytes;
+ }
+ byte[] result = new byte[count];
+ System.arraycopy(bytes, 0, result, 0, count);
+ return result;
+ }
+
+ public String toString(Charset cs) {
+ return new String(bytes, 0, count, cs);
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
new file mode 100644
index 0000000..005fc9d
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+
+package com.android.tools.layoutlib.create;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Unit tests for some methods of {@link AsmAnalyzer}.
+ */
+public class AsmAnalyzerTest {
+
+ private MockLog mLog;
+ private ArrayList<String> mOsJarPath;
+ private AsmAnalyzer mAa;
+
+ @Before
+ public void setUp() throws Exception {
+ mLog = new MockLog();
+ URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
+
+ mOsJarPath = new ArrayList<String>();
+ mOsJarPath.add(url.getFile());
+
+ Set<String> excludeClasses = new HashSet<String>(1);
+ excludeClasses.add("java.lang.JavaClass");
+ mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */,
+ null /* deriveFrom */, null /* includeGlobs */, excludeClasses);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void testParseZip() throws IOException {
+ Map<String, ClassReader> map = mAa.parseZip(mOsJarPath);
+
+ assertArrayEquals(new String[] {
+ "java.lang.JavaClass",
+ "mock_android.dummy.InnerTest",
+ "mock_android.dummy.InnerTest$DerivingClass",
+ "mock_android.dummy.InnerTest$MyGenerics1",
+ "mock_android.dummy.InnerTest$MyIntEnum",
+ "mock_android.dummy.InnerTest$MyStaticInnerClass",
+ "mock_android.dummy.InnerTest$NotStaticInner1",
+ "mock_android.dummy.InnerTest$NotStaticInner2",
+ "mock_android.view.View",
+ "mock_android.view.ViewGroup",
+ "mock_android.view.ViewGroup$LayoutParams",
+ "mock_android.view.ViewGroup$MarginLayoutParams",
+ "mock_android.widget.LinearLayout",
+ "mock_android.widget.LinearLayout$LayoutParams",
+ "mock_android.widget.TableLayout",
+ "mock_android.widget.TableLayout$LayoutParams"
+ },
+ map.keySet().toArray());
+ }
+
+ @Test
+ public void testFindClass() throws IOException, LogAbortException {
+ Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+ TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+
+ ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
+ zipClasses, found);
+
+ assertNotNull(cr);
+ assertEquals("mock_android/view/ViewGroup$LayoutParams", cr.getClassName());
+ assertArrayEquals(new String[] { "mock_android.view.ViewGroup$LayoutParams" },
+ found.keySet().toArray());
+ assertArrayEquals(new ClassReader[] { cr }, found.values().toArray());
+ }
+
+ @Test
+ public void testFindGlobs() throws IOException, LogAbortException {
+ Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+ TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+
+ // this matches classes, a package match returns nothing
+ found.clear();
+ mAa.findGlobs("mock_android.view", zipClasses, found);
+
+ assertArrayEquals(new String[] { },
+ found.keySet().toArray());
+
+ // a complex glob search. * is a search pattern that matches names, not dots
+ mAa.findGlobs("mock_android.*.*Group$*Layout*", zipClasses, found);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.ViewGroup$LayoutParams",
+ "mock_android.view.ViewGroup$MarginLayoutParams"
+ },
+ found.keySet().toArray());
+
+ // a complex glob search. ** is a search pattern that matches names including dots
+ mAa.findGlobs("mock_android.**Group*", zipClasses, found);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.ViewGroup",
+ "mock_android.view.ViewGroup$LayoutParams",
+ "mock_android.view.ViewGroup$MarginLayoutParams"
+ },
+ found.keySet().toArray());
+
+ // matches a single class
+ found.clear();
+ mAa.findGlobs("mock_android.view.View", zipClasses, found);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.View"
+ },
+ found.keySet().toArray());
+
+ // matches everyting inside the given package but not sub-packages
+ found.clear();
+ mAa.findGlobs("mock_android.view.*", zipClasses, found);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.View",
+ "mock_android.view.ViewGroup",
+ "mock_android.view.ViewGroup$LayoutParams",
+ "mock_android.view.ViewGroup$MarginLayoutParams"
+ },
+ found.keySet().toArray());
+
+ for (String key : found.keySet()) {
+ ClassReader value = found.get(key);
+ assertNotNull("No value for " + key, value);
+ assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
+ }
+ }
+
+ @Test
+ public void testFindClassesDerivingFrom() throws LogAbortException, IOException {
+ Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+ TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
+
+ mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.View",
+ "mock_android.view.ViewGroup",
+ "mock_android.widget.LinearLayout",
+ "mock_android.widget.TableLayout",
+ },
+ found.keySet().toArray());
+
+ for (String key : found.keySet()) {
+ ClassReader value = found.get(key);
+ assertNotNull("No value for " + key, value);
+ assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
+ }
+ }
+
+ @Test
+ public void testDependencyVisitor() throws IOException, LogAbortException {
+ Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
+ TreeMap<String, ClassReader> keep = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> in_deps = new TreeMap<String, ClassReader>();
+ TreeMap<String, ClassReader> out_deps = new TreeMap<String, ClassReader>();
+
+ ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep);
+ DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
+
+ // get first level dependencies
+ cr.accept(visitor, 0 /* flags */);
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.ViewGroup",
+ "mock_android.widget.TableLayout$LayoutParams",
+ },
+ out_deps.keySet().toArray());
+
+ in_deps.putAll(out_deps);
+ out_deps.clear();
+
+ // get second level dependencies
+ for (ClassReader cr2 : in_deps.values()) {
+ cr2.accept(visitor, 0 /* flags */);
+ }
+
+ assertArrayEquals(new String[] {
+ "mock_android.view.View",
+ "mock_android.view.ViewGroup$LayoutParams",
+ "mock_android.view.ViewGroup$MarginLayoutParams",
+ },
+ out_deps.keySet().toArray());
+
+ in_deps.putAll(out_deps);
+ out_deps.clear();
+
+ // get third level dependencies (there are none)
+ for (ClassReader cr2 : in_deps.values()) {
+ cr2.accept(visitor, 0 /* flags */);
+ }
+ keep.putAll(new_keep);
+
+ assertArrayEquals(new String[] { }, out_deps.keySet().toArray());
+ assertArrayEquals(new String[] {
+ "mock_android.widget.TableLayout",
+ }, keep.keySet().toArray());
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
new file mode 100644
index 0000000..8a27173
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -0,0 +1,330 @@
+/*
+ * 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.
+ */
+
+
+package com.android.tools.layoutlib.create;
+
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Unit tests for some methods of {@link AsmGenerator}.
+ */
+public class AsmGeneratorTest {
+
+ private MockLog mLog;
+ private ArrayList<String> mOsJarPath;
+ private String mOsDestJar;
+ private File mTempFile;
+
+ // ASM internal name for the the class in java package that should be refactored.
+ private static final String JAVA_CLASS_NAME = "java/lang/JavaClass";
+
+ @Before
+ public void setUp() throws Exception {
+ mLog = new MockLog();
+ URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
+
+ mOsJarPath = new ArrayList<String>();
+ mOsJarPath.add(url.getFile());
+
+ mTempFile = File.createTempFile("mock", ".jar");
+ mOsDestJar = mTempFile.getAbsolutePath();
+ mTempFile.deleteOnExit();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mTempFile != null) {
+ mTempFile.delete();
+ mTempFile = null;
+ }
+ }
+
+ @Test
+ public void testClassRenaming() throws IOException, LogAbortException {
+
+ ICreateInfo ci = new ICreateInfo() {
+ @Override
+ public Class<?>[] getInjectedClasses() {
+ // classes to inject in the final JAR
+ return new Class<?>[0];
+ }
+
+ @Override
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ @Override
+ public String[] getRenamedClasses() {
+ // classes to rename (so that we can replace them)
+ return new String[] {
+ "mock_android.view.View", "mock_android.view._Original_View",
+ "not.an.actual.ClassName", "anoter.fake.NewClassName",
+ };
+ }
+
+ @Override
+ public String[] getJavaPkgClasses() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getDeleteReturns() {
+ // methods deleted from their return type.
+ return new String[0];
+ }
+ };
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
+
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ null, // derived from
+ new String[] { // include classes
+ "**"
+ },
+ new HashSet<String>(0) /* excluded classes */);
+ aa.analyze();
+ agen.generate();
+
+ Set<String> notRenamed = agen.getClassesNotRenamed();
+ assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
+
+ }
+
+ @Test
+ public void testClassRefactoring() throws IOException, LogAbortException {
+ ICreateInfo ci = new ICreateInfo() {
+ @Override
+ public Class<?>[] getInjectedClasses() {
+ // classes to inject in the final JAR
+ return new Class<?>[] {
+ com.android.tools.layoutlib.create.dataclass.JavaClass.class
+ };
+ }
+
+ @Override
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ @Override
+ public String[] getRenamedClasses() {
+ // classes to rename (so that we can replace them)
+ return new String[0];
+ }
+
+ @Override
+ public String[] getJavaPkgClasses() {
+ // classes to refactor (so that we can replace them)
+ return new String[] {
+ "java.lang.JavaClass", "com.android.tools.layoutlib.create.dataclass.JavaClass",
+ };
+ }
+
+ @Override
+ public String[] getDeleteReturns() {
+ // methods deleted from their return type.
+ return new String[0];
+ }
+ };
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
+
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ null, // derived from
+ new String[] { // include classes
+ "**"
+ },
+ new HashSet<String>(1));
+ aa.analyze();
+ agen.generate();
+ Map<String, ClassReader> output = parseZip(mOsDestJar);
+ boolean injectedClassFound = false;
+ for (ClassReader cr: output.values()) {
+ TestClassVisitor cv = new TestClassVisitor();
+ cr.accept(cv, 0);
+ injectedClassFound |= cv.mInjectedClassFound;
+ }
+ assertTrue(injectedClassFound);
+ }
+
+ private Map<String,ClassReader> parseZip(String jarPath) throws IOException {
+ TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
+
+ ZipFile zip = new ZipFile(jarPath);
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ ZipEntry entry;
+ while (entries.hasMoreElements()) {
+ entry = entries.nextElement();
+ if (entry.getName().endsWith(".class")) {
+ ClassReader cr = new ClassReader(zip.getInputStream(entry));
+ String className = classReaderToClassName(cr);
+ classes.put(className, cr);
+ }
+ }
+
+ return classes;
+ }
+
+ private String classReaderToClassName(ClassReader classReader) {
+ if (classReader == null) {
+ return null;
+ } else {
+ return classReader.getClassName().replace('/', '.');
+ }
+ }
+
+ private class TestClassVisitor extends ClassVisitor {
+
+ boolean mInjectedClassFound = false;
+
+ TestClassVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ assertTrue(!getBase(name).equals(JAVA_CLASS_NAME));
+ if (name.equals("com/android/tools/layoutlib/create/dataclass/JavaClass")) {
+ mInjectedClassFound = true;
+ }
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ assertTrue(testType(Type.getType(desc)));
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ @SuppressWarnings("hiding")
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ return new MethodVisitor(Opcodes.ASM4, mv) {
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name,
+ String desc) {
+ assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
+ assertTrue(testType(Type.getType(desc)));
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (cst instanceof Type) {
+ assertTrue(testType((Type)cst));
+ }
+ super.visitLdcInsn(cst);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ assertTrue(!getBase(type).equals(JAVA_CLASS_NAME));
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name,
+ String desc) {
+ assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
+ assertTrue(testType(Type.getType(desc)));
+ super.visitMethodInsn(opcode, owner, name, desc);
+ }
+
+ };
+ }
+
+ private boolean testType(Type type) {
+ int sort = type.getSort();
+ if (sort == Type.OBJECT) {
+ assertTrue(!getBase(type.getInternalName()).equals(JAVA_CLASS_NAME));
+ } else if (sort == Type.ARRAY) {
+ assertTrue(!getBase(type.getElementType().getInternalName())
+ .equals(JAVA_CLASS_NAME));
+ } else if (sort == Type.METHOD) {
+ boolean r = true;
+ for (Type t : type.getArgumentTypes()) {
+ r &= testType(t);
+ }
+ return r & testType(type.getReturnType());
+ }
+ return true;
+ }
+
+ private String getBase(String className) {
+ if (className == null) {
+ return null;
+ }
+ int pos = className.indexOf('$');
+ if (pos > 0) {
+ return className.substring(0, pos);
+ }
+ return className;
+ }
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
new file mode 100644
index 0000000..0135c40
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2010 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.tools.layoutlib.create;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+
+/**
+ * Tests {@link ClassHasNativeVisitor}.
+ */
+public class ClassHasNativeVisitorTest {
+
+ @Test
+ public void testHasNative() throws IOException {
+ MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
+ String className =
+ this.getClass().getCanonicalName() + "$" + ClassWithNative.class.getSimpleName();
+ ClassReader cr = new ClassReader(className);
+
+ cr.accept(cv, 0 /* flags */);
+ assertArrayEquals(new String[] { "native_method" }, cv.getMethodsFound());
+ assertTrue(cv.hasNativeMethods());
+ }
+
+ @Test
+ public void testHasNoNative() throws IOException {
+ MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
+ String className =
+ this.getClass().getCanonicalName() + "$" + ClassWithoutNative.class.getSimpleName();
+ ClassReader cr = new ClassReader(className);
+
+ cr.accept(cv, 0 /* flags */);
+ assertArrayEquals(new String[0], cv.getMethodsFound());
+ assertFalse(cv.hasNativeMethods());
+ }
+
+ //-------
+
+ /**
+ * Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
+ */
+ private static class MockClassHasNativeVisitor extends ClassHasNativeVisitor {
+ private ArrayList<String> mMethodsFound = new ArrayList<String>();
+
+ public String[] getMethodsFound() {
+ return mMethodsFound.toArray(new String[mMethodsFound.size()]);
+ }
+
+ @Override
+ protected void setHasNativeMethods(boolean hasNativeMethods, String methodName) {
+ if (hasNativeMethods) {
+ mMethodsFound.add(methodName);
+ }
+ super.setHasNativeMethods(hasNativeMethods, methodName);
+ }
+ }
+
+ /**
+ * Dummy test class with a native method.
+ */
+ public static class ClassWithNative {
+ public ClassWithNative() {
+ }
+
+ public void callTheNativeMethod() {
+ native_method();
+ }
+
+ private native void native_method();
+ }
+
+ /**
+ * Dummy test class with no native method.
+ */
+ public static class ClassWithoutNative {
+ public ClassWithoutNative() {
+ }
+
+ public void someMethod() {
+ }
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
new file mode 100644
index 0000000..6e120ce
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2010 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.tools.layoutlib.create;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.layoutlib.create.dataclass.ClassWithNative;
+import com.android.tools.layoutlib.create.dataclass.OuterClass;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class DelegateClassAdapterTest {
+
+ private MockLog mLog;
+
+ private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getCanonicalName();
+ private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName();
+ private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" +
+ InnerClass.class.getSimpleName();
+
+ @Before
+ public void setUp() throws Exception {
+ mLog = new MockLog();
+ mLog.setVerbose(true); // capture debug error too
+ }
+
+ /**
+ * Tests that a class not being modified still works.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testNoOp() throws Throwable {
+ // create an instance of the class that will be modified
+ // (load the class in a distinct class loader so that we can trash its definition later)
+ ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
+ Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME);
+ ClassWithNative instance1 = clazz1.newInstance();
+ assertEquals(42, instance1.add(20, 22));
+ try {
+ instance1.callNativeInstance(10, 3.1415, new Object[0] );
+ fail("Test should have failed to invoke callTheNativeMethod [1]");
+ } catch (UnsatisfiedLinkError e) {
+ // This is expected to fail since the native method is not implemented.
+ }
+
+ // Now process it but tell the delegate to not modify any method
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+
+ // Load the generated class in a different class loader and try it again
+
+ ClassLoader2 cl2 = null;
+ try {
+ cl2 = new ClassLoader2() {
+ @Override
+ public void testModifiedInstance() throws Exception {
+ Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
+ Object i2 = clazz2.newInstance();
+ assertNotNull(i2);
+ assertEquals(42, callAdd(i2, 20, 22));
+
+ try {
+ callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
+ fail("Test should have failed to invoke callTheNativeMethod [2]");
+ } catch (InvocationTargetException e) {
+ // This is expected to fail since the native method has NOT been
+ // overridden here.
+ assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
+ }
+
+ // Check that the native method does NOT have the new annotation
+ Method[] m = clazz2.getDeclaredMethods();
+ assertEquals("native_instance", m[2].getName());
+ assertTrue(Modifier.isNative(m[2].getModifiers()));
+ Annotation[] a = m[2].getAnnotations();
+ assertEquals(0, a.length);
+ }
+ };
+ cl2.add(NATIVE_CLASS_NAME, cw);
+ cl2.testModifiedInstance();
+ } catch (Throwable t) {
+ throw dumpGeneratedClass(t, cl2);
+ }
+ }
+
+ /**
+ * {@link DelegateMethodAdapter2} does not support overriding constructors yet,
+ * so this should fail with an {@link UnsupportedOperationException}.
+ *
+ * Although not tested here, the message of the exception should contain the
+ * constructor signature.
+ */
+ @Test(expected=UnsupportedOperationException.class)
+ public void testConstructorsNotSupported() throws IOException {
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+ String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add("<init>");
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+ }
+
+ @Test
+ public void testDelegateNative() throws Throwable {
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+ String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+
+ // Load the generated class in a different class loader and try it
+ ClassLoader2 cl2 = null;
+ try {
+ cl2 = new ClassLoader2() {
+ @Override
+ public void testModifiedInstance() throws Exception {
+ Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
+ Object i2 = clazz2.newInstance();
+ assertNotNull(i2);
+
+ // Use reflection to access inner methods
+ assertEquals(42, callAdd(i2, 20, 22));
+
+ Object[] objResult = new Object[] { null };
+ int result = callCallNativeInstance(i2, 10, 3.1415, objResult);
+ assertEquals((int)(10 + 3.1415), result);
+ assertSame(i2, objResult[0]);
+
+ // Check that the native method now has the new annotation and is not native
+ Method[] m = clazz2.getDeclaredMethods();
+ assertEquals("native_instance", m[2].getName());
+ assertFalse(Modifier.isNative(m[2].getModifiers()));
+ Annotation[] a = m[2].getAnnotations();
+ assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
+ }
+ };
+ cl2.add(NATIVE_CLASS_NAME, cw);
+ cl2.testModifiedInstance();
+ } catch (Throwable t) {
+ throw dumpGeneratedClass(t, cl2);
+ }
+ }
+
+ @Test
+ public void testDelegateInner() throws Throwable {
+ // We'll delegate the "get" method of both the inner and outer class.
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add("get");
+ delegateMethods.add("privateMethod");
+
+ // Generate the delegate for the outer class.
+ ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
+ String outerClassName = OUTER_CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cvOuter = new DelegateClassAdapter(
+ mLog, cwOuter, outerClassName, delegateMethods);
+ ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
+ cr.accept(cvOuter, 0 /* flags */);
+
+ // Generate the delegate for the inner class.
+ ClassWriter cwInner = new ClassWriter(0 /*flags*/);
+ String innerClassName = INNER_CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cvInner = new DelegateClassAdapter(
+ mLog, cwInner, innerClassName, delegateMethods);
+ cr = new ClassReader(INNER_CLASS_NAME);
+ cr.accept(cvInner, 0 /* flags */);
+
+ // Load the generated classes in a different class loader and try them
+ ClassLoader2 cl2 = null;
+ try {
+ cl2 = new ClassLoader2() {
+ @Override
+ public void testModifiedInstance() throws Exception {
+
+ // Check the outer class
+ Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
+ Object o2 = outerClazz2.newInstance();
+ assertNotNull(o2);
+
+ // The original Outer.get returns 1+10+20,
+ // but the delegate makes it return 4+10+20
+ assertEquals(4+10+20, callGet(o2, 10, 20));
+ assertEquals(1+10+20, callGet_Original(o2, 10, 20));
+
+ // The original Outer has a private method that is
+ // delegated. We should be able to call both the delegate
+ // and the original (which is now public).
+ assertEquals("outerPrivateMethod",
+ callMethod(o2, "privateMethod_Original", false /*makePublic*/));
+
+ // The original method is private, so by default we can't access it
+ boolean gotIllegalAccessException = false;
+ try {
+ callMethod(o2, "privateMethod", false /*makePublic*/);
+ } catch(IllegalAccessException e) {
+ gotIllegalAccessException = true;
+ }
+ assertTrue(gotIllegalAccessException);
+ // Try again, but now making it accessible
+ assertEquals("outerPrivate_Delegate",
+ callMethod(o2, "privateMethod", true /*makePublic*/));
+
+ // Check the inner class. Since it's not a static inner class, we need
+ // to use the hidden constructor that takes the outer class as first parameter.
+ Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME);
+ Constructor<?> innerCons = innerClazz2.getConstructor(
+ new Class<?>[] { outerClazz2 });
+ Object i2 = innerCons.newInstance(new Object[] { o2 });
+ assertNotNull(i2);
+
+ // The original Inner.get returns 3+10+20,
+ // but the delegate makes it return 6+10+20
+ assertEquals(6+10+20, callGet(i2, 10, 20));
+ assertEquals(3+10+20, callGet_Original(i2, 10, 20));
+ }
+ };
+ cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
+ cl2.add(INNER_CLASS_NAME, cwInner.toByteArray());
+ cl2.testModifiedInstance();
+ } catch (Throwable t) {
+ throw dumpGeneratedClass(t, cl2);
+ }
+ }
+
+ //-------
+
+ /**
+ * A class loader than can define and instantiate our modified classes.
+ * <p/>
+ * The trick here is that this class loader will test our <em>modified</em> version
+ * of the classes, the one with the delegate calls.
+ * <p/>
+ * Trying to do so in the original class loader generates all sort of link issues because
+ * there are 2 different definitions of the same class name. This class loader will
+ * define and load the class when requested by name and provide helpers to access the
+ * instance methods via reflection.
+ */
+ private abstract class ClassLoader2 extends ClassLoader {
+
+ private final Map<String, byte[]> mClassDefs = new HashMap<String, byte[]>();
+
+ public ClassLoader2() {
+ super(null);
+ }
+
+ public ClassLoader2 add(String className, byte[] definition) {
+ mClassDefs.put(className, definition);
+ return this;
+ }
+
+ public ClassLoader2 add(String className, ClassWriter rewrittenClass) {
+ mClassDefs.put(className, rewrittenClass.toByteArray());
+ return this;
+ }
+
+ private Set<Entry<String, byte[]>> getByteCode() {
+ return mClassDefs.entrySet();
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ try {
+ return super.findClass(name);
+ } catch (ClassNotFoundException e) {
+
+ byte[] def = mClassDefs.get(name);
+ if (def != null) {
+ // Load the modified ClassWithNative from its bytes representation.
+ return defineClass(name, def, 0, def.length);
+ }
+
+ try {
+ // Load everything else from the original definition into the new class loader.
+ ClassReader cr = new ClassReader(name);
+ ClassWriter cw = new ClassWriter(0);
+ cr.accept(cw, 0);
+ byte[] bytes = cw.toByteArray();
+ return defineClass(name, bytes, 0, bytes.length);
+
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ }
+
+ /**
+ * Accesses {@link OuterClass#get} or {@link InnerClass#get}via reflection.
+ */
+ public int callGet(Object instance, int a, long b) throws Exception {
+ Method m = instance.getClass().getMethod("get",
+ new Class<?>[] { int.class, long.class } );
+
+ Object result = m.invoke(instance, new Object[] { a, b });
+ return ((Integer) result).intValue();
+ }
+
+ /**
+ * Accesses the "_Original" methods for {@link OuterClass#get}
+ * or {@link InnerClass#get}via reflection.
+ */
+ public int callGet_Original(Object instance, int a, long b) throws Exception {
+ Method m = instance.getClass().getMethod("get_Original",
+ new Class<?>[] { int.class, long.class } );
+
+ Object result = m.invoke(instance, new Object[] { a, b });
+ return ((Integer) result).intValue();
+ }
+
+ /**
+ * Accesses the any declared method that takes no parameter via reflection.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T callMethod(Object instance, String methodName, boolean makePublic) throws Exception {
+ Method m = instance.getClass().getDeclaredMethod(methodName, (Class<?>[])null);
+
+ boolean wasAccessible = m.isAccessible();
+ if (makePublic && !wasAccessible) {
+ m.setAccessible(true);
+ }
+
+ Object result = m.invoke(instance, (Object[])null);
+
+ if (makePublic && !wasAccessible) {
+ m.setAccessible(false);
+ }
+
+ return (T) result;
+ }
+
+ /**
+ * Accesses {@link ClassWithNative#add(int, int)} via reflection.
+ */
+ public int callAdd(Object instance, int a, int b) throws Exception {
+ Method m = instance.getClass().getMethod("add",
+ new Class<?>[] { int.class, int.class });
+
+ Object result = m.invoke(instance, new Object[] { a, b });
+ return ((Integer) result).intValue();
+ }
+
+ /**
+ * Accesses {@link ClassWithNative#callNativeInstance(int, double, Object[])}
+ * via reflection.
+ */
+ public int callCallNativeInstance(Object instance, int a, double d, Object[] o)
+ throws Exception {
+ Method m = instance.getClass().getMethod("callNativeInstance",
+ new Class<?>[] { int.class, double.class, Object[].class });
+
+ Object result = m.invoke(instance, new Object[] { a, d, o });
+ return ((Integer) result).intValue();
+ }
+
+ public abstract void testModifiedInstance() throws Exception;
+ }
+
+ /**
+ * For debugging, it's useful to dump the content of the generated classes
+ * along with the exception that was generated.
+ *
+ * However to make it work you need to pull in the org.objectweb.asm.util.TraceClassVisitor
+ * class and associated utilities which are found in the ASM source jar. Since we don't
+ * want that dependency in the source code, we only put it manually for development and
+ * access the TraceClassVisitor via reflection if present.
+ *
+ * @param t The exception thrown by {@link ClassLoader2#testModifiedInstance()}
+ * @param cl2 The {@link ClassLoader2} instance with the generated bytecode.
+ * @return Either original {@code t} or a new wrapper {@link Throwable}
+ */
+ private Throwable dumpGeneratedClass(Throwable t, ClassLoader2 cl2) {
+ try {
+ // For debugging, dump the bytecode of the class in case of unexpected error
+ // if we can find the TraceClassVisitor class.
+ Class<?> tcvClass = Class.forName("org.objectweb.asm.util.TraceClassVisitor");
+
+ StringBuilder sb = new StringBuilder();
+ sb.append('\n').append(t.getClass().getCanonicalName());
+ if (t.getMessage() != null) {
+ sb.append(": ").append(t.getMessage());
+ }
+
+ for (Entry<String, byte[]> entry : cl2.getByteCode()) {
+ String className = entry.getKey();
+ byte[] bytes = entry.getValue();
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ // next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw);
+ Constructor<?> cons = tcvClass.getConstructor(new Class<?>[] { pw.getClass() });
+ Object tcv = cons.newInstance(new Object[] { pw });
+ ClassReader cr2 = new ClassReader(bytes);
+ cr2.accept((ClassVisitor) tcv, 0 /* flags */);
+
+ sb.append("\nBytecode dump: <").append(className).append(">:\n")
+ .append(sw.toString());
+ }
+
+ // Re-throw exception with new message
+ RuntimeException ex = new RuntimeException(sb.toString(), t);
+ return ex;
+ } catch (Throwable ignore) {
+ // In case of problem, just throw the original exception as-is.
+ return t;
+ }
+ }
+
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
new file mode 100644
index 0000000..1a5f653
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import static org.junit.Assert.*;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LogTest {
+
+ private MockLog mLog;
+
+ @Before
+ public void setUp() throws Exception {
+ mLog = new MockLog();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // pass
+ }
+
+ @Test
+ public void testDebug() {
+ assertEquals("", mLog.getOut());
+ assertEquals("", mLog.getErr());
+
+ mLog.setVerbose(false);
+ mLog.debug("Test %d", 42);
+ assertEquals("", mLog.getOut());
+
+ mLog.setVerbose(true);
+ mLog.debug("Test %d", 42);
+
+ assertEquals("Test 42\n", mLog.getOut());
+ assertEquals("", mLog.getErr());
+ }
+
+ @Test
+ public void testInfo() {
+ assertEquals("", mLog.getOut());
+ assertEquals("", mLog.getErr());
+
+ mLog.info("Test %d", 43);
+
+ assertEquals("Test 43\n", mLog.getOut());
+ assertEquals("", mLog.getErr());
+ }
+
+ @Test
+ public void testError() {
+ assertEquals("", mLog.getOut());
+ assertEquals("", mLog.getErr());
+
+ mLog.error("Test %d", 44);
+
+ assertEquals("", mLog.getOut());
+ assertEquals("Test 44\n", mLog.getErr());
+ }
+
+ @Test
+ public void testException() {
+ assertEquals("", mLog.getOut());
+ assertEquals("", mLog.getErr());
+
+ Exception e = new Exception("My Exception");
+ mLog.exception(e, "Test %d", 44);
+
+ assertEquals("", mLog.getOut());
+ assertTrue(mLog.getErr().startsWith("Test 44\njava.lang.Exception: My Exception"));
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
new file mode 100644
index 0000000..de750a3
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 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.tools.layoutlib.create;
+
+
+public class MockLog extends Log {
+ StringBuilder mOut = new StringBuilder();
+ StringBuilder mErr = new StringBuilder();
+
+ public String getOut() {
+ return mOut.toString();
+ }
+
+ public String getErr() {
+ return mErr.toString();
+ }
+
+ @Override
+ protected void outPrintln(String msg) {
+ mOut.append(msg);
+ mOut.append('\n');
+ }
+
+ @Override
+ protected void errPrintln(String msg) {
+ mErr.append(msg);
+ mErr.append('\n');
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
new file mode 100644
index 0000000..6211e73
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+
+package com.android.tools.layoutlib.create;
+
+import static org.junit.Assert.*;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class RenameClassAdapterTest {
+
+ private RenameClassAdapter mOuter;
+ private RenameClassAdapter mInner;
+
+ @Before
+ public void setUp() throws Exception {
+ mOuter = new RenameClassAdapter(null, // cv
+ "com.pack.Old",
+ "org.blah.New");
+
+ mInner = new RenameClassAdapter(null, // cv
+ "com.pack.Old$Inner",
+ "org.blah.New$Inner");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ /**
+ * Renames a type, e.g. "Lcom.package.My;"
+ * If the type doesn't need to be renamed, returns the input string as-is.
+ */
+ @Test
+ public void testRenameTypeDesc() {
+
+ // primitive types are left untouched
+ assertEquals("I", mOuter.renameTypeDesc("I"));
+ assertEquals("D", mOuter.renameTypeDesc("D"));
+ assertEquals("V", mOuter.renameTypeDesc("V"));
+
+ // object types that need no renaming are left untouched
+ assertEquals("Lcom.package.MyClass;", mOuter.renameTypeDesc("Lcom.package.MyClass;"));
+ assertEquals("Lcom.package.MyClass;", mInner.renameTypeDesc("Lcom.package.MyClass;"));
+
+ // object types that match the requirements
+ assertEquals("Lorg.blah.New;", mOuter.renameTypeDesc("Lcom.pack.Old;"));
+ assertEquals("Lorg.blah.New$Inner;", mInner.renameTypeDesc("Lcom.pack.Old$Inner;"));
+ // inner classes match the base type which is being renamed
+ assertEquals("Lorg.blah.New$Other;", mOuter.renameTypeDesc("Lcom.pack.Old$Other;"));
+ assertEquals("Lorg.blah.New$Other;", mInner.renameTypeDesc("Lcom.pack.Old$Other;"));
+
+ // arrays
+ assertEquals("[Lorg.blah.New;", mOuter.renameTypeDesc("[Lcom.pack.Old;"));
+ assertEquals("[[Lorg.blah.New;", mOuter.renameTypeDesc("[[Lcom.pack.Old;"));
+
+ assertEquals("[Lorg.blah.New;", mInner.renameTypeDesc("[Lcom.pack.Old;"));
+ assertEquals("[[Lorg.blah.New;", mInner.renameTypeDesc("[[Lcom.pack.Old;"));
+ }
+
+ /**
+ * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
+ * object element, e.g. "[Lcom.package.MyClass;"
+ * If the type doesn't need to be renamed, returns the internal name of the input type.
+ */
+ @Test
+ public void testRenameType() {
+ // Skip. This is actually tested by testRenameTypeDesc above.
+ }
+
+ /**
+ * Renames an internal type name, e.g. "com.package.MyClass".
+ * If the type doesn't need to be renamed, returns the input string as-is.
+ */
+ @Test
+ public void testRenameInternalType() {
+ // an actual FQCN
+ assertEquals("org.blah.New", mOuter.renameInternalType("com.pack.Old"));
+ assertEquals("org.blah.New$Inner", mOuter.renameInternalType("com.pack.Old$Inner"));
+
+ assertEquals("org.blah.New$Other", mInner.renameInternalType("com.pack.Old$Other"));
+ assertEquals("org.blah.New$Other", mInner.renameInternalType("com.pack.Old$Other"));
+ }
+
+ /**
+ * Renames a method descriptor, i.e. applies renameType to all arguments and to the
+ * return value.
+ */
+ @Test
+ public void testRenameMethodDesc() {
+ assertEquals("(IDLorg.blah.New;[Lorg.blah.New$Inner;)Lorg.blah.New$Other;",
+ mOuter.renameMethodDesc("(IDLcom.pack.Old;[Lcom.pack.Old$Inner;)Lcom.pack.Old$Other;"));
+ }
+
+
+
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java
new file mode 100644
index 0000000..c314853
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 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.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * Dummy test class with a native method.
+ * The native method is not defined and any attempt to invoke it will
+ * throw an {@link UnsatisfiedLinkError}.
+ *
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class ClassWithNative {
+ public ClassWithNative() {
+ }
+
+ public int add(int a, int b) {
+ return a + b;
+ }
+
+ // Note: it's good to have a long or double for testing parameters since they take
+ // 2 slots in the stack/locals maps.
+
+ public int callNativeInstance(int a, double d, Object[] o) {
+ return native_instance(a, d, o);
+ }
+
+ private native int native_instance(int a, double d, Object[] o);
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java
new file mode 100644
index 0000000..a3d4dc6
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 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.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * The delegate that receives the call to {@link ClassWithNative_Delegate}'s overridden methods.
+ *
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class ClassWithNative_Delegate {
+ public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
+ if (o != null && o.length > 0) {
+ o[0] = instance;
+ }
+ return (int)(a + d);
+ }
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java
new file mode 100644
index 0000000..9b5a918
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.tools.layoutlib.create.dataclass;
+
+public final class JavaClass {
+
+ public static final String test = "test";
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
new file mode 100644
index 0000000..f083e76
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011 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.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * Test class with an inner class.
+ *
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass {
+ private int mOuterValue = 1;
+ public OuterClass() {
+ }
+
+ // Outer.get returns 1 + a + b
+ // Note: it's good to have a long or double for testing parameters since they take
+ // 2 slots in the stack/locals maps.
+ public int get(int a, long b) {
+ return mOuterValue + a + (int) b;
+ }
+
+ public class InnerClass {
+ public InnerClass() {
+ }
+
+ // Inner.get returns 2 + 1 + a + b
+ public int get(int a, long b) {
+ return 2 + mOuterValue + a + (int) b;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private String privateMethod() {
+ return "outerPrivateMethod";
+ }
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java
new file mode 100644
index 0000000..774be8e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2011 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.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass_Delegate {
+ // The delegate override of Outer.get returns 4 + a + b
+ public static int get(OuterClass instance, int a, long b) {
+ return 4 + a + (int) b;
+ }
+
+ public static String privateMethod(OuterClass instance) {
+ return "outerPrivate_Delegate";
+ }
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java
new file mode 100644
index 0000000..b472220
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 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.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
+
+/**
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass_InnerClass_Delegate {
+ // The delegate override of Inner.get return 6 + a + b
+ public static int get(OuterClass outer, InnerClass inner, int a, long b) {
+ return 6 + a + (int) b;
+ }
+}
diff --git a/tools/layoutlib/create/tests/data/mock_android.jar b/tools/layoutlib/create/tests/data/mock_android.jar
new file mode 100644
index 0000000..60d8efb
--- /dev/null
+++ b/tools/layoutlib/create/tests/data/mock_android.jar
Binary files differ
diff --git a/tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java b/tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java
new file mode 100644
index 0000000..59612e9
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 java.lang;
+
+public class JavaClass {
+
+ public static String test = "test";
+}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java b/tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java
new file mode 100644
index 0000000..d3a1d05
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 mock_android.dummy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+public class InnerTest {
+
+ private int mSomeField;
+ private MyStaticInnerClass mInnerInstance;
+ private MyIntEnum mTheIntEnum;
+ private MyGenerics1<int[][], InnerTest, MyIntEnum, float[]> mGeneric1;
+
+ public class NotStaticInner2 extends NotStaticInner1 {
+
+ }
+
+ public class NotStaticInner1 {
+
+ public void someThing() {
+ mSomeField = 2;
+ mInnerInstance = null;
+ }
+
+ }
+
+ private static class MyStaticInnerClass {
+
+ }
+
+ private static class DerivingClass extends InnerTest {
+
+ }
+
+ // enums are a kind of inner static class
+ public enum MyIntEnum {
+ VALUE0(0),
+ VALUE1(1),
+ VALUE2(2);
+
+ MyIntEnum(int myInt) {
+ this.myInt = myInt;
+ }
+ final int myInt;
+ }
+
+ public static class MyGenerics1<T, U, V, W> {
+ public MyGenerics1() {
+ int a = 1;
+ }
+ }
+
+ public <X> void genericMethod1(X a, X[] b) {
+ }
+
+ public <X, Y> void genericMethod2(X a, List<Y> b) {
+ }
+
+ public <X, Y extends InnerTest> void genericMethod3(X a, List<Y> b) {
+ }
+
+ public <T extends InnerTest> void genericMethod4(T[] a, Collection<T> b, Collection<?> c) {
+ Iterator<T> i = b.iterator();
+ }
+
+ public void someMethod(InnerTest self) {
+ mSomeField = self.mSomeField;
+ MyStaticInnerClass m = new MyStaticInnerClass();
+ mInnerInstance = m;
+ mTheIntEnum = null;
+ mGeneric1 = new MyGenerics1();
+ genericMethod4(new DerivingClass[0], new ArrayList<DerivingClass>(), new ArrayList<InnerTest>());
+ }
+}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/view/View.java b/tools/layoutlib/create/tests/mock_data/mock_android/view/View.java
new file mode 100644
index 0000000..84ec8a9
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/view/View.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 mock_android.view;
+
+import java.lang.JavaClass;
+
+public class View {
+
+ String x = JavaClass.test;
+
+}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/view/ViewGroup.java b/tools/layoutlib/create/tests/mock_data/mock_android/view/ViewGroup.java
new file mode 100644
index 0000000..466470f
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/view/ViewGroup.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 mock_android.view;
+
+public class ViewGroup extends View {
+
+ public class MarginLayoutParams extends LayoutParams {
+
+ }
+
+ public class LayoutParams {
+
+ }
+
+}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java b/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java
new file mode 100644
index 0000000..3870a63
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 mock_android.widget;
+
+import mock_android.view.ViewGroup;
+
+public class LinearLayout extends ViewGroup {
+
+ public class LayoutParams extends mock_android.view.ViewGroup.LayoutParams {
+
+ }
+
+}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/widget/TableLayout.java b/tools/layoutlib/create/tests/mock_data/mock_android/widget/TableLayout.java
new file mode 100644
index 0000000..e455e7d
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/widget/TableLayout.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 mock_android.widget;
+
+import mock_android.view.ViewGroup;
+
+public class TableLayout extends ViewGroup {
+
+ public class LayoutParams extends MarginLayoutParams {
+
+ }
+
+}
diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk
new file mode 100644
index 0000000..9ff56d6
--- /dev/null
+++ b/tools/obbtool/Android.mk
@@ -0,0 +1,48 @@
+#
+# Copyright 2010 The Android Open Source Project
+#
+# Opaque Binary Blob (OBB) Tool
+#
+
+# This tool is prebuilt if we're doing an app-only build.
+ifeq ($(TARGET_BUILD_APPS),)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ Main.cpp
+
+LOCAL_CFLAGS := -Wall -Werror
+
+#LOCAL_C_INCLUDES +=
+
+LOCAL_STATIC_LIBRARIES := \
+ libandroidfw \
+ libutils \
+ libcutils \
+ liblog
+
+ifeq ($(HOST_OS),linux)
+LOCAL_LDLIBS += -ldl -lpthread
+endif
+
+LOCAL_MODULE := obbtool
+
+include $(BUILD_HOST_EXECUTABLE)
+
+#####################################################
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := pbkdf2gen
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := -Wall -Werror
+LOCAL_SRC_FILES := pbkdf2gen.cpp
+LOCAL_LDLIBS += -ldl
+LOCAL_C_INCLUDES := external/openssl/include $(LOCAL_C_INCLUDES)
+LOCAL_STATIC_LIBRARIES := libcrypto_static
+
+include $(BUILD_HOST_EXECUTABLE)
+
+#######################################################
+endif # TARGET_BUILD_APPS
diff --git a/tools/obbtool/Main.cpp b/tools/obbtool/Main.cpp
new file mode 100644
index 0000000..b2152e8
--- /dev/null
+++ b/tools/obbtool/Main.cpp
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <androidfw/ObbFile.h>
+#include <utils/String8.h>
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+using namespace android;
+
+static const char* gProgName = "obbtool";
+static const char* gProgVersion = "1.0";
+
+static int wantUsage = 0;
+static int wantVersion = 0;
+
+#define SALT_LEN 8
+
+#define ADD_OPTS "n:v:os:"
+static const struct option longopts[] = {
+ {"help", no_argument, &wantUsage, 1},
+ {"version", no_argument, &wantVersion, 1},
+
+ /* Args for "add" */
+ {"name", required_argument, NULL, 'n'},
+ {"version", required_argument, NULL, 'v'},
+ {"overlay", optional_argument, NULL, 'o'},
+ {"salt", required_argument, NULL, 's'},
+
+ {NULL, 0, NULL, '\0'}
+};
+
+class PackageInfo {
+public:
+ PackageInfo()
+ : packageName(NULL)
+ , packageVersion(-1)
+ , overlay(false)
+ , salted(false)
+ {
+ memset(&salt, 0, sizeof(salt));
+ }
+
+ char* packageName;
+ int packageVersion;
+ bool overlay;
+ bool salted;
+ unsigned char salt[SALT_LEN];
+};
+
+/*
+ * Print usage info.
+ */
+void usage(void)
+{
+ fprintf(stderr, "Opaque Binary Blob (OBB) Tool\n\n");
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr,
+ " %s a[dd] [ OPTIONS ] FILENAME\n"
+ " Adds an OBB signature to the file.\n\n", gProgName);
+ fprintf(stderr,
+ " Options:\n"
+ " -n <package name> sets the OBB package name (required)\n"
+ " -v <OBB version> sets the OBB version (required)\n"
+ " -o sets the OBB overlay flag\n"
+ " -s <8 byte hex salt> sets the crypto key salt (if encrypted)\n"
+ "\n");
+ fprintf(stderr,
+ " %s r[emove] FILENAME\n"
+ " Removes the OBB signature from the file.\n\n", gProgName);
+ fprintf(stderr,
+ " %s i[nfo] FILENAME\n"
+ " Prints the OBB signature information of a file.\n\n", gProgName);
+}
+
+void doAdd(const char* filename, struct PackageInfo* info) {
+ ObbFile *obb = new ObbFile();
+ if (obb->readFrom(filename)) {
+ fprintf(stderr, "ERROR: %s: OBB signature already present\n", filename);
+ return;
+ }
+
+ obb->setPackageName(String8(info->packageName));
+ obb->setVersion(info->packageVersion);
+ obb->setOverlay(info->overlay);
+ if (info->salted) {
+ obb->setSalt(info->salt, SALT_LEN);
+ }
+
+ if (!obb->writeTo(filename)) {
+ fprintf(stderr, "ERROR: %s: couldn't write OBB signature: %s\n",
+ filename, strerror(errno));
+ return;
+ }
+
+ fprintf(stderr, "OBB signature successfully written\n");
+}
+
+void doRemove(const char* filename) {
+ ObbFile *obb = new ObbFile();
+ if (!obb->readFrom(filename)) {
+ fprintf(stderr, "ERROR: %s: no OBB signature present\n", filename);
+ return;
+ }
+
+ if (!obb->removeFrom(filename)) {
+ fprintf(stderr, "ERROR: %s: couldn't remove OBB signature\n", filename);
+ return;
+ }
+
+ fprintf(stderr, "OBB signature successfully removed\n");
+}
+
+void doInfo(const char* filename) {
+ ObbFile *obb = new ObbFile();
+ if (!obb->readFrom(filename)) {
+ fprintf(stderr, "ERROR: %s: couldn't read OBB signature\n", filename);
+ return;
+ }
+
+ printf("OBB info for '%s':\n", filename);
+ printf("Package name: %s\n", obb->getPackageName().string());
+ printf(" Version: %d\n", obb->getVersion());
+ printf(" Flags: 0x%08x\n", obb->getFlags());
+ printf(" Overlay: %s\n", obb->isOverlay() ? "true" : "false");
+ printf(" Salt: ");
+
+ size_t saltLen;
+ const unsigned char* salt = obb->getSalt(&saltLen);
+ if (salt != NULL) {
+ for (int i = 0; i < SALT_LEN; i++) {
+ printf("%02x", salt[i]);
+ }
+ printf("\n");
+ } else {
+ printf("<empty>\n");
+ }
+}
+
+bool fromHex(char h, unsigned char *b) {
+ if (h >= '0' && h <= '9') {
+ *b = h - '0';
+ return true;
+ } else if (h >= 'a' && h <= 'f') {
+ *b = h - 'a' + 10;
+ return true;
+ } else if (h >= 'A' && h <= 'F') {
+ *b = h - 'A' + 10;
+ return true;
+ }
+ return false;
+}
+
+bool hexToByte(char h1, char h2, unsigned char* b) {
+ unsigned char first, second;
+ if (!fromHex(h1, &first)) return false;
+ if (!fromHex(h2, &second)) return false;
+ *b = (first << 4) | second;
+ return true;
+}
+
+/*
+ * Parse args.
+ */
+int main(int argc, char* const argv[])
+{
+ int opt;
+ int option_index = 0;
+ struct PackageInfo package_info;
+
+ int result = 1; // pessimistically assume an error.
+
+ if (argc < 2) {
+ wantUsage = 1;
+ goto bail;
+ }
+
+ while ((opt = getopt_long(argc, argv, ADD_OPTS, longopts, &option_index)) != -1) {
+ switch (opt) {
+ case 0:
+ if (longopts[option_index].flag)
+ break;
+ fprintf(stderr, "'%s' requires an argument\n", longopts[option_index].name);
+ wantUsage = 1;
+ goto bail;
+ case 'n':
+ package_info.packageName = optarg;
+ break;
+ case 'v': {
+ char* end;
+ package_info.packageVersion = strtol(optarg, &end, 10);
+ if (*optarg == '\0' || *end != '\0') {
+ fprintf(stderr, "ERROR: invalid version; should be integer!\n\n");
+ wantUsage = 1;
+ goto bail;
+ }
+ break;
+ }
+ case 'o':
+ package_info.overlay = true;
+ break;
+ case 's':
+ if (strlen(optarg) != SALT_LEN * 2) {
+ fprintf(stderr, "ERROR: salt must be 8 bytes in hex (e.g., ABCD65031337D00D)\n\n");
+ wantUsage = 1;
+ goto bail;
+ }
+
+ package_info.salted = true;
+
+ unsigned char b;
+ for (int i = 0, j = 0; i < SALT_LEN; i++, j+=2) {
+ if (!hexToByte(optarg[j], optarg[j+1], &b)) {
+ fprintf(stderr, "ERROR: salt must be in hex (e.g., ABCD65031337D00D)\n");
+ wantUsage = 1;
+ goto bail;
+ }
+ package_info.salt[i] = b;
+ }
+ break;
+ case '?':
+ wantUsage = 1;
+ goto bail;
+ }
+ }
+
+ if (wantVersion) {
+ fprintf(stderr, "%s %s\n", gProgName, gProgVersion);
+ }
+
+ if (wantUsage) {
+ goto bail;
+ }
+
+#define CHECK_OP(name) \
+ if (strncmp(op, name, opsize)) { \
+ fprintf(stderr, "ERROR: unknown function '%s'!\n\n", op); \
+ wantUsage = 1; \
+ goto bail; \
+ }
+
+ if (optind < argc) {
+ const char* op = argv[optind++];
+ const int opsize = strlen(op);
+
+ if (optind >= argc) {
+ fprintf(stderr, "ERROR: filename required!\n\n");
+ wantUsage = 1;
+ goto bail;
+ }
+
+ const char* filename = argv[optind++];
+
+ switch (op[0]) {
+ case 'a':
+ CHECK_OP("add");
+ if (package_info.packageName == NULL) {
+ fprintf(stderr, "ERROR: arguments required 'packageName' and 'version'\n");
+ goto bail;
+ }
+ doAdd(filename, &package_info);
+ break;
+ case 'r':
+ CHECK_OP("remove");
+ doRemove(filename);
+ break;
+ case 'i':
+ CHECK_OP("info");
+ doInfo(filename);
+ break;
+ default:
+ fprintf(stderr, "ERROR: unknown command '%s'!\n\n", op);
+ wantUsage = 1;
+ goto bail;
+ }
+ }
+
+bail:
+ if (wantUsage) {
+ usage();
+ result = 2;
+ }
+
+ return result;
+}
diff --git a/tools/obbtool/mkobb.sh b/tools/obbtool/mkobb.sh
new file mode 100755
index 0000000..725250d
--- /dev/null
+++ b/tools/obbtool/mkobb.sh
@@ -0,0 +1,281 @@
+#!/bin/bash
+#
+# Copyright (C) 2010 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.
+#
+
+# mkobb.sh - Creates OBB files on Linux machines
+
+# Directory where we should temporarily mount the OBB loopback to copy files
+MOUNTDIR=/tmp
+
+# Presets. Changing these will probably break your OBB on the device
+CRYPTO=twofish
+FS=vfat
+MKFS=mkfs.vfat
+LOSETUP=losetup
+BLOCK_SIZE=512
+SLOP=512 # Amount of filesystem slop in ${BLOCK_SIZE} blocks
+
+find_binaries() {
+ MKFSBIN=`which ${MKFS}`
+ LOSETUPBIN=`which ${LOSETUP}`
+ MOUNTBIN=`which mount`
+ UMOUNTBIN=`which umount`
+ DDBIN=`which dd`
+ RSYNCBIN=`which rsync`
+ PBKDF2GEN=`which pbkdf2gen`
+}
+
+check_prereqs() {
+ if [ "`uname -s`x" != "Linuxx" ]; then \
+ echo "ERROR: This script only works on Linux!"
+ exit 1
+ fi
+
+ if ! egrep -q "^cryptoloop " /proc/modules; then \
+ echo "ERROR: Could not find cryptoloop in the kernel."
+ echo "Perhaps you need to: modprobe cryptoloop"
+ exit 1
+ fi
+
+ if ! egrep -q "name\s*:\s*${CRYPTO}$" /proc/crypto; then \
+ echo "ERROR: Could not find crypto \`${CRYPTO}' in the kernel."
+ echo "Perhaps you need to: modprobe ${CRYPTO}"
+ exit 1
+ fi
+
+ if ! egrep -q "^\s*${FS}$" /proc/filesystems; then \
+ echo "ERROR: Could not find filesystem \`${FS}' in the kernel."
+ echo "Perhaps you need to: modprobe ${FS}"
+ exit 1
+ fi
+
+ if [ "${MKFSBIN}x" = "x" ]; then \
+ echo "ERROR: Could not find ${MKFS} in your path!"
+ exit 1
+ elif [ ! -x "${MKFSBIN}" ]; then \
+ echo "ERROR: ${MKFSBIN} is not executable!"
+ exit 1
+ fi
+
+ if [ "${LOSETUPBIN}x" = "x" ]; then \
+ echo "ERROR: Could not find ${LOSETUP} in your path!"
+ exit 1
+ elif [ ! -x "${LOSETUPBIN}" ]; then \
+ echo "ERROR: ${LOSETUPBIN} is not executable!"
+ exit 1
+ fi
+
+ if [ "${PBKDF2GEN}x" = "x" ]; then \
+ echo "ERROR: Could not find pbkdf2gen in your path!"
+ exit 1
+ fi
+}
+
+cleanup() {
+ if [ "${loopdev}x" != "x" ]; then \
+ ${LOSETUPBIN} -d ${loopdev}
+ fi
+}
+
+hidden_prompt() {
+ unset output
+ prompt="$1"
+ outvar="$2"
+ while read -s -n 1 -p "$prompt" c; do \
+ if [ "x$c" = "x" ]; then \
+ break
+ fi
+ prompt='*'
+ output="${output}${c}"
+ done
+ echo
+ eval $outvar="$output"
+ unset output
+}
+
+read_key() {
+ hidden_prompt " Encryption key: " key
+
+ if [ "${key}x" = "x" ]; then \
+ echo "ERROR: An empty key is not allowed!"
+ exit 1
+ fi
+
+ hidden_prompt "Encryption key (again): " key2
+
+ if [ "${key}x" != "${key2}x" ]; then \
+ echo "ERROR: Encryption keys do not match!"
+ exit 1
+ fi
+}
+
+onexit() {
+ if [ "x${temp_mount}" != "x" ]; then \
+ ${UMOUNTBIN} ${temp_mount}
+ rmdir ${temp_mount}
+ fi
+ if [ "x${loop_dev}" != "x" ]; then \
+ if [ ${use_crypto} -eq 1 ]; then \
+ dmsetup remove -f ${loop_dev}
+ ${LOSETUPBIN} -d ${old_loop_dev}
+ else \
+ ${LOSETUPBIN} -d ${loop_dev}
+ fi
+ fi
+ if [ "x${tempfile}" != "x" -a -f "${tempfile}" ]; then \
+ rm -f ${tempfile}
+ fi
+ if [ "x${keyfile}" != "x" -a -f "${keyfile}" ]; then \
+ rm -f ${keyfile}
+ fi
+ echo "Fatal error."
+ exit 1
+}
+
+usage() {
+ echo "mkobb.sh -- Create OBB files for use on Android"
+ echo ""
+ echo " -d <directory> Use <directory> as input for OBB files"
+ echo " -k <key> Use <key> to encrypt OBB file"
+ echo " -K Prompt for key to encrypt OBB file"
+ echo " -o <filename> Write OBB file out to <filename>"
+ echo " -v Verbose mode"
+ echo " -h Help; this usage screen"
+}
+
+find_binaries
+check_prereqs
+
+use_crypto=0
+
+args=`getopt -o d:hk:Ko:v -- "$@"`
+eval set -- "$args"
+
+while true; do \
+ case "$1" in
+ -d) directory=$2; shift 2;;
+ -h) usage; exit 1;;
+ -k) key=$2; use_crypto=1; shift 2;;
+ -K) prompt_key=1; use_crypto=1; shift;;
+ -v) verbose=1; shift;;
+ -o) filename=$2; shift 2;;
+ --) shift; break;;
+ *) echo "ERROR: Invalid argument in option parsing! Cannot recover. Ever."; exit 1;;
+ esac
+done
+
+if [ "${directory}x" = "x" -o ! -d "${directory}" ]; then \
+ echo "ERROR: Must specify valid input directory"
+ echo ""
+ usage
+ exit 1;
+fi
+
+if [ "${filename}x" = "x" ]; then \
+ echo "ERROR: Must specify filename"
+ echo ""
+ usage
+ exit 1;
+fi
+
+if [ ${use_crypto} -eq 1 -a "${key}x" = "x" -a 0${prompt_key} -eq 0 ]; then \
+ echo "ERROR: Crypto desired, but no key supplied or requested to prompt for."
+ exit 1
+fi
+
+if [ 0${prompt_key} -eq 1 ]; then \
+ read_key
+fi
+
+outdir=`dirname ${filename}`
+if [ ! -d "${outdir}" ]; then \
+ echo "ERROR: Output directory does not exist: ${outdir}"
+ exit 1
+fi
+
+# Make sure we clean up any stuff we create from here on during error conditions
+trap onexit ERR
+
+tempfile=$(tempfile -d ${outdir}) || ( echo "ERROR: couldn't create temporary file in ${outdir}"; exit 1 )
+
+block_count=`du -s --apparent-size --block-size=512 ${directory} | awk '{ print $1; }'`
+if [ $? -ne 0 ]; then \
+ echo "ERROR: Couldn't read size of input directory ${directory}"
+ exit 1
+fi
+
+echo "Creating temporary file..."
+${DDBIN} if=/dev/zero of=${tempfile} bs=${BLOCK_SIZE} count=$((${block_count} + ${SLOP})) > /dev/null 2>&1
+if [ $? -ne 0 ]; then \
+ echo "ERROR: creating temporary file: $?"
+fi
+
+loop_dev=$(${LOSETUPBIN} -f) || ( echo "ERROR: losetup wouldn't tell us the next unused device"; exit 1 )
+
+${LOSETUPBIN} ${loop_dev} ${tempfile} || ( echo "ERROR: couldn't create loopback device"; exit 1 )
+
+if [ ${use_crypto} -eq 1 ]; then \
+ eval `${PBKDF2GEN} ${key}`
+ unique_dm_name=`basename ${tempfile}`
+ echo "0 `blockdev --getsize ${loop_dev}` crypt ${CRYPTO} ${key} 0 ${loop_dev} 0" | dmsetup create ${unique_dm_name}
+ old_loop_dev=${loop_dev}
+ loop_dev=/dev/mapper/${unique_dm_name}
+fi
+
+#
+# Create the filesystem
+#
+echo ""
+${MKFSBIN} -I ${loop_dev}
+echo ""
+
+#
+# Make the temporary mount point and mount it
+#
+temp_mount="${MOUNTDIR}/${RANDOM}"
+mkdir ${temp_mount}
+${MOUNTBIN} -t ${FS} -o loop ${loop_dev} ${temp_mount}
+
+#
+# rsync the files!
+#
+echo "Copying files:"
+${RSYNCBIN} -av --no-owner --no-group ${directory}/ ${temp_mount}/
+echo ""
+
+echo "Successfully created \`${filename}'"
+
+if [ ${use_crypto} -eq 1 ]; then \
+ echo "salt for use with obbtool is:"
+ echo "${salt}"
+fi
+
+#
+# Undo all the temporaries
+#
+umount ${temp_mount}
+rmdir ${temp_mount}
+if [ ${use_crypto} -eq 1 ]; then \
+ dmsetup remove -f ${loop_dev}
+ ${LOSETUPBIN} -d ${old_loop_dev}
+else \
+ ${LOSETUPBIN} -d ${loop_dev}
+fi
+mv ${tempfile} ${filename}
+
+trap - ERR
+
+exit 0
diff --git a/tools/obbtool/pbkdf2gen.cpp b/tools/obbtool/pbkdf2gen.cpp
new file mode 100644
index 0000000..98d67c0
--- /dev/null
+++ b/tools/obbtool/pbkdf2gen.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <openssl/evp.h>
+
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+/**
+ * Simple program to generate a key based on PBKDF2 with preset inputs.
+ *
+ * Will print out the salt and key in hex.
+ */
+
+#define SALT_LEN 8
+#define ROUNDS 1024
+#define KEY_BITS 128
+
+int main(int argc, char* argv[])
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <password>\n", argv[0]);
+ exit(1);
+ }
+
+ int fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Could not open /dev/urandom: %s\n", strerror(errno));
+ close(fd);
+ exit(1);
+ }
+
+ unsigned char salt[SALT_LEN];
+
+ if (read(fd, &salt, SALT_LEN) != SALT_LEN) {
+ fprintf(stderr, "Could not read salt from /dev/urandom: %s\n", strerror(errno));
+ close(fd);
+ exit(1);
+ }
+ close(fd);
+
+ unsigned char rawKey[KEY_BITS];
+
+ if (PKCS5_PBKDF2_HMAC_SHA1(argv[1], strlen(argv[1]), salt, SALT_LEN,
+ ROUNDS, KEY_BITS, rawKey) != 1) {
+ fprintf(stderr, "Could not generate PBKDF2 output: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ printf("salt=");
+ for (int i = 0; i < SALT_LEN; i++) {
+ printf("%02x", salt[i]);
+ }
+ printf("\n");
+
+ printf("key=");
+ for (int i = 0; i < (KEY_BITS / 8); i++) {
+ printf("%02x", rawKey[i]);
+ }
+ printf("\n");
+}
diff --git a/tools/orientationplot/README.txt b/tools/orientationplot/README.txt
new file mode 100644
index 0000000..d53f65e
--- /dev/null
+++ b/tools/orientationplot/README.txt
@@ -0,0 +1,87 @@
+This directory contains a simple python script for visualizing
+the behavior of the WindowOrientationListener.
+
+
+PREREQUISITES
+-------------
+
+1. Python 2.6
+2. numpy
+3. matplotlib
+
+
+USAGE
+-----
+
+The tool works by scaping the debug log output from WindowOrientationListener
+for interesting data and then plotting it.
+
+1. Plug in the device. Ensure that it is the only device plugged in
+ since this script is of very little brain and will get confused otherwise.
+
+2. Enable the Window Orientation Listener debugging data log.
+ adb shell setprop debug.orientation.log true
+ adb shell stop
+ adb shell start
+
+3. Run "orientationplot.py".
+
+
+WHAT IT ALL MEANS
+-----------------
+
+The tool displays several time series graphs that plot the output of the
+WindowOrientationListener. Here you can see the raw accelerometer data,
+filtered accelerometer data, measured tilt and orientation angle, confidence
+intervals for the proposed orientation and accelerometer latency.
+
+Things to look for:
+
+1. Ensure the filtering is not too aggressive. If the filter cut-off frequency is
+ less than about 1Hz, then the filtered accelorometer data becomes too smooth
+ and the latency for orientation detection goes up. One way to observe this
+ is by holding the device vertically in one orientation then sharply turning
+ it 90 degrees to a different orientation. Compared the rapid changes in the
+ raw accelerometer data with the smoothed out filtered data. If the filtering
+ is too aggressive, the filter response may lag by hundreds of milliseconds.
+
+2. Ensure that there is an appropriate gap between adjacent orientation angles
+ for hysteresis. Try holding the device in one orientation and slowly turning
+ it 90 degrees. Note that the confidence intervals will all drop to 0 at some
+ point in between the two orientations; that is the gap. The gap should be
+ observed between all adjacent pairs of orientations when turning the device
+ in either direction.
+
+ Next try holding the device in one orientation and rapidly turning it end
+ over end to a midpoint about 45 degrees between two opposing orientations.
+ There should be no gap observed initially. The algorithm should pick one
+ of the orientations and settle into it (since it is obviously quite
+ different from the original orientation of the device). However, once it
+ settles, the confidence values should start trending to 0 again because
+ the measured orientation angle is now within the gap between the new
+ orientation and the adjacent orientation.
+
+ In other words, the hysteresis gap applies only when the measured orientation
+ angle (say, 45 degrees) is between the current orientation's ideal angle
+ (say, 0 degrees) and an adjacent orientation's ideal angle (say, 90 degrees).
+
+3. Accelerometer jitter. The accelerometer latency graph displays the interval
+ between sensor events as reported by the SensorEvent.timestamp field. It
+ should be a fairly constant 60ms. If the latency jumps around wildly or
+ greatly exceeds 60ms then there is a problem with the accelerometer or the
+ sensor manager.
+
+4. The orientation angle is not measured when the tilt is too close to 90 or -90
+ degrees (refer to MAX_TILT constant). Consequently, you should expect there
+ to be no data. Likewise, all dependent calculations are suppressed in this case
+ so there will be no orientation proposal either.
+
+5. Each orientation has its own bound on allowable tilt angles. It's a good idea to
+ verify that these limits are being enforced by gradually varying the tilt of
+ the device until it is inside/outside the limit for each orientation.
+
+6. Orientation changes should be significantly harder when the device is held
+ overhead. People reading on tablets in bed often have their head turned
+ a little to the side, or they hold the device loosely so its orientation
+ can be a bit unusual. The tilt is a good indicator of whether the device is
+ overhead.
diff --git a/tools/orientationplot/orientationplot.py b/tools/orientationplot/orientationplot.py
new file mode 100755
index 0000000..6fc3922
--- /dev/null
+++ b/tools/orientationplot/orientationplot.py
@@ -0,0 +1,457 @@
+#!/usr/bin/env python2.6
+#
+# Copyright (C) 2011 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.
+#
+
+#
+# Plots debug log output from WindowOrientationListener.
+# See README.txt for details.
+#
+
+import numpy as np
+import matplotlib.pyplot as plot
+import subprocess
+import re
+import fcntl
+import os
+import errno
+import bisect
+from datetime import datetime, timedelta
+
+# Parameters.
+timespan = 15 # seconds total span shown
+scrolljump = 5 # seconds jump when scrolling
+timeticks = 1 # seconds between each time tick
+
+# Non-blocking stream wrapper.
+class NonBlockingStream:
+ def __init__(self, stream):
+ fcntl.fcntl(stream, fcntl.F_SETFL, os.O_NONBLOCK)
+ self.stream = stream
+ self.buffer = ''
+ self.pos = 0
+
+ def readline(self):
+ while True:
+ index = self.buffer.find('\n', self.pos)
+ if index != -1:
+ result = self.buffer[self.pos:index]
+ self.pos = index + 1
+ return result
+
+ self.buffer = self.buffer[self.pos:]
+ self.pos = 0
+ try:
+ chunk = os.read(self.stream.fileno(), 4096)
+ except OSError, e:
+ if e.errno == errno.EAGAIN:
+ return None
+ raise e
+ if len(chunk) == 0:
+ if len(self.buffer) == 0:
+ raise(EOFError)
+ else:
+ result = self.buffer
+ self.buffer = ''
+ self.pos = 0
+ return result
+ self.buffer += chunk
+
+# Plotter
+class Plotter:
+ def __init__(self, adbout):
+ self.adbout = adbout
+
+ self.fig = plot.figure(1)
+ self.fig.suptitle('Window Orientation Listener', fontsize=12)
+ self.fig.set_dpi(96)
+ self.fig.set_size_inches(16, 12, forward=True)
+
+ self.raw_acceleration_x = self._make_timeseries()
+ self.raw_acceleration_y = self._make_timeseries()
+ self.raw_acceleration_z = self._make_timeseries()
+ self.raw_acceleration_magnitude = self._make_timeseries()
+ self.raw_acceleration_axes = self._add_timeseries_axes(
+ 1, 'Raw Acceleration', 'm/s^2', [-20, 20],
+ yticks=range(-15, 16, 5))
+ self.raw_acceleration_line_x = self._add_timeseries_line(
+ self.raw_acceleration_axes, 'x', 'red')
+ self.raw_acceleration_line_y = self._add_timeseries_line(
+ self.raw_acceleration_axes, 'y', 'green')
+ self.raw_acceleration_line_z = self._add_timeseries_line(
+ self.raw_acceleration_axes, 'z', 'blue')
+ self.raw_acceleration_line_magnitude = self._add_timeseries_line(
+ self.raw_acceleration_axes, 'magnitude', 'orange', linewidth=2)
+ self._add_timeseries_legend(self.raw_acceleration_axes)
+
+ shared_axis = self.raw_acceleration_axes
+
+ self.filtered_acceleration_x = self._make_timeseries()
+ self.filtered_acceleration_y = self._make_timeseries()
+ self.filtered_acceleration_z = self._make_timeseries()
+ self.filtered_acceleration_magnitude = self._make_timeseries()
+ self.filtered_acceleration_axes = self._add_timeseries_axes(
+ 2, 'Filtered Acceleration', 'm/s^2', [-20, 20],
+ sharex=shared_axis,
+ yticks=range(-15, 16, 5))
+ self.filtered_acceleration_line_x = self._add_timeseries_line(
+ self.filtered_acceleration_axes, 'x', 'red')
+ self.filtered_acceleration_line_y = self._add_timeseries_line(
+ self.filtered_acceleration_axes, 'y', 'green')
+ self.filtered_acceleration_line_z = self._add_timeseries_line(
+ self.filtered_acceleration_axes, 'z', 'blue')
+ self.filtered_acceleration_line_magnitude = self._add_timeseries_line(
+ self.filtered_acceleration_axes, 'magnitude', 'orange', linewidth=2)
+ self._add_timeseries_legend(self.filtered_acceleration_axes)
+
+ self.tilt_angle = self._make_timeseries()
+ self.tilt_angle_axes = self._add_timeseries_axes(
+ 3, 'Tilt Angle', 'degrees', [-105, 105],
+ sharex=shared_axis,
+ yticks=range(-90, 91, 30))
+ self.tilt_angle_line = self._add_timeseries_line(
+ self.tilt_angle_axes, 'tilt', 'black')
+ self._add_timeseries_legend(self.tilt_angle_axes)
+
+ self.orientation_angle = self._make_timeseries()
+ self.orientation_angle_axes = self._add_timeseries_axes(
+ 4, 'Orientation Angle', 'degrees', [-25, 375],
+ sharex=shared_axis,
+ yticks=range(0, 361, 45))
+ self.orientation_angle_line = self._add_timeseries_line(
+ self.orientation_angle_axes, 'orientation', 'black')
+ self._add_timeseries_legend(self.orientation_angle_axes)
+
+ self.current_rotation = self._make_timeseries()
+ self.proposed_rotation = self._make_timeseries()
+ self.predicted_rotation = self._make_timeseries()
+ self.orientation_axes = self._add_timeseries_axes(
+ 5, 'Current / Proposed Orientation', 'rotation', [-1, 4],
+ sharex=shared_axis,
+ yticks=range(0, 4))
+ self.current_rotation_line = self._add_timeseries_line(
+ self.orientation_axes, 'current', 'black', linewidth=2)
+ self.predicted_rotation_line = self._add_timeseries_line(
+ self.orientation_axes, 'predicted', 'purple', linewidth=3)
+ self.proposed_rotation_line = self._add_timeseries_line(
+ self.orientation_axes, 'proposed', 'green', linewidth=3)
+ self._add_timeseries_legend(self.orientation_axes)
+
+ self.time_until_settled = self._make_timeseries()
+ self.time_until_flat_delay_expired = self._make_timeseries()
+ self.time_until_swing_delay_expired = self._make_timeseries()
+ self.time_until_acceleration_delay_expired = self._make_timeseries()
+ self.stability_axes = self._add_timeseries_axes(
+ 6, 'Proposal Stability', 'ms', [-10, 600],
+ sharex=shared_axis,
+ yticks=range(0, 600, 100))
+ self.time_until_settled_line = self._add_timeseries_line(
+ self.stability_axes, 'time until settled', 'black', linewidth=2)
+ self.time_until_flat_delay_expired_line = self._add_timeseries_line(
+ self.stability_axes, 'time until flat delay expired', 'green')
+ self.time_until_swing_delay_expired_line = self._add_timeseries_line(
+ self.stability_axes, 'time until swing delay expired', 'blue')
+ self.time_until_acceleration_delay_expired_line = self._add_timeseries_line(
+ self.stability_axes, 'time until acceleration delay expired', 'red')
+ self._add_timeseries_legend(self.stability_axes)
+
+ self.sample_latency = self._make_timeseries()
+ self.sample_latency_axes = self._add_timeseries_axes(
+ 7, 'Accelerometer Sampling Latency', 'ms', [-10, 500],
+ sharex=shared_axis,
+ yticks=range(0, 500, 100))
+ self.sample_latency_line = self._add_timeseries_line(
+ self.sample_latency_axes, 'latency', 'black')
+ self._add_timeseries_legend(self.sample_latency_axes)
+
+ self.fig.canvas.mpl_connect('button_press_event', self._on_click)
+ self.paused = False
+
+ self.timer = self.fig.canvas.new_timer(interval=100)
+ self.timer.add_callback(lambda: self.update())
+ self.timer.start()
+
+ self.timebase = None
+ self._reset_parse_state()
+
+ # Handle a click event to pause or restart the timer.
+ def _on_click(self, ev):
+ if not self.paused:
+ self.paused = True
+ self.timer.stop()
+ else:
+ self.paused = False
+ self.timer.start()
+
+ # Initialize a time series.
+ def _make_timeseries(self):
+ return [[], []]
+
+ # Add a subplot to the figure for a time series.
+ def _add_timeseries_axes(self, index, title, ylabel, ylim, yticks, sharex=None):
+ num_graphs = 7
+ height = 0.9 / num_graphs
+ top = 0.95 - height * index
+ axes = self.fig.add_axes([0.1, top, 0.8, height],
+ xscale='linear',
+ xlim=[0, timespan],
+ ylabel=ylabel,
+ yscale='linear',
+ ylim=ylim,
+ sharex=sharex)
+ axes.text(0.02, 0.02, title, transform=axes.transAxes, fontsize=10, fontweight='bold')
+ axes.set_xlabel('time (s)', fontsize=10, fontweight='bold')
+ axes.set_ylabel(ylabel, fontsize=10, fontweight='bold')
+ axes.set_xticks(range(0, timespan + 1, timeticks))
+ axes.set_yticks(yticks)
+ axes.grid(True)
+
+ for label in axes.get_xticklabels():
+ label.set_fontsize(9)
+ for label in axes.get_yticklabels():
+ label.set_fontsize(9)
+
+ return axes
+
+ # Add a line to the axes for a time series.
+ def _add_timeseries_line(self, axes, label, color, linewidth=1):
+ return axes.plot([], label=label, color=color, linewidth=linewidth)[0]
+
+ # Add a legend to a time series.
+ def _add_timeseries_legend(self, axes):
+ axes.legend(
+ loc='upper left',
+ bbox_to_anchor=(1.01, 1),
+ borderpad=0.1,
+ borderaxespad=0.1,
+ prop={'size': 10})
+
+ # Resets the parse state.
+ def _reset_parse_state(self):
+ self.parse_raw_acceleration_x = None
+ self.parse_raw_acceleration_y = None
+ self.parse_raw_acceleration_z = None
+ self.parse_raw_acceleration_magnitude = None
+ self.parse_filtered_acceleration_x = None
+ self.parse_filtered_acceleration_y = None
+ self.parse_filtered_acceleration_z = None
+ self.parse_filtered_acceleration_magnitude = None
+ self.parse_tilt_angle = None
+ self.parse_orientation_angle = None
+ self.parse_current_rotation = None
+ self.parse_proposed_rotation = None
+ self.parse_predicted_rotation = None
+ self.parse_time_until_settled = None
+ self.parse_time_until_flat_delay_expired = None
+ self.parse_time_until_swing_delay_expired = None
+ self.parse_time_until_acceleration_delay_expired = None
+ self.parse_sample_latency = None
+
+ # Update samples.
+ def update(self):
+ timeindex = 0
+ while True:
+ try:
+ line = self.adbout.readline()
+ except EOFError:
+ plot.close()
+ return
+ if line is None:
+ break
+ print line
+
+ try:
+ timestamp = self._parse_timestamp(line)
+ except ValueError, e:
+ continue
+ if self.timebase is None:
+ self.timebase = timestamp
+ delta = timestamp - self.timebase
+ timeindex = delta.seconds + delta.microseconds * 0.000001
+
+ if line.find('Raw acceleration vector:') != -1:
+ self.parse_raw_acceleration_x = self._get_following_number(line, 'x=')
+ self.parse_raw_acceleration_y = self._get_following_number(line, 'y=')
+ self.parse_raw_acceleration_z = self._get_following_number(line, 'z=')
+ self.parse_raw_acceleration_magnitude = self._get_following_number(line, 'magnitude=')
+
+ if line.find('Filtered acceleration vector:') != -1:
+ self.parse_filtered_acceleration_x = self._get_following_number(line, 'x=')
+ self.parse_filtered_acceleration_y = self._get_following_number(line, 'y=')
+ self.parse_filtered_acceleration_z = self._get_following_number(line, 'z=')
+ self.parse_filtered_acceleration_magnitude = self._get_following_number(line, 'magnitude=')
+
+ if line.find('tiltAngle=') != -1:
+ self.parse_tilt_angle = self._get_following_number(line, 'tiltAngle=')
+
+ if line.find('orientationAngle=') != -1:
+ self.parse_orientation_angle = self._get_following_number(line, 'orientationAngle=')
+
+ if line.find('Result:') != -1:
+ self.parse_current_rotation = self._get_following_number(line, 'currentRotation=')
+ self.parse_proposed_rotation = self._get_following_number(line, 'proposedRotation=')
+ self.parse_predicted_rotation = self._get_following_number(line, 'predictedRotation=')
+ self.parse_sample_latency = self._get_following_number(line, 'timeDeltaMS=')
+ self.parse_time_until_settled = self._get_following_number(line, 'timeUntilSettledMS=')
+ self.parse_time_until_flat_delay_expired = self._get_following_number(line, 'timeUntilFlatDelayExpiredMS=')
+ self.parse_time_until_swing_delay_expired = self._get_following_number(line, 'timeUntilSwingDelayExpiredMS=')
+ self.parse_time_until_acceleration_delay_expired = self._get_following_number(line, 'timeUntilAccelerationDelayExpiredMS=')
+
+ self._append(self.raw_acceleration_x, timeindex, self.parse_raw_acceleration_x)
+ self._append(self.raw_acceleration_y, timeindex, self.parse_raw_acceleration_y)
+ self._append(self.raw_acceleration_z, timeindex, self.parse_raw_acceleration_z)
+ self._append(self.raw_acceleration_magnitude, timeindex, self.parse_raw_acceleration_magnitude)
+ self._append(self.filtered_acceleration_x, timeindex, self.parse_filtered_acceleration_x)
+ self._append(self.filtered_acceleration_y, timeindex, self.parse_filtered_acceleration_y)
+ self._append(self.filtered_acceleration_z, timeindex, self.parse_filtered_acceleration_z)
+ self._append(self.filtered_acceleration_magnitude, timeindex, self.parse_filtered_acceleration_magnitude)
+ self._append(self.tilt_angle, timeindex, self.parse_tilt_angle)
+ self._append(self.orientation_angle, timeindex, self.parse_orientation_angle)
+ self._append(self.current_rotation, timeindex, self.parse_current_rotation)
+ if self.parse_proposed_rotation >= 0:
+ self._append(self.proposed_rotation, timeindex, self.parse_proposed_rotation)
+ else:
+ self._append(self.proposed_rotation, timeindex, None)
+ if self.parse_predicted_rotation >= 0:
+ self._append(self.predicted_rotation, timeindex, self.parse_predicted_rotation)
+ else:
+ self._append(self.predicted_rotation, timeindex, None)
+ self._append(self.time_until_settled, timeindex, self.parse_time_until_settled)
+ self._append(self.time_until_flat_delay_expired, timeindex, self.parse_time_until_flat_delay_expired)
+ self._append(self.time_until_swing_delay_expired, timeindex, self.parse_time_until_swing_delay_expired)
+ self._append(self.time_until_acceleration_delay_expired, timeindex, self.parse_time_until_acceleration_delay_expired)
+ self._append(self.sample_latency, timeindex, self.parse_sample_latency)
+ self._reset_parse_state()
+
+ # Scroll the plots.
+ if timeindex > timespan:
+ bottom = int(timeindex) - timespan + scrolljump
+ self.timebase += timedelta(seconds=bottom)
+ self._scroll(self.raw_acceleration_x, bottom)
+ self._scroll(self.raw_acceleration_y, bottom)
+ self._scroll(self.raw_acceleration_z, bottom)
+ self._scroll(self.raw_acceleration_magnitude, bottom)
+ self._scroll(self.filtered_acceleration_x, bottom)
+ self._scroll(self.filtered_acceleration_y, bottom)
+ self._scroll(self.filtered_acceleration_z, bottom)
+ self._scroll(self.filtered_acceleration_magnitude, bottom)
+ self._scroll(self.tilt_angle, bottom)
+ self._scroll(self.orientation_angle, bottom)
+ self._scroll(self.current_rotation, bottom)
+ self._scroll(self.proposed_rotation, bottom)
+ self._scroll(self.predicted_rotation, bottom)
+ self._scroll(self.time_until_settled, bottom)
+ self._scroll(self.time_until_flat_delay_expired, bottom)
+ self._scroll(self.time_until_swing_delay_expired, bottom)
+ self._scroll(self.time_until_acceleration_delay_expired, bottom)
+ self._scroll(self.sample_latency, bottom)
+
+ # Redraw the plots.
+ self.raw_acceleration_line_x.set_data(self.raw_acceleration_x)
+ self.raw_acceleration_line_y.set_data(self.raw_acceleration_y)
+ self.raw_acceleration_line_z.set_data(self.raw_acceleration_z)
+ self.raw_acceleration_line_magnitude.set_data(self.raw_acceleration_magnitude)
+ self.filtered_acceleration_line_x.set_data(self.filtered_acceleration_x)
+ self.filtered_acceleration_line_y.set_data(self.filtered_acceleration_y)
+ self.filtered_acceleration_line_z.set_data(self.filtered_acceleration_z)
+ self.filtered_acceleration_line_magnitude.set_data(self.filtered_acceleration_magnitude)
+ self.tilt_angle_line.set_data(self.tilt_angle)
+ self.orientation_angle_line.set_data(self.orientation_angle)
+ self.current_rotation_line.set_data(self.current_rotation)
+ self.proposed_rotation_line.set_data(self.proposed_rotation)
+ self.predicted_rotation_line.set_data(self.predicted_rotation)
+ self.time_until_settled_line.set_data(self.time_until_settled)
+ self.time_until_flat_delay_expired_line.set_data(self.time_until_flat_delay_expired)
+ self.time_until_swing_delay_expired_line.set_data(self.time_until_swing_delay_expired)
+ self.time_until_acceleration_delay_expired_line.set_data(self.time_until_acceleration_delay_expired)
+ self.sample_latency_line.set_data(self.sample_latency)
+
+ self.fig.canvas.draw_idle()
+
+ # Scroll a time series.
+ def _scroll(self, timeseries, bottom):
+ bottom_index = bisect.bisect_left(timeseries[0], bottom)
+ del timeseries[0][:bottom_index]
+ del timeseries[1][:bottom_index]
+ for i, timeindex in enumerate(timeseries[0]):
+ timeseries[0][i] = timeindex - bottom
+
+ # Extract a word following the specified prefix.
+ def _get_following_word(self, line, prefix):
+ prefix_index = line.find(prefix)
+ if prefix_index == -1:
+ return None
+ start_index = prefix_index + len(prefix)
+ delim_index = line.find(',', start_index)
+ if delim_index == -1:
+ return line[start_index:]
+ else:
+ return line[start_index:delim_index]
+
+ # Extract a number following the specified prefix.
+ def _get_following_number(self, line, prefix):
+ word = self._get_following_word(line, prefix)
+ if word is None:
+ return None
+ return float(word)
+
+ # Extract an array of numbers following the specified prefix.
+ def _get_following_array_of_numbers(self, line, prefix):
+ prefix_index = line.find(prefix + '[')
+ if prefix_index == -1:
+ return None
+ start_index = prefix_index + len(prefix) + 1
+ delim_index = line.find(']', start_index)
+ if delim_index == -1:
+ return None
+
+ result = []
+ while start_index < delim_index:
+ comma_index = line.find(', ', start_index, delim_index)
+ if comma_index == -1:
+ result.append(float(line[start_index:delim_index]))
+ break;
+ result.append(float(line[start_index:comma_index]))
+ start_index = comma_index + 2
+ return result
+
+ # Add a value to a time series.
+ def _append(self, timeseries, timeindex, number):
+ timeseries[0].append(timeindex)
+ timeseries[1].append(number)
+
+ # Parse the logcat timestamp.
+ # Timestamp has the form '01-21 20:42:42.930'
+ def _parse_timestamp(self, line):
+ return datetime.strptime(line[0:18], '%m-%d %H:%M:%S.%f')
+
+# Notice
+print "Window Orientation Listener plotting tool"
+print "-----------------------------------------\n"
+print "Please turn on the Window Orientation Listener logging in Development Settings."
+
+# Start adb.
+print "Starting adb logcat.\n"
+
+adb = subprocess.Popen(['adb', 'logcat', '-s', '-v', 'time', 'WindowOrientationListener:V'],
+ stdout=subprocess.PIPE)
+adbout = NonBlockingStream(adb.stdout)
+
+# Prepare plotter.
+plotter = Plotter(adbout)
+plotter.update()
+
+# Main loop.
+plot.show()
diff --git a/tools/preload/20080522.compiled b/tools/preload/20080522.compiled
new file mode 100644
index 0000000..a2af422
--- /dev/null
+++ b/tools/preload/20080522.compiled
Binary files differ
diff --git a/tools/preload/20090811.compiled b/tools/preload/20090811.compiled
new file mode 100644
index 0000000..6dbeca0
--- /dev/null
+++ b/tools/preload/20090811.compiled
Binary files differ
diff --git a/tools/preload/20100223.compiled b/tools/preload/20100223.compiled
new file mode 100644
index 0000000..3056388
--- /dev/null
+++ b/tools/preload/20100223.compiled
Binary files differ
diff --git a/tools/preload/Android.mk b/tools/preload/Android.mk
new file mode 100644
index 0000000..f325870
--- /dev/null
+++ b/tools/preload/Android.mk
@@ -0,0 +1,23 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ Compile.java \
+ LoadedClass.java \
+ MemoryUsage.java \
+ Operation.java \
+ Policy.java \
+ PrintCsv.java \
+ PrintHtmlDiff.java \
+ PrintPsTree.java \
+ Proc.java \
+ Record.java \
+ Root.java \
+ WritePreloadedClassFile.java
+
+LOCAL_MODULE:= preload
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-subdir-makefiles)
diff --git a/tools/preload/Compile.java b/tools/preload/Compile.java
new file mode 100644
index 0000000..67258ef
--- /dev/null
+++ b/tools/preload/Compile.java
@@ -0,0 +1,73 @@
+/*
+ * 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.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+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 {
+ if (args.length != 2) {
+ System.err.println("Usage: Compile [log file] [output file]");
+ System.exit(0);
+ }
+
+ Root root = new Root();
+
+ List<Record> records = new ArrayList<Record>();
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(
+ new FileInputStream(args[0])));
+
+ String line;
+ int lineNumber = 0;
+ while ((line = in.readLine()) != null) {
+ lineNumber++;
+ if (line.startsWith("I/PRELOAD")) {
+ 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);
+ }
+ }
+ }
+
+ for (Record record : records) {
+ root.indexProcess(record);
+ }
+
+ for (Record record : records) {
+ root.indexClassOperation(record);
+ }
+
+ in.close();
+
+ root.toFile(args[1]);
+ }
+}
diff --git a/tools/preload/LoadedClass.java b/tools/preload/LoadedClass.java
new file mode 100644
index 0000000..86e5dfc
--- /dev/null
+++ b/tools/preload/LoadedClass.java
@@ -0,0 +1,130 @@
+/*
+ * 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.io.Serializable;
+import java.util.*;
+
+/**
+ * A loaded class.
+ */
+class LoadedClass implements Serializable, Comparable<LoadedClass> {
+
+ private static final long serialVersionUID = 0;
+
+ /** Class name. */
+ final String name;
+
+ /** Load operations. */
+ final List<Operation> loads = new ArrayList<Operation>();
+
+ /** Static initialization operations. */
+ final List<Operation> initializations = new ArrayList<Operation>();
+
+ /** Memory usage gathered by loading only this class in its own VM. */
+ MemoryUsage memoryUsage = MemoryUsage.NOT_AVAILABLE;
+
+ /**
+ * Whether or not this class was loaded in the system class loader.
+ */
+ final boolean systemClass;
+
+ /** Whether or not this class will be preloaded. */
+ boolean preloaded;
+
+ /** Constructs a new class. */
+ LoadedClass(String name, boolean systemClass) {
+ this.name = name;
+ this.systemClass = systemClass;
+ }
+
+ void measureMemoryUsage() {
+ this.memoryUsage = MemoryUsage.forClass(name);
+ }
+
+ int mlt = -1;
+
+ /** Median time to load this class. */
+ int medianLoadTimeMicros() {
+ if (mlt != -1) {
+ return mlt;
+ }
+
+ return mlt = calculateMedian(loads);
+ }
+
+ int mit = -1;
+
+ /** Median time to initialize this class. */
+ int medianInitTimeMicros() {
+ if (mit != -1) {
+ return mit;
+ }
+
+ return mit = calculateMedian(initializations);
+ }
+
+ int medianTimeMicros() {
+ return medianInitTimeMicros() + medianLoadTimeMicros();
+ }
+
+ /** Calculates the median duration for a list of operations. */
+ private static int calculateMedian(List<Operation> operations) {
+ int size = operations.size();
+ if (size == 0) {
+ return 0;
+ }
+
+ int[] times = new int[size];
+ for (int i = 0; i < size; i++) {
+ times[i] = operations.get(i).exclusiveTimeMicros();
+ }
+
+ Arrays.sort(times);
+ int middle = size / 2;
+ if (size % 2 == 1) {
+ // Odd
+ return times[middle];
+ } else {
+ // Even -- average the two.
+ return (times[middle - 1] + times[middle]) / 2;
+ }
+ }
+
+ /** Returns names of processes that loaded this class. */
+ Set<String> processNames() {
+ Set<String> names = new HashSet<String>();
+ addProcessNames(loads, names);
+ addProcessNames(initializations, names);
+ return names;
+ }
+
+ private void addProcessNames(List<Operation> ops, Set<String> names) {
+ for (Operation operation : ops) {
+ if (operation.process.fromZygote()) {
+ names.add(operation.process.name);
+ }
+ }
+ }
+
+ public int compareTo(LoadedClass o) {
+ return name.compareTo(o.name);
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/tools/preload/MemoryUsage.java b/tools/preload/MemoryUsage.java
new file mode 100644
index 0000000..d8f95f4
--- /dev/null
+++ b/tools/preload/MemoryUsage.java
@@ -0,0 +1,298 @@
+/*
+ * 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.io.Serializable;
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Memory usage information.
+ */
+class MemoryUsage implements Serializable {
+
+ private static final long serialVersionUID = 0;
+
+ static final MemoryUsage NOT_AVAILABLE = new MemoryUsage();
+
+ static int errorCount = 0;
+
+ // These values are in 1kB increments (not 4kB like you'd expect).
+ final int nativeSharedPages;
+ final int javaSharedPages;
+ final int otherSharedPages;
+ final int nativePrivatePages;
+ final int javaPrivatePages;
+ final int otherPrivatePages;
+
+ final int allocCount;
+ final int allocSize;
+ final int freedCount;
+ final int freedSize;
+ final long nativeHeapSize;
+
+ public MemoryUsage(String line) {
+ String[] parsed = line.split(",");
+
+ nativeSharedPages = Integer.parseInt(parsed[1]);
+ javaSharedPages = Integer.parseInt(parsed[2]);
+ otherSharedPages = Integer.parseInt(parsed[3]);
+ nativePrivatePages = Integer.parseInt(parsed[4]);
+ javaPrivatePages = Integer.parseInt(parsed[5]);
+ otherPrivatePages = Integer.parseInt(parsed[6]);
+ allocCount = Integer.parseInt(parsed[7]);
+ allocSize = Integer.parseInt(parsed[8]);
+ freedCount = Integer.parseInt(parsed[9]);
+ freedSize = Integer.parseInt(parsed[10]);
+ nativeHeapSize = Long.parseLong(parsed[11]);
+ }
+
+ MemoryUsage() {
+ nativeSharedPages = -1;
+ javaSharedPages = -1;
+ otherSharedPages = -1;
+ nativePrivatePages = -1;
+ javaPrivatePages = -1;
+ otherPrivatePages = -1;
+
+ allocCount = -1;
+ allocSize = -1;
+ freedCount = -1;
+ freedSize = -1;
+ nativeHeapSize = -1;
+ }
+
+ MemoryUsage(int nativeSharedPages,
+ int javaSharedPages,
+ int otherSharedPages,
+ int nativePrivatePages,
+ int javaPrivatePages,
+ int otherPrivatePages,
+ int allocCount,
+ int allocSize,
+ int freedCount,
+ int freedSize,
+ long nativeHeapSize) {
+ this.nativeSharedPages = nativeSharedPages;
+ this.javaSharedPages = javaSharedPages;
+ this.otherSharedPages = otherSharedPages;
+ this.nativePrivatePages = nativePrivatePages;
+ this.javaPrivatePages = javaPrivatePages;
+ this.otherPrivatePages = otherPrivatePages;
+ this.allocCount = allocCount;
+ this.allocSize = allocSize;
+ this.freedCount = freedCount;
+ this.freedSize = freedSize;
+ this.nativeHeapSize = nativeHeapSize;
+ }
+
+ MemoryUsage subtract(MemoryUsage baseline) {
+ return new MemoryUsage(
+ nativeSharedPages - baseline.nativeSharedPages,
+ javaSharedPages - baseline.javaSharedPages,
+ otherSharedPages - baseline.otherSharedPages,
+ nativePrivatePages - baseline.nativePrivatePages,
+ javaPrivatePages - baseline.javaPrivatePages,
+ otherPrivatePages - baseline.otherPrivatePages,
+ allocCount - baseline.allocCount,
+ allocSize - baseline.allocSize,
+ freedCount - baseline.freedCount,
+ freedSize - baseline.freedSize,
+ nativeHeapSize - baseline.nativeHeapSize);
+ }
+
+ int javaHeapSize() {
+ return allocSize - freedSize;
+ }
+
+ int totalHeap() {
+ return javaHeapSize() + (int) nativeHeapSize;
+ }
+
+ int javaPagesInK() {
+ return javaSharedPages + javaPrivatePages;
+ }
+
+ int nativePagesInK() {
+ return nativeSharedPages + nativePrivatePages;
+ }
+ int otherPagesInK() {
+ return otherSharedPages + otherPrivatePages;
+ }
+
+ int totalPages() {
+ return javaSharedPages + javaPrivatePages + nativeSharedPages +
+ nativePrivatePages + otherSharedPages + otherPrivatePages;
+ }
+
+ /**
+ * Was this information available?
+ */
+ boolean isAvailable() {
+ return nativeSharedPages != -1;
+ }
+
+ /**
+ * Measures baseline memory usage.
+ */
+ static MemoryUsage baseline() {
+ return forClass(null);
+ }
+
+ private static final String CLASS_PATH = "-Xbootclasspath"
+ + ":/system/framework/core.jar"
+ + ":/system/framework/ext.jar"
+ + ":/system/framework/framework.jar"
+ + ":/system/framework/framework-tests.jar"
+ + ":/system/framework/services.jar"
+ + ":/system/framework/loadclass.jar";
+
+ private static final String[] GET_DIRTY_PAGES = {
+ "adb", "shell", "dalvikvm", CLASS_PATH, "LoadClass" };
+
+ /**
+ * Measures memory usage for the given class.
+ */
+ static MemoryUsage forClass(String className) {
+ MeasureWithTimeout measurer = new MeasureWithTimeout(className);
+
+ new Thread(measurer).start();
+
+ synchronized (measurer) {
+ if (measurer.memoryUsage == null) {
+ // Wait up to 10s.
+ try {
+ measurer.wait(30000);
+ } catch (InterruptedException e) {
+ System.err.println("Interrupted waiting for measurement.");
+ e.printStackTrace();
+ return NOT_AVAILABLE;
+ }
+
+ // If it's still null.
+ if (measurer.memoryUsage == null) {
+ System.err.println("Timed out while measuring "
+ + className + ".");
+ return NOT_AVAILABLE;
+ }
+ }
+
+ System.err.println("Got memory usage for " + className + ".");
+ return measurer.memoryUsage;
+ }
+ }
+
+ static class MeasureWithTimeout implements Runnable {
+
+ final String className;
+ MemoryUsage memoryUsage = null;
+
+ MeasureWithTimeout(String className) {
+ this.className = className;
+ }
+
+ public void run() {
+ MemoryUsage measured = measure();
+
+ synchronized (this) {
+ memoryUsage = measured;
+ notifyAll();
+ }
+ }
+
+ private MemoryUsage measure() {
+ String[] commands = GET_DIRTY_PAGES;
+ if (className != null) {
+ List<String> commandList = new ArrayList<String>(
+ GET_DIRTY_PAGES.length + 1);
+ commandList.addAll(Arrays.asList(commands));
+ commandList.add(className);
+ commands = commandList.toArray(new String[commandList.size()]);
+ }
+
+ try {
+ final Process process = Runtime.getRuntime().exec(commands);
+
+ final InputStream err = process.getErrorStream();
+
+ // Send error output to stderr.
+ Thread errThread = new Thread() {
+ @Override
+ public void run() {
+ copy(err, System.err);
+ }
+ };
+ errThread.setDaemon(true);
+ errThread.start();
+
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(process.getInputStream()));
+ String line = in.readLine();
+ if (line == null || !line.startsWith("DECAFBAD,")) {
+ System.err.println("Got bad response for " + className
+ + ": " + line + "; command was " + Arrays.toString(commands));
+ errorCount += 1;
+ return NOT_AVAILABLE;
+ }
+
+ in.close();
+ err.close();
+ process.destroy();
+
+ return new MemoryUsage(line);
+ } catch (IOException e) {
+ System.err.println("Error getting stats for "
+ + className + ".");
+ e.printStackTrace();
+ return NOT_AVAILABLE;
+ }
+ }
+
+ }
+
+ /**
+ * Copies from one stream to another.
+ */
+ private static void copy(InputStream in, OutputStream out) {
+ byte[] buffer = new byte[1024];
+ int read;
+ try {
+ while ((read = in.read(buffer)) > -1) {
+ out.write(buffer, 0, read);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Measures memory usage information and stores it in the model. */
+ public static void main(String[] args) throws IOException,
+ ClassNotFoundException {
+ Root root = Root.fromFile(args[0]);
+ root.baseline = baseline();
+ for (LoadedClass loadedClass : root.loadedClasses.values()) {
+ if (loadedClass.systemClass) {
+ loadedClass.measureMemoryUsage();
+ }
+ }
+ root.toFile(args[0]);
+ }
+}
diff --git a/tools/preload/Operation.java b/tools/preload/Operation.java
new file mode 100644
index 0000000..4f1938e
--- /dev/null
+++ b/tools/preload/Operation.java
@@ -0,0 +1,136 @@
+/*
+ * 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.List;
+import java.util.ArrayList;
+import java.io.Serializable;
+
+/**
+ * An operation with a duration. Could represent a class load or initialization.
+ */
+class Operation implements Serializable {
+
+ private static final long serialVersionUID = 0;
+
+ /**
+ * Type of operation.
+ */
+ enum Type {
+ LOAD, INIT
+ }
+
+ /** Process this operation occurred in. */
+ final Proc process;
+
+ /** Start time for this operation. */
+ final long startTimeNanos;
+
+ /** Index of this operation relative to its process. */
+ final int index;
+
+ /** Type of operation. */
+ final Type type;
+
+ /** End time for this operation. */
+ long endTimeNanos = -1;
+
+ /** The class that this operation loaded or initialized. */
+ final LoadedClass loadedClass;
+
+ /** Other operations that occurred during this one. */
+ final List<Operation> subops = new ArrayList<Operation>();
+
+ /** Constructs a new operation. */
+ Operation(Proc process, LoadedClass loadedClass, long startTimeNanos,
+ int index, Type type) {
+ this.process = process;
+ this.loadedClass = loadedClass;
+ this.startTimeNanos = startTimeNanos;
+ this.index = index;
+ this.type = type;
+ }
+
+ /**
+ * Returns how long this class initialization and all the nested class
+ * initializations took.
+ */
+ private long inclusiveTimeNanos() {
+ if (endTimeNanos == -1) {
+ throw new IllegalStateException("End time hasn't been set yet: "
+ + loadedClass.name);
+ }
+
+ return endTimeNanos - startTimeNanos;
+ }
+
+ /**
+ * Returns how long this class initialization took.
+ */
+ int exclusiveTimeMicros() {
+ long exclusive = inclusiveTimeNanos();
+
+ for (Operation child : subops) {
+ exclusive -= child.inclusiveTimeNanos();
+ }
+
+ if (exclusive < 0) {
+ throw new AssertionError(loadedClass.name);
+ }
+
+ return nanosToMicros(exclusive);
+ }
+
+ /** Gets the median time that this operation took across all processes. */
+ int medianExclusiveTimeMicros() {
+ switch (type) {
+ case LOAD: return loadedClass.medianLoadTimeMicros();
+ case INIT: return loadedClass.medianInitTimeMicros();
+ default: throw new AssertionError();
+ }
+ }
+
+ /**
+ * Converts nanoseconds to microseconds.
+ *
+ * @throws RuntimeException if overflow occurs
+ */
+ private static int nanosToMicros(long nanos) {
+ long micros = nanos / 1000;
+ int microsInt = (int) micros;
+ if (microsInt != micros) {
+ throw new RuntimeException("Integer overflow: " + nanos);
+ }
+ 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..af46820
--- /dev/null
+++ b/tools/preload/Policy.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+/**
+ * Policy that governs which classes are preloaded.
+ */
+public class Policy {
+
+ /**
+ * No constructor - use static methods only
+ */
+ private Policy() {}
+
+ /**
+ * This location (in the build system) of the preloaded-classes file.
+ */
+ static final String PRELOADED_CLASS_FILE
+ = "frameworks/base/preloaded-classes";
+
+ /**
+ * Long running services. These are restricted in their contribution to the
+ * preloader because their launch time is less critical.
+ */
+ // TODO: Generate this automatically from package manager.
+ private static final Set<String> SERVICES = new HashSet<String>(Arrays.asList(
+ "system_server",
+ "com.google.process.content",
+ "android.process.media",
+ "com.android.bluetooth",
+ "com.android.calendar",
+ "com.android.inputmethod.latin",
+ "com.android.phone",
+ "com.google.android.apps.maps.FriendService", // pre froyo
+ "com.google.android.apps.maps:FriendService", // froyo
+ "com.google.android.apps.maps.LocationFriendService",
+ "com.google.android.deskclock",
+ "com.google.process.gapps",
+ "android.tts"
+ ));
+
+ /**
+ * 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",
+ "android.webkit.WebViewClassic$1",
+ "java.lang.ProcessManager"
+ ));
+
+ /**
+ * Returns true 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 be preloaded. */
+ public static boolean isPreloadable(LoadedClass clazz) {
+ return clazz.systemClass && !EXCLUDED_CLASSES.contains(clazz.name)
+ && !clazz.name.endsWith("$NoPreloadHolder");
+ }
+}
diff --git a/tools/preload/PrintCsv.java b/tools/preload/PrintCsv.java
new file mode 100644
index 0000000..1820830
--- /dev/null
+++ b/tools/preload/PrintCsv.java
@@ -0,0 +1,127 @@
+/*
+ * 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.io.IOException;
+import java.io.FileInputStream;
+import java.io.ObjectInputStream;
+import java.io.BufferedInputStream;
+import java.io.Writer;
+import java.io.PrintStream;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.TreeSet;
+import java.util.Iterator;
+
+/**
+ * Prints raw information in CSV format.
+ */
+public class PrintCsv {
+
+ public static void main(String[] args)
+ throws IOException, ClassNotFoundException {
+ if (args.length != 1) {
+ System.err.println("Usage: PrintCsv [compiled log file]");
+ System.exit(0);
+ }
+
+ Root root = Root.fromFile(args[0]);
+
+ printHeaders(System.out);
+
+ MemoryUsage baseline = MemoryUsage.baseline();
+
+ for (LoadedClass loadedClass : root.loadedClasses.values()) {
+ if (!loadedClass.systemClass) {
+ continue;
+ }
+
+ printRow(System.out, baseline, loadedClass);
+ }
+ }
+
+ static void printHeaders(PrintStream out) {
+ out.println("Name"
+ + ",Preloaded"
+ + ",Median Load Time (us)"
+ + ",Median Init Time (us)"
+ + ",Process Names"
+ + ",Load Count"
+ + ",Init Count"
+ + ",Managed Heap (B)"
+ + ",Native Heap (B)"
+ + ",Managed Pages (kB)"
+ + ",Native Pages (kB)"
+ + ",Other Pages (kB)");
+ }
+
+ static void printRow(PrintStream out, MemoryUsage baseline,
+ LoadedClass loadedClass) {
+ out.print(loadedClass.name);
+ out.print(',');
+ out.print(loadedClass.preloaded);
+ out.print(',');
+ out.print(loadedClass.medianLoadTimeMicros());
+ out.print(',');
+ out.print(loadedClass.medianInitTimeMicros());
+ out.print(',');
+ out.print('"');
+
+ Set<String> procNames = new TreeSet<String>();
+ for (Operation op : loadedClass.loads)
+ procNames.add(op.process.name);
+ for (Operation op : loadedClass.initializations)
+ procNames.add(op.process.name);
+
+ if (procNames.size() <= 3) {
+ for (String name : procNames) {
+ out.print(name + "\n");
+ }
+ } else {
+ Iterator<String> i = procNames.iterator();
+ out.print(i.next() + "\n");
+ out.print(i.next() + "\n");
+ out.print("...and " + (procNames.size() - 2)
+ + " others.");
+ }
+
+ out.print('"');
+ out.print(',');
+ out.print(loadedClass.loads.size());
+ out.print(',');
+ out.print(loadedClass.initializations.size());
+
+ if (loadedClass.memoryUsage.isAvailable()) {
+ MemoryUsage subtracted
+ = loadedClass.memoryUsage.subtract(baseline);
+
+ out.print(',');
+ out.print(subtracted.javaHeapSize());
+ out.print(',');
+ out.print(subtracted.nativeHeapSize);
+ out.print(',');
+ out.print(subtracted.javaPagesInK());
+ out.print(',');
+ out.print(subtracted.nativePagesInK());
+ out.print(',');
+ out.print(subtracted.otherPagesInK());
+
+ } else {
+ out.print(",n/a,n/a,n/a,n/a,n/a");
+ }
+
+ out.println();
+ }
+}
diff --git a/tools/preload/PrintHtmlDiff.java b/tools/preload/PrintHtmlDiff.java
new file mode 100644
index 0000000..b101c85
--- /dev/null
+++ b/tools/preload/PrintHtmlDiff.java
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.FileReader;
+import java.io.BufferedReader;
+import java.io.PrintStream;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * Prints HTML containing removed and added files.
+ */
+public class PrintHtmlDiff {
+
+ private static final String OLD_PRELOADED_CLASSES
+ = "old-preloaded-classes";
+
+ public static void main(String[] args) throws IOException,
+ ClassNotFoundException {
+ Root root = Root.fromFile(args[0]);
+
+ BufferedReader oldClasses = new BufferedReader(
+ new FileReader(OLD_PRELOADED_CLASSES));
+
+ // Classes loaded implicitly by the zygote.
+ Set<LoadedClass> zygote = new HashSet<LoadedClass>();
+ for (Proc proc : root.processes.values()) {
+ if (proc.name.equals("zygote")) {
+ for (Operation op : proc.operations) {
+ zygote.add(op.loadedClass);
+ }
+ break;
+ }
+ }
+
+ Set<LoadedClass> removed = new TreeSet<LoadedClass>();
+ Set<LoadedClass> added = new TreeSet<LoadedClass>();
+
+ for (LoadedClass loadedClass : root.loadedClasses.values()) {
+ if (loadedClass.preloaded && !zygote.contains(loadedClass)) {
+ added.add(loadedClass);
+ }
+ }
+
+ String line;
+ while ((line = oldClasses.readLine()) != null) {
+ line = line.trim();
+ LoadedClass clazz = root.loadedClasses.get(line);
+ if (clazz != null) {
+ added.remove(clazz);
+ if (!clazz.preloaded) removed.add(clazz);
+ }
+ }
+
+ PrintStream out = System.out;
+
+ out.println("<html><body>");
+ out.println("<style>");
+ out.println("a, th, td, h2 { font-family: arial }");
+ out.println("th, td { font-size: small }");
+ out.println("</style>");
+ out.println("<script src=\"sorttable.js\"></script>");
+ out.println("<p><a href=\"#removed\">Removed</a>");
+ out.println("<a name=\"added\"/><h2>Added</h2>");
+ printTable(out, root.baseline, added);
+ out.println("<a name=\"removed\"/><h2>Removed</h2>");
+ printTable(out, root.baseline, removed);
+ out.println("</body></html>");
+ }
+
+ static void printTable(PrintStream out, MemoryUsage baseline,
+ Iterable<LoadedClass> classes) {
+ out.println("<table border=\"1\" cellpadding=\"5\""
+ + " class=\"sortable\">");
+
+ out.println("<thead><tr>");
+ out.println("<th>Name</th>");
+ out.println("<th>Load Time (us)</th>");
+ out.println("<th>Loaded By</th>");
+ out.println("<th>Heap (B)</th>");
+ out.println("<th>Pages</th>");
+ out.println("</tr></thead>");
+
+ for (LoadedClass clazz : classes) {
+ out.println("<tr>");
+ out.println("<td>" + clazz.name + "</td>");
+ out.println("<td>" + clazz.medianTimeMicros() + "</td>");
+
+ out.println("<td>");
+ Set<String> procNames = new TreeSet<String>();
+ for (Operation op : clazz.loads) procNames.add(op.process.name);
+ for (Operation op : clazz.initializations) {
+ procNames.add(op.process.name);
+ }
+ if (procNames.size() <= 3) {
+ for (String name : procNames) {
+ out.print(name + "<br/>");
+ }
+ } else {
+ Iterator<String> i = procNames.iterator();
+ out.print(i.next() + "<br/>");
+ out.print(i.next() + "<br/>");
+ out.print("...and " + (procNames.size() - 2)
+ + " others.");
+ }
+ out.println("</td>");
+
+ if (clazz.memoryUsage.isAvailable()) {
+ MemoryUsage subtracted
+ = clazz.memoryUsage.subtract(baseline);
+
+ out.println("<td>" + (subtracted.javaHeapSize()
+ + subtracted.nativeHeapSize) + "</td>");
+ out.println("<td>" + subtracted.totalPages() + "</td>");
+ } else {
+ for (int i = 0; i < 2; i++) {
+ out.println("<td>n/a</td>");
+ }
+ }
+
+ out.println("</tr>");
+ }
+
+ out.println("</table>");
+ }
+}
diff --git a/tools/preload/PrintPsTree.java b/tools/preload/PrintPsTree.java
new file mode 100644
index 0000000..22701fa
--- /dev/null
+++ b/tools/preload/PrintPsTree.java
@@ -0,0 +1,46 @@
+/*
+ * 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.io.IOException;
+import java.io.FileInputStream;
+import java.io.ObjectInputStream;
+import java.io.BufferedInputStream;
+
+/**
+ * Prints raw information in CSV format.
+ */
+public class PrintPsTree {
+
+ public static void main(String[] args)
+ throws IOException, ClassNotFoundException {
+ if (args.length != 1) {
+ System.err.println("Usage: PrintCsv [compiled log file]");
+ System.exit(0);
+ }
+
+ FileInputStream fin = new FileInputStream(args[0]);
+ ObjectInputStream oin = new ObjectInputStream(
+ new BufferedInputStream(fin));
+
+ Root root = (Root) oin.readObject();
+
+ for (Proc proc : root.processes.values()) {
+ if (proc.parent == null) {
+ proc.print();
+ }
+ }
+ }
+}
diff --git a/tools/preload/Proc.java b/tools/preload/Proc.java
new file mode 100644
index 0000000..2105021
--- /dev/null
+++ b/tools/preload/Proc.java
@@ -0,0 +1,168 @@
+/*
+ * 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.List;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.HashMap;
+import java.io.Serializable;
+
+/**
+ * A Dalvik process.
+ */
+class Proc implements Serializable {
+
+ private static final long serialVersionUID = 0;
+
+ /** Parent process. */
+ final Proc parent;
+
+ /** Process ID. */
+ final int id;
+
+ /**
+ * Name of this process. We may not have the correct name at first, i.e.
+ * some classes could have been loaded before the process name was set.
+ */
+ String name;
+
+ /** Child processes. */
+ final List<Proc> children = new ArrayList<Proc>();
+
+ /** Maps thread ID to operation stack. */
+ transient final Map<Integer, LinkedList<Operation>> stacks
+ = new HashMap<Integer, LinkedList<Operation>>();
+
+ /** Number of operations. */
+ int operationCount;
+
+ /** Sequential list of operations that happened in this process. */
+ final List<Operation> operations = new ArrayList<Operation>();
+
+ /** List of past process names. */
+ final List<String> nameHistory = new ArrayList<String>();
+
+ /** Constructs a new process. */
+ Proc(Proc parent, int id) {
+ this.parent = parent;
+ this.id = id;
+ }
+
+ /** Sets name of this process. */
+ void setName(String name) {
+ if (!name.equals(this.name)) {
+ if (this.name != null) {
+ nameHistory.add(this.name);
+ }
+ this.name = name;
+ }
+ }
+
+ /**
+ * Returns true if this process comes from the zygote.
+ */
+ public boolean fromZygote() {
+ return parent != null && parent.name.equals("zygote")
+ && !name.equals("com.android.development");
+ }
+
+ /**
+ * Starts an operation.
+ *
+ * @param threadId thread the operation started in
+ * @param loadedClass class operation happened to
+ * @param time the operation started
+ */
+ void startOperation(int threadId, LoadedClass loadedClass, long time,
+ Operation.Type type) {
+ Operation o = new Operation(
+ this, loadedClass, time, operationCount++, type);
+ operations.add(o);
+
+ LinkedList<Operation> stack = stacks.get(threadId);
+ if (stack == null) {
+ stack = new LinkedList<Operation>();
+ stacks.put(threadId, stack);
+ }
+
+ if (!stack.isEmpty()) {
+ stack.getLast().subops.add(o);
+ }
+
+ stack.add(o);
+ }
+
+ /**
+ * Ends an operation.
+ *
+ * @param threadId thread the operation ended in
+ * @param loadedClass class operation happened to
+ * @param time the operation ended
+ */
+ Operation endOperation(int threadId, String className,
+ LoadedClass loadedClass, long time) {
+ LinkedList<Operation> stack = stacks.get(threadId);
+
+ if (stack == null || stack.isEmpty()) {
+ didNotStart(className);
+ return null;
+ }
+
+ Operation o = stack.getLast();
+ if (loadedClass != o.loadedClass) {
+ didNotStart(className);
+ return null;
+ }
+
+ stack.removeLast();
+
+ o.endTimeNanos = time;
+ return o;
+ }
+
+ /**
+ * Prints an error indicating that we saw the end of an operation but not
+ * the start. A bug in the logging framework which results in dropped logs
+ * causes this.
+ */
+ private static void didNotStart(String name) {
+ System.err.println("Warning: An operation ended on " + name
+ + " but it never started!");
+ }
+
+ /**
+ * Prints this process tree to stdout.
+ */
+ void print() {
+ print("");
+ }
+
+ /**
+ * Prints a child proc to standard out.
+ */
+ private void print(String prefix) {
+ System.out.println(prefix + "id=" + id + ", name=" + name);
+ for (Proc child : children) {
+ child.print(prefix + " ");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+}
diff --git a/tools/preload/Record.java b/tools/preload/Record.java
new file mode 100644
index 0000000..d0a2af4
--- /dev/null
+++ b/tools/preload/Record.java
@@ -0,0 +1,182 @@
+/*
+ * 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.
+ */
+
+/**
+ * One line from the loaded-classes file.
+ */
+class Record {
+
+ /**
+ * The delimiter character we use, {@code :}, conflicts with some other
+ * names. In that case, manually replace the delimiter with something else.
+ */
+ private static final String[] REPLACE_CLASSES = {
+ "com.google.android.apps.maps:FriendService",
+ "com.google.android.apps.maps\\u003AFriendService",
+ "com.google.android.apps.maps:driveabout",
+ "com.google.android.apps.maps\\u003Adriveabout",
+ "com.google.android.apps.maps:GoogleLocationService",
+ "com.google.android.apps.maps\\u003AGoogleLocationService",
+ "com.google.android.apps.maps:LocationFriendService",
+ "com.google.android.apps.maps\\u003ALocationFriendService",
+ "com.google.android.apps.maps:MapsBackgroundService",
+ "com.google.android.apps.maps\\u003AMapsBackgroundService",
+ "com.google.android.apps.maps:NetworkLocationService",
+ "com.google.android.apps.maps\\u003ANetworkLocationService",
+ "com.android.chrome:sandboxed_process",
+ "com.android.chrome\\u003Asandboxed_process",
+ "com.android.fakeoemfeatures:background",
+ "com.android.fakeoemfeatures\\u003Abackground",
+ "com.android.fakeoemfeatures:core",
+ "com.android.fakeoemfeatures\\u003Acore",
+ "com.android.launcher:wallpaper_chooser",
+ "com.android.launcher\\u003Awallpaper_chooser",
+ "com.android.nfc:handover",
+ "com.android.nfc\\u003Ahandover",
+ "com.google.android.music:main",
+ "com.google.android.music\\u003Amain",
+ "com.google.android.music:ui",
+ "com.google.android.music\\u003Aui",
+ "com.google.android.setupwarlock:broker",
+ "com.google.android.setupwarlock\\u003Abroker",
+ "mobi.mgeek.TunnyBrowser:DolphinNotification",
+ "mobi.mgeek.TunnyBrowser\\u003ADolphinNotification",
+ "com.qo.android.sp.oem:Quickword",
+ "com.qo.android.sp.oem\\u003AQuickword",
+ "android:ui",
+ "android\\u003Aui",
+ "system:ui",
+ "system\\u003Aui",
+ };
+
+ enum Type {
+ /** Start of initialization. */
+ START_LOAD,
+
+ /** End of initialization. */
+ END_LOAD,
+
+ /** Start of initialization. */
+ START_INIT,
+
+ /** End of initialization. */
+ END_INIT
+ }
+
+ /** Parent process ID. */
+ final int ppid;
+
+ /** Process ID. */
+ final int pid;
+
+ /** Thread ID. */
+ final int tid;
+
+ /** Process name. */
+ final String processName;
+
+ /** Class loader pointer. */
+ final int classLoader;
+
+ /** Type of record. */
+ final Type type;
+
+ /** Name of loaded class. */
+ final String className;
+
+ /** Record time (ns). */
+ final long time;
+
+ /** Source file line# */
+ int sourceLineNumber;
+
+ /**
+ * Parses a line from the loaded-classes file.
+ */
+ Record(String line, int lineNum) {
+ char typeChar = line.charAt(0);
+ switch (typeChar) {
+ case '>': type = Type.START_LOAD; break;
+ case '<': type = Type.END_LOAD; break;
+ case '+': type = Type.START_INIT; break;
+ case '-': type = Type.END_INIT; break;
+ default: throw new AssertionError("Bad line: " + line);
+ }
+
+ sourceLineNumber = lineNum;
+
+ for (int i = 0; i < REPLACE_CLASSES.length; i+= 2) {
+ line = line.replace(REPLACE_CLASSES[i], REPLACE_CLASSES[i+1]);
+ }
+
+ line = line.substring(1);
+ String[] parts = line.split(":");
+
+ ppid = Integer.parseInt(parts[0]);
+ pid = Integer.parseInt(parts[1]);
+ tid = Integer.parseInt(parts[2]);
+
+ processName = decode(parts[3]).intern();
+
+ classLoader = Integer.parseInt(parts[4]);
+ 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.
+ */
+ 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 + " in line " + sourceLineNumber);
+ }
+
+ typeName = typeName.substring(1, typeName.length() - 1);
+ return typeName.replace("/", ".");
+ }
+}
diff --git a/tools/preload/Root.java b/tools/preload/Root.java
new file mode 100644
index 0000000..0bc29bf
--- /dev/null
+++ b/tools/preload/Root.java
@@ -0,0 +1,163 @@
+/*
+ * 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.io.Serializable;
+import java.io.IOException;
+import java.io.Writer;
+import java.io.BufferedWriter;
+import java.io.OutputStreamWriter;
+import java.io.FileOutputStream;
+import java.io.FileInputStream;
+import java.io.ObjectInputStream;
+import java.io.BufferedInputStream;
+import java.io.ObjectOutputStream;
+import java.io.BufferedOutputStream;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.Arrays;
+import java.nio.charset.Charset;
+
+/**
+ * Root of our data model.
+ */
+public class Root implements Serializable {
+
+ private static final long serialVersionUID = 0;
+
+ /** pid -> Proc */
+ final Map<Integer, Proc> processes = new HashMap<Integer, Proc>();
+
+ /** Class name -> LoadedClass */
+ final Map<String, LoadedClass> loadedClasses
+ = new HashMap<String, LoadedClass>();
+
+ MemoryUsage baseline = MemoryUsage.baseline();
+
+ /**
+ * Records class loads and initializations.
+ */
+ void indexClassOperation(Record record) {
+ Proc process = processes.get(record.pid);
+
+ // Ignore dexopt output. It loads applications classes through the
+ // system class loader and messes us up.
+ if (record.processName.equals("dexopt")) {
+ return;
+ }
+
+ String name = record.className;
+ LoadedClass loadedClass = loadedClasses.get(name);
+ Operation o = null;
+
+ switch (record.type) {
+ case START_LOAD:
+ case START_INIT:
+ if (loadedClass == null) {
+ loadedClass = new LoadedClass(
+ name, record.classLoader == 0);
+ if (loadedClass.systemClass) {
+ // Only measure memory for classes in the boot
+ // classpath.
+ loadedClass.measureMemoryUsage();
+ }
+ loadedClasses.put(name, loadedClass);
+ }
+ break;
+
+ case END_LOAD:
+ case END_INIT:
+ o = process.endOperation(record.tid, record.className,
+ loadedClass, record.time);
+ if (o == null) {
+ return;
+ }
+ }
+
+ switch (record.type) {
+ case START_LOAD:
+ process.startOperation(record.tid, loadedClass, record.time,
+ Operation.Type.LOAD);
+ break;
+
+ case START_INIT:
+ process.startOperation(record.tid, loadedClass, record.time,
+ Operation.Type.INIT);
+ break;
+
+ case END_LOAD:
+ loadedClass.loads.add(o);
+ break;
+
+ case END_INIT:
+ loadedClass.initializations.add(o);
+ break;
+ }
+ }
+
+ /**
+ * Indexes information about the process from the given record.
+ */
+ void indexProcess(Record record) {
+ Proc proc = processes.get(record.pid);
+
+ if (proc == null) {
+ // Create a new process object.
+ Proc parent = processes.get(record.ppid);
+ proc = new Proc(parent, record.pid);
+ processes.put(proc.id, proc);
+ if (parent != null) {
+ parent.children.add(proc);
+ }
+ }
+
+ proc.setName(record.processName);
+ }
+
+ /**
+ * Writes this graph to a file.
+ */
+ void toFile(String fileName) throws IOException {
+ FileOutputStream out = new FileOutputStream(fileName);
+ ObjectOutputStream oout = new ObjectOutputStream(
+ new BufferedOutputStream(out));
+
+ System.err.println("Writing object model...");
+
+ oout.writeObject(this);
+
+ oout.close();
+
+ System.err.println("Done!");
+ }
+
+ /**
+ * Reads Root from a file.
+ */
+ static Root fromFile(String fileName)
+ throws IOException, ClassNotFoundException {
+ FileInputStream fin = new FileInputStream(fileName);
+ ObjectInputStream oin = new ObjectInputStream(
+ new BufferedInputStream(fin));
+
+ Root root = (Root) oin.readObject();
+
+ oin.close();
+
+ return root;
+ }
+}
diff --git a/tools/preload/WritePreloadedClassFile.java b/tools/preload/WritePreloadedClassFile.java
new file mode 100644
index 0000000..b067bc2
--- /dev/null
+++ b/tools/preload/WritePreloadedClassFile.java
@@ -0,0 +1,155 @@
+/*
+ * 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.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Writes /frameworks/base/preloaded-classes. Also updates
+ * {@link LoadedClass#preloaded} fields and writes over compiled log file.
+ */
+public class WritePreloadedClassFile {
+
+ /**
+ * Preload any class that take longer to load than MIN_LOAD_TIME_MICROS us.
+ */
+ static final int MIN_LOAD_TIME_MICROS = 1250;
+
+ /**
+ * Preload any class that was loaded by at least MIN_PROCESSES processes.
+ */
+ static final int MIN_PROCESSES = 10;
+
+ public static void main(String[] args) throws IOException,
+ ClassNotFoundException {
+ if (args.length != 1) {
+ System.err.println("Usage: WritePreloadedClassFile [compiled log]");
+ System.exit(-1);
+ }
+ String rootFile = args[0];
+ Root root = Root.fromFile(rootFile);
+
+ // No classes are preloaded to start.
+ for (LoadedClass loadedClass : root.loadedClasses.values()) {
+ loadedClass.preloaded = false;
+ }
+
+ // Open preloaded-classes file for output.
+ Writer out = new BufferedWriter(new OutputStreamWriter(
+ new FileOutputStream(Policy.PRELOADED_CLASS_FILE),
+ Charset.forName("US-ASCII")));
+
+ out.write("# Classes which are preloaded by"
+ + " com.android.internal.os.ZygoteInit.\n");
+ out.write("# Automatically generated by frameworks/base/tools/preload/"
+ + WritePreloadedClassFile.class.getSimpleName() + ".java.\n");
+ out.write("# MIN_LOAD_TIME_MICROS=" + MIN_LOAD_TIME_MICROS + "\n");
+ out.write("# MIN_PROCESSES=" + MIN_PROCESSES + "\n");
+
+ /*
+ * The set of classes to preload. We preload a class if:
+ *
+ * a) it's loaded in the bootclasspath (i.e., is a system class)
+ * b) it takes > MIN_LOAD_TIME_MICROS us to load, and
+ * c) it's loaded by more than one process, or it's loaded by an
+ * application (i.e., not a long running service)
+ */
+ Set<LoadedClass> toPreload = new TreeSet<LoadedClass>();
+
+ // Preload classes that were loaded by at least 2 processes. Hopefully,
+ // the memory associated with these classes will be shared.
+ for (LoadedClass loadedClass : root.loadedClasses.values()) {
+ Set<String> names = loadedClass.processNames();
+ if (!Policy.isPreloadable(loadedClass)) {
+ continue;
+ }
+
+ if (names.size() >= MIN_PROCESSES ||
+ (loadedClass.medianTimeMicros() > MIN_LOAD_TIME_MICROS && names.size() > 1)) {
+ toPreload.add(loadedClass);
+ }
+ }
+
+ int initialSize = toPreload.size();
+ System.out.println(initialSize
+ + " classses were loaded by more than one app.");
+
+ // Preload eligable classes from applications (not long-running
+ // services).
+ for (Proc proc : root.processes.values()) {
+ if (proc.fromZygote() && !Policy.isService(proc.name)) {
+ for (Operation operation : proc.operations) {
+ LoadedClass loadedClass = operation.loadedClass;
+ if (shouldPreload(loadedClass)) {
+ toPreload.add(loadedClass);
+ }
+ }
+ }
+ }
+
+ System.out.println("Added " + (toPreload.size() - initialSize)
+ + " more to speed up applications.");
+
+ System.out.println(toPreload.size()
+ + " total classes will be preloaded.");
+
+ // Make classes that were implicitly loaded by the zygote explicit.
+ // This adds minimal overhead but avoid confusion about classes not
+ // appearing in the list.
+ addAllClassesFrom("zygote", root, toPreload);
+
+ for (LoadedClass loadedClass : toPreload) {
+ out.write(loadedClass.name + "\n");
+ }
+
+ out.close();
+
+ // Update data to reflect LoadedClass.preloaded changes.
+ for (LoadedClass loadedClass : toPreload) {
+ loadedClass.preloaded = true;
+ }
+ root.toFile(rootFile);
+ }
+
+ private static void addAllClassesFrom(String processName, Root root,
+ Set<LoadedClass> toPreload) {
+ for (Proc proc : root.processes.values()) {
+ if (proc.name.equals(processName)) {
+ for (Operation operation : proc.operations) {
+ boolean preloadable
+ = Policy.isPreloadable(operation.loadedClass);
+ if (preloadable) {
+ toPreload.add(operation.loadedClass);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the class should be preloaded.
+ */
+ private static boolean shouldPreload(LoadedClass clazz) {
+ return Policy.isPreloadable(clazz)
+ && clazz.medianTimeMicros() > MIN_LOAD_TIME_MICROS;
+ }
+}
diff --git a/tools/preload/loadclass/Android.mk b/tools/preload/loadclass/Android.mk
new file mode 100644
index 0000000..65828be
--- /dev/null
+++ b/tools/preload/loadclass/Android.mk
@@ -0,0 +1,9 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE := loadclass
+
+include $(BUILD_JAVA_LIBRARY)
diff --git a/tools/preload/loadclass/LoadClass.java b/tools/preload/loadclass/LoadClass.java
new file mode 100644
index 0000000..a71b6a8
--- /dev/null
+++ b/tools/preload/loadclass/LoadClass.java
@@ -0,0 +1,84 @@
+/*
+ * 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 android.util.Log;
+import android.os.Debug;
+
+/**
+ * Loads a class, runs the garbage collector, and prints showmap output.
+ *
+ * <p>Usage: dalvikvm LoadClass [class name]
+ */
+class LoadClass {
+
+ public static void main(String[] args) {
+ System.loadLibrary("android_runtime");
+
+ if (registerNatives() < 0) {
+ throw new RuntimeException("Error registering natives.");
+ }
+
+ Debug.startAllocCounting();
+
+ if (args.length > 0) {
+ try {
+ long start = System.currentTimeMillis();
+ Class.forName(args[0]);
+ long elapsed = System.currentTimeMillis() - start;
+ Log.i("LoadClass", "Loaded " + args[0] + " in " + elapsed
+ + "ms.");
+ } catch (ClassNotFoundException e) {
+ Log.w("LoadClass", e);
+ return;
+ }
+ }
+
+ System.gc();
+
+ int allocCount = Debug.getGlobalAllocCount();
+ int allocSize = Debug.getGlobalAllocSize();
+ int freedCount = Debug.getGlobalFreedCount();
+ int freedSize = Debug.getGlobalFreedSize();
+ long nativeHeapSize = Debug.getNativeHeapSize();
+
+ Debug.stopAllocCounting();
+
+ StringBuilder response = new StringBuilder("DECAFBAD");
+
+ int[] pages = new int[6];
+ Debug.MemoryInfo memoryInfo = new Debug.MemoryInfo();
+ Debug.getMemoryInfo(memoryInfo);
+ response.append(',').append(memoryInfo.nativeSharedDirty);
+ response.append(',').append(memoryInfo.dalvikSharedDirty);
+ response.append(',').append(memoryInfo.otherSharedDirty);
+ response.append(',').append(memoryInfo.nativePrivateDirty);
+ response.append(',').append(memoryInfo.dalvikPrivateDirty);
+ response.append(',').append(memoryInfo.otherPrivateDirty);
+
+ response.append(',').append(allocCount);
+ response.append(',').append(allocSize);
+ response.append(',').append(freedCount);
+ response.append(',').append(freedSize);
+ response.append(',').append(nativeHeapSize);
+
+ System.out.println(response.toString());
+ }
+
+ /**
+ * Registers native functions. See AndroidRuntime.cpp.
+ */
+ static native int registerNatives();
+}
diff --git a/tools/preload/preload.iml b/tools/preload/preload.iml
new file mode 100644
index 0000000..2d87c55
--- /dev/null
+++ b/tools/preload/preload.iml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="false">
+ <output url="file:///tmp/preload" />
+ <output-test url="file:///tmp/preload" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
+
diff --git a/tools/preload/preload.ipr b/tools/preload/preload.ipr
new file mode 100644
index 0000000..0c9621c
--- /dev/null
+++ b/tools/preload/preload.ipr
@@ -0,0 +1,495 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project relativePaths="false" version="4">
+ <component name="AntConfiguration">
+ <defaultAnt bundledAnt="true" />
+ </component>
+ <component name="BuildJarProjectSettings">
+ <option name="BUILD_JARS_ON_MAKE" value="false" />
+ </component>
+ <component name="ChangeBrowserSettings">
+ <option name="MAIN_SPLITTER_PROPORTION" value="0.3" />
+ <option name="MESSAGES_SPLITTER_PROPORTION" value="0.8" />
+ <option name="USE_DATE_BEFORE_FILTER" value="false" />
+ <option name="USE_DATE_AFTER_FILTER" value="false" />
+ <option name="USE_CHANGE_BEFORE_FILTER" value="false" />
+ <option name="USE_CHANGE_AFTER_FILTER" value="false" />
+ <option name="DATE_BEFORE" value="" />
+ <option name="DATE_AFTER" value="" />
+ <option name="CHANGE_BEFORE" value="" />
+ <option name="CHANGE_AFTER" value="" />
+ <option name="USE_USER_FILTER" value="false" />
+ <option name="USER" value="" />
+ </component>
+ <component name="CodeStyleProjectProfileManger">
+ <option name="PROJECT_PROFILE" />
+ <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
+ </component>
+ <component name="CodeStyleSettingsManager">
+ <option name="PER_PROJECT_SETTINGS">
+ <value>
+ <ADDITIONAL_INDENT_OPTIONS fileType="java">
+ <option name="INDENT_SIZE" value="4" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ </ADDITIONAL_INDENT_OPTIONS>
+ <ADDITIONAL_INDENT_OPTIONS fileType="xml">
+ <option name="INDENT_SIZE" value="4" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ </ADDITIONAL_INDENT_OPTIONS>
+ </value>
+ </option>
+ <option name="USE_PER_PROJECT_SETTINGS" value="false" />
+ </component>
+ <component name="CompilerConfiguration">
+ <option name="DEFAULT_COMPILER" value="Javac" />
+ <option name="DEPLOY_AFTER_MAKE" value="0" />
+ <resourceExtensions>
+ <entry name=".+\.(properties|xml|html|dtd|tld)" />
+ <entry name=".+\.(gif|png|jpeg|jpg)" />
+ </resourceExtensions>
+ <wildcardResourcePatterns>
+ <entry name="?*.properties" />
+ <entry name="?*.xml" />
+ <entry name="?*.gif" />
+ <entry name="?*.png" />
+ <entry name="?*.jpeg" />
+ <entry name="?*.jpg" />
+ <entry name="?*.html" />
+ <entry name="?*.dtd" />
+ <entry name="?*.tld" />
+ </wildcardResourcePatterns>
+ </component>
+ <component name="Cvs2Configuration">
+ <option name="PRUNE_EMPTY_DIRECTORIES" value="true" />
+ <option name="MERGING_MODE" value="0" />
+ <option name="MERGE_WITH_BRANCH1_NAME" value="HEAD" />
+ <option name="MERGE_WITH_BRANCH2_NAME" value="HEAD" />
+ <option name="RESET_STICKY" value="false" />
+ <option name="CREATE_NEW_DIRECTORIES" value="true" />
+ <option name="DEFAULT_TEXT_FILE_SUBSTITUTION" value="kv" />
+ <option name="PROCESS_UNKNOWN_FILES" value="false" />
+ <option name="PROCESS_DELETED_FILES" value="false" />
+ <option name="PROCESS_IGNORED_FILES" value="false" />
+ <option name="RESERVED_EDIT" value="false" />
+ <option name="CHECKOUT_DATE_OR_REVISION_SETTINGS">
+ <value>
+ <option name="BRANCH" value="" />
+ <option name="DATE" value="" />
+ <option name="USE_BRANCH" value="false" />
+ <option name="USE_DATE" value="false" />
+ </value>
+ </option>
+ <option name="UPDATE_DATE_OR_REVISION_SETTINGS">
+ <value>
+ <option name="BRANCH" value="" />
+ <option name="DATE" value="" />
+ <option name="USE_BRANCH" value="false" />
+ <option name="USE_DATE" value="false" />
+ </value>
+ </option>
+ <option name="SHOW_CHANGES_REVISION_SETTINGS">
+ <value>
+ <option name="BRANCH" value="" />
+ <option name="DATE" value="" />
+ <option name="USE_BRANCH" value="false" />
+ <option name="USE_DATE" value="false" />
+ </value>
+ </option>
+ <option name="SHOW_OUTPUT" value="false" />
+ <option name="ADD_WATCH_INDEX" value="0" />
+ <option name="REMOVE_WATCH_INDEX" value="0" />
+ <option name="UPDATE_KEYWORD_SUBSTITUTION" />
+ <option name="MAKE_NEW_FILES_READONLY" value="false" />
+ <option name="SHOW_CORRUPTED_PROJECT_FILES" value="0" />
+ <option name="TAG_AFTER_PROJECT_COMMIT" value="false" />
+ <option name="OVERRIDE_EXISTING_TAG_FOR_PROJECT" value="true" />
+ <option name="TAG_AFTER_PROJECT_COMMIT_NAME" value="" />
+ <option name="CLEAN_COPY" value="false" />
+ </component>
+ <component name="DependenciesAnalyzeManager">
+ <option name="myForwardDirection" value="false" />
+ </component>
+ <component name="DependencyValidationManager">
+ <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+ </component>
+ <component name="EclipseCompilerSettings">
+ <option name="DEBUGGING_INFO" value="true" />
+ <option name="GENERATE_NO_WARNINGS" value="true" />
+ <option name="DEPRECATION" value="false" />
+ <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+ <option name="MAXIMUM_HEAP_SIZE" value="128" />
+ </component>
+ <component name="EclipseEmbeddedCompilerSettings">
+ <option name="DEBUGGING_INFO" value="true" />
+ <option name="GENERATE_NO_WARNINGS" value="true" />
+ <option name="DEPRECATION" value="false" />
+ <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+ <option name="MAXIMUM_HEAP_SIZE" value="128" />
+ </component>
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
+ <component name="EntryPointsManager">
+ <entry_points version="2.0" />
+ </component>
+ <component name="ExportToHTMLSettings">
+ <option name="PRINT_LINE_NUMBERS" value="false" />
+ <option name="OPEN_IN_BROWSER" value="false" />
+ <option name="OUTPUT_DIRECTORY" />
+ </component>
+ <component name="IdProvider" IDEtalkID="D171F99B9178C1675593DC9A76A5CC7E" />
+ <component name="InspectionProjectProfileManager">
+ <option name="PROJECT_PROFILE" value="Project Default" />
+ <option name="USE_PROJECT_PROFILE" value="true" />
+ <version value="1.0" />
+ <profiles>
+ <profile version="1.0" is_locked="false">
+ <option name="myName" value="Project Default" />
+ <option name="myLocal" value="false" />
+ <inspection_tool class="JavaDoc" enabled="false" level="WARNING" enabled_by_default="false">
+ <option name="TOP_LEVEL_CLASS_OPTIONS">
+ <value>
+ <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+ <option name="REQUIRED_TAGS" value="" />
+ </value>
+ </option>
+ <option name="INNER_CLASS_OPTIONS">
+ <value>
+ <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+ <option name="REQUIRED_TAGS" value="" />
+ </value>
+ </option>
+ <option name="METHOD_OPTIONS">
+ <value>
+ <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+ <option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
+ </value>
+ </option>
+ <option name="FIELD_OPTIONS">
+ <value>
+ <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
+ <option name="REQUIRED_TAGS" value="" />
+ </value>
+ </option>
+ <option name="IGNORE_DEPRECATED" value="false" />
+ <option name="IGNORE_JAVADOC_PERIOD" value="true" />
+ <option name="myAdditionalJavadocTags" value="" />
+ </inspection_tool>
+ <inspection_tool class="JavaLangImport" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="OnDemandImport" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="RedundantImport" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="SamePackageImport" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="UnusedImport" enabled="true" level="WARNING" enabled_by_default="true" />
+ </profile>
+ </profiles>
+ <list size="4">
+ <item index="0" class="java.lang.String" itemvalue="WARNING" />
+ <item index="1" class="java.lang.String" itemvalue="SERVER PROBLEM" />
+ <item index="2" class="java.lang.String" itemvalue="INFO" />
+ <item index="3" class="java.lang.String" itemvalue="ERROR" />
+ </list>
+ </component>
+ <component name="JavacSettings">
+ <option name="DEBUGGING_INFO" value="true" />
+ <option name="GENERATE_NO_WARNINGS" value="false" />
+ <option name="DEPRECATION" value="true" />
+ <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+ <option name="MAXIMUM_HEAP_SIZE" value="128" />
+ </component>
+ <component name="JavadocGenerationManager">
+ <option name="OUTPUT_DIRECTORY" />
+ <option name="OPTION_SCOPE" value="protected" />
+ <option name="OPTION_HIERARCHY" value="true" />
+ <option name="OPTION_NAVIGATOR" value="true" />
+ <option name="OPTION_INDEX" value="true" />
+ <option name="OPTION_SEPARATE_INDEX" value="true" />
+ <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
+ <option name="OPTION_DEPRECATED_LIST" value="true" />
+ <option name="OTHER_OPTIONS" value="" />
+ <option name="HEAP_SIZE" />
+ <option name="LOCALE" />
+ <option name="OPEN_IN_BROWSER" value="true" />
+ </component>
+ <component name="JikesSettings">
+ <option name="JIKES_PATH" value="" />
+ <option name="DEBUGGING_INFO" value="true" />
+ <option name="DEPRECATION" value="true" />
+ <option name="GENERATE_NO_WARNINGS" value="false" />
+ <option name="IS_EMACS_ERRORS_MODE" value="true" />
+ <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+ </component>
+ <component name="LogConsolePreferences">
+ <option name="FILTER_ERRORS" value="false" />
+ <option name="FILTER_WARNINGS" value="false" />
+ <option name="FILTER_INFO" value="true" />
+ <option name="CUSTOM_FILTER" />
+ </component>
+ <component name="Palette2">
+ <group name="Swing">
+ <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+ </item>
+ <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+ </item>
+ <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
+ <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+ <initial-values>
+ <property name="text" value="Button" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="RadioButton" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="CheckBox" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="Label" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+ <preferred-size width="200" height="200" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+ <preferred-size width="200" height="200" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+ </item>
+ <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+ <preferred-size width="-1" height="20" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+ </item>
+ <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+ </item>
+ </group>
+ </component>
+ <component name="PerforceChangeBrowserSettings">
+ <option name="USE_CLIENT_FILTER" value="true" />
+ <option name="CLIENT" value="" />
+ </component>
+ <component name="ProjectDetails">
+ <option name="projectName" value="preload" />
+ </component>
+ <component name="ProjectFileVersion" converted="true" />
+ <component name="ProjectKey">
+ <option name="state" value="project:///Volumes/Android/donut/frameworks/base/tools/preload/preload.ipr" />
+ </component>
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/preload.iml" filepath="$PROJECT_DIR$/preload.iml" />
+ </modules>
+ </component>
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-name="1.5" project-jdk-type="JavaSDK">
+ <output url="file:///tmp/preload" />
+ </component>
+ <component name="RmicSettings">
+ <option name="IS_EANABLED" value="false" />
+ <option name="DEBUGGING_INFO" value="true" />
+ <option name="GENERATE_NO_WARNINGS" value="false" />
+ <option name="GENERATE_IIOP_STUBS" value="false" />
+ <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+ </component>
+ <component name="StarteamConfiguration">
+ <option name="SERVER" value="" />
+ <option name="PORT" value="49201" />
+ <option name="USER" value="" />
+ <option name="PASSWORD" value="" />
+ <option name="PROJECT" value="" />
+ <option name="VIEW" value="" />
+ <option name="ALTERNATIVE_WORKING_PATH" value="" />
+ <option name="LOCK_ON_CHECKOUT" value="false" />
+ <option name="UNLOCK_ON_CHECKIN" value="false" />
+ </component>
+ <component name="Struts Assistant">
+ <option name="showInputs" value="true" />
+ <option name="resources">
+ <value>
+ <option name="strutsPath" />
+ <option name="strutsHelp" />
+ </value>
+ </option>
+ <option name="selectedTaglibs" />
+ <option name="selectedTaglibs" />
+ <option name="myStrutsValidationEnabled" value="true" />
+ <option name="myTilesValidationEnabled" value="true" />
+ <option name="myValidatorValidationEnabled" value="true" />
+ <option name="myReportErrorsAsWarnings" value="true" />
+ </component>
+ <component name="SvnBranchConfigurationManager">
+ <option name="mySupportsUserInfoFilter" value="true" />
+ </component>
+ <component name="SvnChangesBrowserSettings">
+ <option name="USE_AUTHOR_FIELD" value="true" />
+ <option name="AUTHOR" value="" />
+ <option name="LOCATION" value="" />
+ <option name="USE_PROJECT_SETTINGS" value="true" />
+ <option name="USE_ALTERNATE_LOCATION" value="false" />
+ </component>
+ <component name="VCS.FileViewConfiguration">
+ <option name="SELECTED_STATUSES" value="DEFAULT" />
+ <option name="SELECTED_COLUMNS" value="DEFAULT" />
+ <option name="SHOW_FILTERS" value="true" />
+ <option name="CUSTOMIZE_VIEW" value="true" />
+ <option name="SHOW_FILE_HISTORY_AS_TREE" value="true" />
+ </component>
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Perforce" />
+ <mapping directory="/Volumes/Android/donut/frameworks/base" vcs="Git" />
+ </component>
+ <component name="VssConfiguration">
+ <option name="CLIENT_PATH" value="" />
+ <option name="SRCSAFEINI_PATH" value="" />
+ <option name="USER_NAME" value="" />
+ <option name="PWD" value="" />
+ <option name="VSS_IS_INITIALIZED" value="false" />
+ <CheckoutOptions>
+ <option name="COMMENT" value="" />
+ <option name="DO_NOT_GET_LATEST_VERSION" value="false" />
+ <option name="REPLACE_WRITABLE" value="false" />
+ <option name="RECURSIVE" value="false" />
+ </CheckoutOptions>
+ <CheckinOptions>
+ <option name="COMMENT" value="" />
+ <option name="KEEP_CHECKED_OUT" value="false" />
+ <option name="RECURSIVE" value="false" />
+ </CheckinOptions>
+ <AddOptions>
+ <option name="STORE_ONLY_LATEST_VERSION" value="false" />
+ <option name="CHECK_OUT_IMMEDIATELY" value="false" />
+ <option name="FILE_TYPE" value="0" />
+ </AddOptions>
+ <UndocheckoutOptions>
+ <option name="MAKE_WRITABLE" value="false" />
+ <option name="REPLACE_LOCAL_COPY" value="0" />
+ <option name="RECURSIVE" value="false" />
+ </UndocheckoutOptions>
+ <GetOptions>
+ <option name="REPLACE_WRITABLE" value="0" />
+ <option name="MAKE_WRITABLE" value="false" />
+ <option name="ANSWER_NEGATIVELY" value="false" />
+ <option name="ANSWER_POSITIVELY" value="false" />
+ <option name="RECURSIVE" value="false" />
+ <option name="VERSION" />
+ </GetOptions>
+ <VssConfigurableExcludedFilesTag />
+ </component>
+ <component name="antWorkspaceConfiguration">
+ <option name="IS_AUTOSCROLL_TO_SOURCE" value="false" />
+ <option name="FILTER_TARGETS" value="false" />
+ </component>
+ <component name="com.intellij.ide.util.scopeChooser.ScopeChooserConfigurable" proportions="" version="1">
+ <option name="myLastEditedConfigurable" />
+ </component>
+ <component name="com.intellij.jsf.UserDefinedFacesConfigs">
+ <option name="USER_DEFINED_CONFIGS">
+ <value>
+ <list size="0" />
+ </value>
+ </option>
+ </component>
+ <component name="com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectRootMasterDetailsConfigurable" proportions="" version="1">
+ <option name="myPlainMode" value="false" />
+ <option name="myLastEditedConfigurable" />
+ </component>
+ <component name="com.intellij.profile.ui.ErrorOptionsConfigurable" proportions="" version="1">
+ <option name="myLastEditedConfigurable" />
+ </component>
+ <component name="uidesigner-configuration">
+ <option name="INSTRUMENT_CLASSES" value="true" />
+ <option name="COPY_FORMS_RUNTIME_TO_OUTPUT" value="true" />
+ <option name="DEFAULT_LAYOUT_MANAGER" value="GridLayoutManager" />
+ </component>
+</project>
+
diff --git a/tools/preload/sorttable.js b/tools/preload/sorttable.js
new file mode 100644
index 0000000..25bccb2
--- /dev/null
+++ b/tools/preload/sorttable.js
@@ -0,0 +1,493 @@
+/*
+ SortTable
+ version 2
+ 7th April 2007
+ Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
+
+ Instructions:
+ Download this file
+ Add <script src="sorttable.js"></script> to your HTML
+ Add class="sortable" to any table you'd like to make sortable
+ Click on the headers to sort
+
+ Thanks to many, many people for contributions and suggestions.
+ Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
+ This basically means: do what you want with it.
+*/
+
+
+var stIsIE = /*@cc_on!@*/false;
+
+sorttable = {
+ init: function() {
+ // quit if this function has already been called
+ if (arguments.callee.done) return;
+ // flag this function so we don't do the same thing twice
+ arguments.callee.done = true;
+ // kill the timer
+ if (_timer) clearInterval(_timer);
+
+ if (!document.createElement || !document.getElementsByTagName) return;
+
+ sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
+
+ forEach(document.getElementsByTagName('table'), function(table) {
+ if (table.className.search(/\bsortable\b/) != -1) {
+ sorttable.makeSortable(table);
+ }
+ });
+
+ },
+
+ makeSortable: function(table) {
+ if (table.getElementsByTagName('thead').length == 0) {
+ // table doesn't have a tHead. Since it should have, create one and
+ // put the first table row in it.
+ the = document.createElement('thead');
+ the.appendChild(table.rows[0]);
+ table.insertBefore(the,table.firstChild);
+ }
+ // Safari doesn't support table.tHead, sigh
+ if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
+
+ if (table.tHead.rows.length != 1) return; // can't cope with two header rows
+
+ // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as
+ // "total" rows, for example). This is B&R, since what you're supposed
+ // to do is put them in a tfoot. So, if there are sortbottom rows,
+ // for backwards compatibility, move them to tfoot (creating it if needed).
+ sortbottomrows = [];
+ for (var i=0; i<table.rows.length; i++) {
+ if (table.rows[i].className.search(/\bsortbottom\b/) != -1) {
+ sortbottomrows[sortbottomrows.length] = table.rows[i];
+ }
+ }
+ if (sortbottomrows) {
+ if (table.tFoot == null) {
+ // table doesn't have a tfoot. Create one.
+ tfo = document.createElement('tfoot');
+ table.appendChild(tfo);
+ }
+ for (var i=0; i<sortbottomrows.length; i++) {
+ tfo.appendChild(sortbottomrows[i]);
+ }
+ delete sortbottomrows;
+ }
+
+ // work through each column and calculate its type
+ headrow = table.tHead.rows[0].cells;
+ for (var i=0; i<headrow.length; i++) {
+ // manually override the type with a sorttable_type attribute
+ if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col
+ mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/);
+ if (mtch) { override = mtch[1]; }
+ if (mtch && typeof sorttable["sort_"+override] == 'function') {
+ headrow[i].sorttable_sortfunction = sorttable["sort_"+override];
+ } else {
+ headrow[i].sorttable_sortfunction = sorttable.guessType(table,i);
+ }
+ // make it clickable to sort
+ headrow[i].sorttable_columnindex = i;
+ headrow[i].sorttable_tbody = table.tBodies[0];
+ dean_addEvent(headrow[i],"click", function(e) {
+
+ if (this.className.search(/\bsorttable_sorted\b/) != -1) {
+ // if we're already sorted by this column, just
+ // reverse the table, which is quicker
+ sorttable.reverse(this.sorttable_tbody);
+ this.className = this.className.replace('sorttable_sorted',
+ 'sorttable_sorted_reverse');
+ this.removeChild(document.getElementById('sorttable_sortfwdind'));
+ sortrevind = document.createElement('span');
+ sortrevind.id = "sorttable_sortrevind";
+ sortrevind.innerHTML = stIsIE ? ' <font face="webdings">5</font>' : ' ▴';
+ this.appendChild(sortrevind);
+ return;
+ }
+ if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) {
+ // if we're already sorted by this column in reverse, just
+ // re-reverse the table, which is quicker
+ sorttable.reverse(this.sorttable_tbody);
+ this.className = this.className.replace('sorttable_sorted_reverse',
+ 'sorttable_sorted');
+ this.removeChild(document.getElementById('sorttable_sortrevind'));
+ sortfwdind = document.createElement('span');
+ sortfwdind.id = "sorttable_sortfwdind";
+ sortfwdind.innerHTML = stIsIE ? ' <font face="webdings">6</font>' : ' ▾';
+ this.appendChild(sortfwdind);
+ return;
+ }
+
+ // remove sorttable_sorted classes
+ theadrow = this.parentNode;
+ forEach(theadrow.childNodes, function(cell) {
+ if (cell.nodeType == 1) { // an element
+ cell.className = cell.className.replace('sorttable_sorted_reverse','');
+ cell.className = cell.className.replace('sorttable_sorted','');
+ }
+ });
+ sortfwdind = document.getElementById('sorttable_sortfwdind');
+ if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }
+ sortrevind = document.getElementById('sorttable_sortrevind');
+ if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }
+
+ this.className += ' sorttable_sorted';
+ sortfwdind = document.createElement('span');
+ sortfwdind.id = "sorttable_sortfwdind";
+ sortfwdind.innerHTML = stIsIE ? ' <font face="webdings">6</font>' : ' ▾';
+ this.appendChild(sortfwdind);
+
+ // build an array to sort. This is a Schwartzian transform thing,
+ // i.e., we "decorate" each row with the actual sort key,
+ // sort based on the sort keys, and then put the rows back in order
+ // which is a lot faster because you only do getInnerText once per row
+ row_array = [];
+ col = this.sorttable_columnindex;
+ rows = this.sorttable_tbody.rows;
+ for (var j=0; j<rows.length; j++) {
+ row_array[row_array.length] = [sorttable.getInnerText(rows[j].cells[col]), rows[j]];
+ }
+ /* If you want a stable sort, uncomment the following line */
+ //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
+ /* and comment out this one */
+ row_array.sort(this.sorttable_sortfunction);
+
+ tb = this.sorttable_tbody;
+ for (var j=0; j<row_array.length; j++) {
+ tb.appendChild(row_array[j][1]);
+ }
+
+ delete row_array;
+ });
+ }
+ }
+ },
+
+ guessType: function(table, column) {
+ // guess the type of a column based on its first non-blank row
+ sortfn = sorttable.sort_alpha;
+ for (var i=0; i<table.tBodies[0].rows.length; i++) {
+ text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);
+ if (text != '') {
+ if (text.match(/^-?[£$¤]?[\d,.]+%?$/)) {
+ return sorttable.sort_numeric;
+ }
+ // check for a date: dd/mm/yyyy or dd/mm/yy
+ // can have / or . or - as separator
+ // can be mm/dd as well
+ possdate = text.match(sorttable.DATE_RE)
+ if (possdate) {
+ // looks like a date
+ first = parseInt(possdate[1]);
+ second = parseInt(possdate[2]);
+ if (first > 12) {
+ // definitely dd/mm
+ return sorttable.sort_ddmm;
+ } else if (second > 12) {
+ return sorttable.sort_mmdd;
+ } else {
+ // looks like a date, but we can't tell which, so assume
+ // that it's dd/mm (English imperialism!) and keep looking
+ sortfn = sorttable.sort_ddmm;
+ }
+ }
+ }
+ }
+ return sortfn;
+ },
+
+ getInnerText: function(node) {
+ // gets the text we want to use for sorting for a cell.
+ // strips leading and trailing whitespace.
+ // this is *not* a generic getInnerText function; it's special to sorttable.
+ // for example, you can override the cell text with a customkey attribute.
+ // it also gets .value for <input> fields.
+
+ hasInputs = (typeof node.getElementsByTagName == 'function') &&
+ node.getElementsByTagName('input').length;
+
+ if (node.getAttribute("sorttable_customkey") != null) {
+ return node.getAttribute("sorttable_customkey");
+ }
+ else if (typeof node.textContent != 'undefined' && !hasInputs) {
+ return node.textContent.replace(/^\s+|\s+$/g, '');
+ }
+ else if (typeof node.innerText != 'undefined' && !hasInputs) {
+ return node.innerText.replace(/^\s+|\s+$/g, '');
+ }
+ else if (typeof node.text != 'undefined' && !hasInputs) {
+ return node.text.replace(/^\s+|\s+$/g, '');
+ }
+ else {
+ switch (node.nodeType) {
+ case 3:
+ if (node.nodeName.toLowerCase() == 'input') {
+ return node.value.replace(/^\s+|\s+$/g, '');
+ }
+ case 4:
+ return node.nodeValue.replace(/^\s+|\s+$/g, '');
+ break;
+ case 1:
+ case 11:
+ var innerText = '';
+ for (var i = 0; i < node.childNodes.length; i++) {
+ innerText += sorttable.getInnerText(node.childNodes[i]);
+ }
+ return innerText.replace(/^\s+|\s+$/g, '');
+ break;
+ default:
+ return '';
+ }
+ }
+ },
+
+ reverse: function(tbody) {
+ // reverse the rows in a tbody
+ newrows = [];
+ for (var i=0; i<tbody.rows.length; i++) {
+ newrows[newrows.length] = tbody.rows[i];
+ }
+ for (var i=newrows.length-1; i>=0; i--) {
+ tbody.appendChild(newrows[i]);
+ }
+ delete newrows;
+ },
+
+ /* sort functions
+ each sort function takes two parameters, a and b
+ you are comparing a[0] and b[0] */
+ sort_numeric: function(a,b) {
+ aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
+ if (isNaN(aa)) aa = 0;
+ bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));
+ if (isNaN(bb)) bb = 0;
+ return aa-bb;
+ },
+ sort_alpha: function(a,b) {
+ if (a[0]==b[0]) return 0;
+ if (a[0]<b[0]) return -1;
+ return 1;
+ },
+ sort_ddmm: function(a,b) {
+ mtch = a[0].match(sorttable.DATE_RE);
+ y = mtch[3]; m = mtch[2]; d = mtch[1];
+ if (m.length == 1) m = '0'+m;
+ if (d.length == 1) d = '0'+d;
+ dt1 = y+m+d;
+ mtch = b[0].match(sorttable.DATE_RE);
+ y = mtch[3]; m = mtch[2]; d = mtch[1];
+ if (m.length == 1) m = '0'+m;
+ if (d.length == 1) d = '0'+d;
+ dt2 = y+m+d;
+ if (dt1==dt2) return 0;
+ if (dt1<dt2) return -1;
+ return 1;
+ },
+ sort_mmdd: function(a,b) {
+ mtch = a[0].match(sorttable.DATE_RE);
+ y = mtch[3]; d = mtch[2]; m = mtch[1];
+ if (m.length == 1) m = '0'+m;
+ if (d.length == 1) d = '0'+d;
+ dt1 = y+m+d;
+ mtch = b[0].match(sorttable.DATE_RE);
+ y = mtch[3]; d = mtch[2]; m = mtch[1];
+ if (m.length == 1) m = '0'+m;
+ if (d.length == 1) d = '0'+d;
+ dt2 = y+m+d;
+ if (dt1==dt2) return 0;
+ if (dt1<dt2) return -1;
+ return 1;
+ },
+
+ shaker_sort: function(list, comp_func) {
+ // A stable sort function to allow multi-level sorting of data
+ // see: http://en.wikipedia.org/wiki/Cocktail_sort
+ // thanks to Joseph Nahmias
+ var b = 0;
+ var t = list.length - 1;
+ var swap = true;
+
+ while(swap) {
+ swap = false;
+ for(var i = b; i < t; ++i) {
+ if ( comp_func(list[i], list[i+1]) > 0 ) {
+ var q = list[i]; list[i] = list[i+1]; list[i+1] = q;
+ swap = true;
+ }
+ } // for
+ t--;
+
+ if (!swap) break;
+
+ for(var i = t; i > b; --i) {
+ if ( comp_func(list[i], list[i-1]) < 0 ) {
+ var q = list[i]; list[i] = list[i-1]; list[i-1] = q;
+ swap = true;
+ }
+ } // for
+ b++;
+
+ } // while(swap)
+ }
+}
+
+/* ******************************************************************
+ Supporting functions: bundled here to avoid depending on a library
+ ****************************************************************** */
+
+// Dean Edwards/Matthias Miller/John Resig
+
+/* for Mozilla/Opera9 */
+if (document.addEventListener) {
+ document.addEventListener("DOMContentLoaded", sorttable.init, false);
+}
+
+/* for Internet Explorer */
+/*@cc_on @*/
+/*@if (@_win32)
+ document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
+ var script = document.getElementById("__ie_onload");
+ script.onreadystatechange = function() {
+ if (this.readyState == "complete") {
+ sorttable.init(); // call the onload handler
+ }
+ };
+/*@end @*/
+
+/* for Safari */
+if (/WebKit/i.test(navigator.userAgent)) { // sniff
+ var _timer = setInterval(function() {
+ if (/loaded|complete/.test(document.readyState)) {
+ sorttable.init(); // call the onload handler
+ }
+ }, 10);
+}
+
+/* for other browsers */
+window.onload = sorttable.init;
+
+// written by Dean Edwards, 2005
+// with input from Tino Zijdel, Matthias Miller, Diego Perini
+
+// http://dean.edwards.name/weblog/2005/10/add-event/
+
+function dean_addEvent(element, type, handler) {
+ if (element.addEventListener) {
+ element.addEventListener(type, handler, false);
+ } else {
+ // assign each event handler a unique ID
+ if (!handler.$$guid) handler.$$guid = dean_addEvent.guid++;
+ // create a hash table of event types for the element
+ if (!element.events) element.events = {};
+ // create a hash table of event handlers for each element/event pair
+ var handlers = element.events[type];
+ if (!handlers) {
+ handlers = element.events[type] = {};
+ // store the existing event handler (if there is one)
+ if (element["on" + type]) {
+ handlers[0] = element["on" + type];
+ }
+ }
+ // store the event handler in the hash table
+ handlers[handler.$$guid] = handler;
+ // assign a global event handler to do all the work
+ element["on" + type] = handleEvent;
+ }
+};
+// a counter used to create unique IDs
+dean_addEvent.guid = 1;
+
+function removeEvent(element, type, handler) {
+ if (element.removeEventListener) {
+ element.removeEventListener(type, handler, false);
+ } else {
+ // delete the event handler from the hash table
+ if (element.events && element.events[type]) {
+ delete element.events[type][handler.$$guid];
+ }
+ }
+};
+
+function handleEvent(event) {
+ var returnValue = true;
+ // grab the event object (IE uses a global event object)
+ event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
+ // get a reference to the hash table of event handlers
+ var handlers = this.events[event.type];
+ // execute each event handler
+ for (var i in handlers) {
+ this.$$handleEvent = handlers[i];
+ if (this.$$handleEvent(event) === false) {
+ returnValue = false;
+ }
+ }
+ return returnValue;
+};
+
+function fixEvent(event) {
+ // add W3C standard event methods
+ event.preventDefault = fixEvent.preventDefault;
+ event.stopPropagation = fixEvent.stopPropagation;
+ return event;
+};
+fixEvent.preventDefault = function() {
+ this.returnValue = false;
+};
+fixEvent.stopPropagation = function() {
+ this.cancelBubble = true;
+}
+
+// Dean's forEach: http://dean.edwards.name/base/forEach.js
+/*
+ forEach, version 1.0
+ Copyright 2006, Dean Edwards
+ License: http://www.opensource.org/licenses/mit-license.php
+*/
+
+// array-like enumeration
+if (!Array.forEach) { // mozilla already supports this
+ Array.forEach = function(array, block, context) {
+ for (var i = 0; i < array.length; i++) {
+ block.call(context, array[i], i, array);
+ }
+ };
+}
+
+// generic enumeration
+Function.prototype.forEach = function(object, block, context) {
+ for (var key in object) {
+ if (typeof this.prototype[key] == "undefined") {
+ block.call(context, object[key], key, object);
+ }
+ }
+};
+
+// character enumeration
+String.forEach = function(string, block, context) {
+ Array.forEach(string.split(""), function(chr, index) {
+ block.call(context, chr, index, string);
+ });
+};
+
+// globally resolve forEach enumeration
+var forEach = function(object, block, context) {
+ if (object) {
+ var resolve = Object; // default
+ if (object instanceof Function) {
+ // functions have a "length" property
+ resolve = Function;
+ } else if (object.forEach instanceof Function) {
+ // the object implements a custom forEach method so use that
+ object.forEach(block, context);
+ return;
+ } else if (typeof object == "string") {
+ // the object is a string
+ resolve = String;
+ } else if (typeof object.length == "number") {
+ // the object is array-like
+ resolve = Array;
+ }
+ resolve.forEach(object, block, context);
+ }
+};
+
diff --git a/tools/validatekeymaps/Android.mk b/tools/validatekeymaps/Android.mk
new file mode 100644
index 0000000..9af721d
--- /dev/null
+++ b/tools/validatekeymaps/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright 2010 The Android Open Source Project
+#
+# Keymap validation tool.
+#
+
+# This tool is prebuilt if we're doing an app-only build.
+ifeq ($(TARGET_BUILD_APPS),)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ Main.cpp
+
+LOCAL_CFLAGS := -Wall -Werror
+
+LOCAL_STATIC_LIBRARIES := \
+ libinput \
+ libutils \
+ libcutils \
+ liblog
+
+ifeq ($(HOST_OS),linux)
+LOCAL_LDLIBS += -ldl -lpthread
+endif
+
+LOCAL_MODULE := validatekeymaps
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # TARGET_BUILD_APPS
diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp
new file mode 100644
index 0000000..5b45c55
--- /dev/null
+++ b/tools/validatekeymaps/Main.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <input/KeyCharacterMap.h>
+#include <input/KeyLayoutMap.h>
+#include <input/VirtualKeyMap.h>
+#include <utils/PropertyMap.h>
+#include <utils/String8.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+using namespace android;
+
+static const char* gProgName = "validatekeymaps";
+
+enum FileType {
+ FILETYPE_UNKNOWN,
+ FILETYPE_KEYLAYOUT,
+ FILETYPE_KEYCHARACTERMAP,
+ FILETYPE_VIRTUALKEYDEFINITION,
+ FILETYPE_INPUTDEVICECONFIGURATION,
+};
+
+
+static void usage() {
+ fprintf(stderr, "Keymap Validation Tool\n\n");
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr,
+ " %s [*.kl] [*.kcm] [*.idc] [virtualkeys.*] [...]\n"
+ " Validates the specified key layouts, key character maps, \n"
+ " input device configurations, or virtual key definitions.\n\n",
+ gProgName);
+}
+
+static FileType getFileType(const char* filename) {
+ const char *extension = strrchr(filename, '.');
+ if (extension) {
+ if (strcmp(extension, ".kl") == 0) {
+ return FILETYPE_KEYLAYOUT;
+ }
+ if (strcmp(extension, ".kcm") == 0) {
+ return FILETYPE_KEYCHARACTERMAP;
+ }
+ if (strcmp(extension, ".idc") == 0) {
+ return FILETYPE_INPUTDEVICECONFIGURATION;
+ }
+ }
+
+ if (strstr(filename, "virtualkeys.")) {
+ return FILETYPE_VIRTUALKEYDEFINITION;
+ }
+
+ return FILETYPE_UNKNOWN;
+}
+
+static bool validateFile(const char* filename) {
+ fprintf(stdout, "Validating file '%s'...\n", filename);
+
+ FileType fileType = getFileType(filename);
+ switch (fileType) {
+ case FILETYPE_UNKNOWN:
+ fprintf(stderr, "Supported file types: *.kl, *.kcm, virtualkeys.*\n\n");
+ return false;
+
+ case FILETYPE_KEYLAYOUT: {
+ sp<KeyLayoutMap> map;
+ status_t status = KeyLayoutMap::load(String8(filename), &map);
+ if (status) {
+ fprintf(stderr, "Error %d parsing key layout file.\n\n", status);
+ return false;
+ }
+ break;
+ }
+
+ case FILETYPE_KEYCHARACTERMAP: {
+ sp<KeyCharacterMap> map;
+ status_t status = KeyCharacterMap::load(String8(filename),
+ KeyCharacterMap::FORMAT_ANY, &map);
+ if (status) {
+ fprintf(stderr, "Error %d parsing key character map file.\n\n", status);
+ return false;
+ }
+ break;
+ }
+
+ case FILETYPE_INPUTDEVICECONFIGURATION: {
+ PropertyMap* map;
+ status_t status = PropertyMap::load(String8(filename), &map);
+ if (status) {
+ fprintf(stderr, "Error %d parsing input device configuration file.\n\n", status);
+ return false;
+ }
+ delete map;
+ break;
+ }
+
+ case FILETYPE_VIRTUALKEYDEFINITION: {
+ VirtualKeyMap* map;
+ status_t status = VirtualKeyMap::load(String8(filename), &map);
+ if (status) {
+ fprintf(stderr, "Error %d parsing virtual key definition file.\n\n", status);
+ return false;
+ }
+ delete map;
+ break;
+ }
+ }
+
+ fputs("No errors.\n\n", stdout);
+ return true;
+}
+
+int main(int argc, const char** argv) {
+ if (argc < 2) {
+ usage();
+ return 1;
+ }
+
+ int result = 0;
+ for (int i = 1; i < argc; i++) {
+ if (!validateFile(argv[i])) {
+ result = 1;
+ }
+ }
+
+ if (result) {
+ fputs("Failed!\n", stderr);
+ } else {
+ fputs("Success.\n", stdout);
+ }
+ return result;
+}
diff --git a/tools/velocityplot/velocityplot.py b/tools/velocityplot/velocityplot.py
new file mode 100755
index 0000000..421bed4
--- /dev/null
+++ b/tools/velocityplot/velocityplot.py
@@ -0,0 +1,289 @@
+#!/usr/bin/env python2.6
+#
+# Copyright (C) 2011 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.
+#
+
+#
+# Plots debug log output from VelocityTracker.
+# Enable DEBUG_VELOCITY to print the output.
+#
+# This code supports side-by-side comparison of two algorithms.
+# The old algorithm should be modified to emit debug log messages containing
+# the word "OLD".
+#
+
+import numpy as np
+import matplotlib.pyplot as plot
+import subprocess
+import re
+import fcntl
+import os
+import errno
+import bisect
+from datetime import datetime, timedelta
+
+# Parameters.
+timespan = 15 # seconds total span shown
+scrolljump = 5 # seconds jump when scrolling
+timeticks = 1 # seconds between each time tick
+
+# Non-blocking stream wrapper.
+class NonBlockingStream:
+ def __init__(self, stream):
+ fcntl.fcntl(stream, fcntl.F_SETFL, os.O_NONBLOCK)
+ self.stream = stream
+ self.buffer = ''
+ self.pos = 0
+
+ def readline(self):
+ while True:
+ index = self.buffer.find('\n', self.pos)
+ if index != -1:
+ result = self.buffer[self.pos:index]
+ self.pos = index + 1
+ return result
+
+ self.buffer = self.buffer[self.pos:]
+ self.pos = 0
+ try:
+ chunk = os.read(self.stream.fileno(), 4096)
+ except OSError, e:
+ if e.errno == errno.EAGAIN:
+ return None
+ raise e
+ if len(chunk) == 0:
+ if len(self.buffer) == 0:
+ raise(EOFError)
+ else:
+ result = self.buffer
+ self.buffer = ''
+ self.pos = 0
+ return result
+ self.buffer += chunk
+
+# Plotter
+class Plotter:
+ def __init__(self, adbout):
+ self.adbout = adbout
+
+ self.fig = plot.figure(1)
+ self.fig.suptitle('Velocity Tracker', fontsize=12)
+ self.fig.set_dpi(96)
+ self.fig.set_size_inches(16, 12, forward=True)
+
+ self.velocity_x = self._make_timeseries()
+ self.velocity_y = self._make_timeseries()
+ self.velocity_magnitude = self._make_timeseries()
+ self.velocity_axes = self._add_timeseries_axes(
+ 1, 'Velocity', 'px/s', [-5000, 5000],
+ yticks=range(-5000, 5000, 1000))
+ self.velocity_line_x = self._add_timeseries_line(
+ self.velocity_axes, 'vx', 'red')
+ self.velocity_line_y = self._add_timeseries_line(
+ self.velocity_axes, 'vy', 'green')
+ self.velocity_line_magnitude = self._add_timeseries_line(
+ self.velocity_axes, 'magnitude', 'blue')
+ self._add_timeseries_legend(self.velocity_axes)
+
+ shared_axis = self.velocity_axes
+
+ self.old_velocity_x = self._make_timeseries()
+ self.old_velocity_y = self._make_timeseries()
+ self.old_velocity_magnitude = self._make_timeseries()
+ self.old_velocity_axes = self._add_timeseries_axes(
+ 2, 'Old Algorithm Velocity', 'px/s', [-5000, 5000],
+ sharex=shared_axis,
+ yticks=range(-5000, 5000, 1000))
+ self.old_velocity_line_x = self._add_timeseries_line(
+ self.old_velocity_axes, 'vx', 'red')
+ self.old_velocity_line_y = self._add_timeseries_line(
+ self.old_velocity_axes, 'vy', 'green')
+ self.old_velocity_line_magnitude = self._add_timeseries_line(
+ self.old_velocity_axes, 'magnitude', 'blue')
+ self._add_timeseries_legend(self.old_velocity_axes)
+
+ self.timer = self.fig.canvas.new_timer(interval=100)
+ self.timer.add_callback(lambda: self.update())
+ self.timer.start()
+
+ self.timebase = None
+ self._reset_parse_state()
+
+ # Initialize a time series.
+ def _make_timeseries(self):
+ return [[], []]
+
+ # Add a subplot to the figure for a time series.
+ def _add_timeseries_axes(self, index, title, ylabel, ylim, yticks, sharex=None):
+ num_graphs = 2
+ height = 0.9 / num_graphs
+ top = 0.95 - height * index
+ axes = self.fig.add_axes([0.1, top, 0.8, height],
+ xscale='linear',
+ xlim=[0, timespan],
+ ylabel=ylabel,
+ yscale='linear',
+ ylim=ylim,
+ sharex=sharex)
+ axes.text(0.02, 0.02, title, transform=axes.transAxes, fontsize=10, fontweight='bold')
+ axes.set_xlabel('time (s)', fontsize=10, fontweight='bold')
+ axes.set_ylabel(ylabel, fontsize=10, fontweight='bold')
+ axes.set_xticks(range(0, timespan + 1, timeticks))
+ axes.set_yticks(yticks)
+ axes.grid(True)
+
+ for label in axes.get_xticklabels():
+ label.set_fontsize(9)
+ for label in axes.get_yticklabels():
+ label.set_fontsize(9)
+
+ return axes
+
+ # Add a line to the axes for a time series.
+ def _add_timeseries_line(self, axes, label, color, linewidth=1):
+ return axes.plot([], label=label, color=color, linewidth=linewidth)[0]
+
+ # Add a legend to a time series.
+ def _add_timeseries_legend(self, axes):
+ axes.legend(
+ loc='upper left',
+ bbox_to_anchor=(1.01, 1),
+ borderpad=0.1,
+ borderaxespad=0.1,
+ prop={'size': 10})
+
+ # Resets the parse state.
+ def _reset_parse_state(self):
+ self.parse_velocity_x = None
+ self.parse_velocity_y = None
+ self.parse_velocity_magnitude = None
+ self.parse_old_velocity_x = None
+ self.parse_old_velocity_y = None
+ self.parse_old_velocity_magnitude = None
+
+ # Update samples.
+ def update(self):
+ timeindex = 0
+ while True:
+ try:
+ line = self.adbout.readline()
+ except EOFError:
+ plot.close()
+ return
+ if line is None:
+ break
+ print line
+
+ try:
+ timestamp = self._parse_timestamp(line)
+ except ValueError, e:
+ continue
+ if self.timebase is None:
+ self.timebase = timestamp
+ delta = timestamp - self.timebase
+ timeindex = delta.seconds + delta.microseconds * 0.000001
+
+ if line.find(': position') != -1:
+ self.parse_velocity_x = self._get_following_number(line, 'vx=')
+ self.parse_velocity_y = self._get_following_number(line, 'vy=')
+ self.parse_velocity_magnitude = self._get_following_number(line, 'speed=')
+ self._append(self.velocity_x, timeindex, self.parse_velocity_x)
+ self._append(self.velocity_y, timeindex, self.parse_velocity_y)
+ self._append(self.velocity_magnitude, timeindex, self.parse_velocity_magnitude)
+
+ if line.find(': OLD') != -1:
+ self.parse_old_velocity_x = self._get_following_number(line, 'vx=')
+ self.parse_old_velocity_y = self._get_following_number(line, 'vy=')
+ self.parse_old_velocity_magnitude = self._get_following_number(line, 'speed=')
+ self._append(self.old_velocity_x, timeindex, self.parse_old_velocity_x)
+ self._append(self.old_velocity_y, timeindex, self.parse_old_velocity_y)
+ self._append(self.old_velocity_magnitude, timeindex, self.parse_old_velocity_magnitude)
+
+ # Scroll the plots.
+ if timeindex > timespan:
+ bottom = int(timeindex) - timespan + scrolljump
+ self.timebase += timedelta(seconds=bottom)
+ self._scroll(self.velocity_x, bottom)
+ self._scroll(self.velocity_y, bottom)
+ self._scroll(self.velocity_magnitude, bottom)
+ self._scroll(self.old_velocity_x, bottom)
+ self._scroll(self.old_velocity_y, bottom)
+ self._scroll(self.old_velocity_magnitude, bottom)
+
+ # Redraw the plots.
+ self.velocity_line_x.set_data(self.velocity_x)
+ self.velocity_line_y.set_data(self.velocity_y)
+ self.velocity_line_magnitude.set_data(self.velocity_magnitude)
+ self.old_velocity_line_x.set_data(self.old_velocity_x)
+ self.old_velocity_line_y.set_data(self.old_velocity_y)
+ self.old_velocity_line_magnitude.set_data(self.old_velocity_magnitude)
+
+ self.fig.canvas.draw_idle()
+
+ # Scroll a time series.
+ def _scroll(self, timeseries, bottom):
+ bottom_index = bisect.bisect_left(timeseries[0], bottom)
+ del timeseries[0][:bottom_index]
+ del timeseries[1][:bottom_index]
+ for i, timeindex in enumerate(timeseries[0]):
+ timeseries[0][i] = timeindex - bottom
+
+ # Extract a word following the specified prefix.
+ def _get_following_word(self, line, prefix):
+ prefix_index = line.find(prefix)
+ if prefix_index == -1:
+ return None
+ start_index = prefix_index + len(prefix)
+ delim_index = line.find(',', start_index)
+ if delim_index == -1:
+ return line[start_index:]
+ else:
+ return line[start_index:delim_index]
+
+ # Extract a number following the specified prefix.
+ def _get_following_number(self, line, prefix):
+ word = self._get_following_word(line, prefix)
+ if word is None:
+ return None
+ return float(word)
+
+ # Add a value to a time series.
+ def _append(self, timeseries, timeindex, number):
+ timeseries[0].append(timeindex)
+ timeseries[1].append(number)
+
+ # Parse the logcat timestamp.
+ # Timestamp has the form '01-21 20:42:42.930'
+ def _parse_timestamp(self, line):
+ return datetime.strptime(line[0:18], '%m-%d %H:%M:%S.%f')
+
+# Notice
+print "Velocity Tracker plotting tool"
+print "-----------------------------------------\n"
+print "Please enable debug logging and recompile the code."
+
+# Start adb.
+print "Starting adb logcat.\n"
+
+adb = subprocess.Popen(['adb', 'logcat', '-s', '-v', 'time', 'Input:*', 'VelocityTracker:*'],
+ stdout=subprocess.PIPE)
+adbout = NonBlockingStream(adb.stdout)
+
+# Prepare plotter.
+plotter = Plotter(adbout)
+plotter.update()
+
+# Main loop.
+plot.show()