bin/adb tools/adb
bin/sqlite3 tools/sqlite3
bin/dmtracedump tools/dmtracedump
+bin/etc1tool tools/etc1tool
bin/hprof-conv tools/hprof-conv
bin/mksdcard tools/mksdcard
bin/zipalign tools/zipalign
sdk/files/android.el tools/lib/android.el
# samples
-development/apps/GestureBuilder platforms/${PLATFORM_NAME}/samples/GestureBuilder
-development/samples/BluetoothChat platforms/${PLATFORM_NAME}/samples/BluetoothChat
-development/samples/Home platforms/${PLATFORM_NAME}/samples/Home
-development/samples/LunarLander platforms/${PLATFORM_NAME}/samples/LunarLander
-development/samples/NotePad platforms/${PLATFORM_NAME}/samples/NotePad
-development/samples/ApiDemos platforms/${PLATFORM_NAME}/samples/ApiDemos
-development/samples/SkeletonApp platforms/${PLATFORM_NAME}/samples/SkeletonApp
-development/samples/Snake platforms/${PLATFORM_NAME}/samples/Snake
-development/samples/SoftKeyboard platforms/${PLATFORM_NAME}/samples/SoftKeyboard
-development/samples/JetBoy platforms/${PLATFORM_NAME}/samples/JetBoy
-development/samples/SearchableDictionary platforms/${PLATFORM_NAME}/samples/SearchableDictionary
-development/samples/ContactManager platforms/${PLATFORM_NAME}/samples/ContactManager
-development/samples/MultiResolution platforms/${PLATFORM_NAME}/samples/MultiResolution
-development/samples/Wiktionary platforms/${PLATFORM_NAME}/samples/Wiktionary
-development/samples/WiktionarySimple platforms/${PLATFORM_NAME}/samples/WiktionarySimple
-development/samples/CubeLiveWallpaper platforms/${PLATFORM_NAME}/samples/CubeLiveWallpaper
+development/samples/source.properties samples/${PLATFORM_NAME}/source.properties
+development/apps/GestureBuilder samples/${PLATFORM_NAME}/GestureBuilder
+development/samples/BluetoothChat samples/${PLATFORM_NAME}/BluetoothChat
+development/samples/Home samples/${PLATFORM_NAME}/Home
+development/samples/LunarLander samples/${PLATFORM_NAME}/LunarLander
+development/samples/NotePad samples/${PLATFORM_NAME}/NotePad
+development/samples/ApiDemos samples/${PLATFORM_NAME}/ApiDemos
+development/samples/SkeletonApp samples/${PLATFORM_NAME}/SkeletonApp
+development/samples/Snake samples/${PLATFORM_NAME}/Snake
+development/samples/SoftKeyboard samples/${PLATFORM_NAME}/SoftKeyboard
+development/samples/JetBoy samples/${PLATFORM_NAME}/JetBoy
+development/samples/SearchableDictionary samples/${PLATFORM_NAME}/SearchableDictionary
+development/samples/ContactManager samples/${PLATFORM_NAME}/ContactManager
+development/samples/MultiResolution samples/${PLATFORM_NAME}/MultiResolution
+development/samples/Wiktionary samples/${PLATFORM_NAME}/Wiktionary
+development/samples/WiktionarySimple samples/${PLATFORM_NAME}/WiktionarySimple
+development/samples/CubeLiveWallpaper samples/${PLATFORM_NAME}/CubeLiveWallpaper
# dx
bin/dx platforms/${PLATFORM_NAME}/tools/dx
# NOTICE files are copied by build/core/Makefile from sdk.git
sdk/files/sdk_files_NOTICE.txt platforms/${PLATFORM_NAME}/templates/NOTICE.txt
-sdk/files/sdk_files_NOTICE.txt platforms/${PLATFORM_NAME}/samples/NOTICE.txt
sdk/files/sdk_files_NOTICE.txt platforms/${PLATFORM_NAME}/data/NOTICE.txt
sdk/files/sdk_files_NOTICE.txt platforms/${PLATFORM_NAME}/skins/NOTICE.txt
sdk/files/sdk_files_NOTICE.txt platforms/${PLATFORM_NAME}/tools/NOTICE.txt
+sdk/files/sdk_files_NOTICE.txt samples/${PLATFORM_NAME}/NOTICE.txt
# the readme
development/docs/SDK_RELEASE_NOTES RELEASE_NOTES.html
function build() {
+ # IMPORTANT: For Cygwin to be able to build Android targets here,
+ # you will generally need to edit build/core/main.mk and add directories
+ # where Android.mk makefiles are to be found to the SDK_ONLY==true section.
+
echo
echo "Building..."
[ -n "$MAKE_OPT" ] && echo "Make options: $MAKE_OPT"
+
. build/envsetup.sh
- make -j 4 emulator || die "Build failed"
+
# Disable parallel build: it generates "permission denied" issues when
# multiple "ar.exe" are running in parallel.
- make aapt adb aidl \
+ make \
+ aapt adb aidl \
+ etc1tool \
prebuilt \
dexdump dmtracedump \
fastboot \
sdklauncher sqlite3 \
zipalign \
|| die "Build failed"
+
+ # It's worth building the emulator with -j 4 so do it separately
+ make -j 4 emulator || die "Build failed"
}
function package() {
# Remove obsolete stuff from tools & platform
TOOLS="$TEMP_SDK_DIR/tools"
LIB="$TEMP_SDK_DIR/tools/lib"
- rm -v "$TOOLS"/{adb,android,apkbuilder,ddms,dmtracedump,draw9patch,emulator}
+ rm -v "$TOOLS"/{adb,android,apkbuilder,ddms,dmtracedump,draw9patch,emulator,etc1tool}
rm -v "$TOOLS"/{hierarchyviewer,hprof-conv,layoutopt,mksdcard,sqlite3,traceview,zipalign}
rm -v "$LIB"/*/swt.jar
rm -v "$PLATFORM_TOOLS"/{aapt,aidl,dx,dexdump}
-/**
-** Copyright 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.
-*/
-
+/*
+ * Copyright 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 com.android.commands.monkey;
/**
* Monkey Debugging/Dev Support
- *
+ * <p>
* All values should be zero when checking in.
*/
private final static int DEBUG_ALLOW_ANY_STARTS = 0;
+
private final static int DEBUG_ALLOW_ANY_RESTARTS = 0;
+
private IActivityManager mAm;
+
private IWindowManager mWm;
+
private IPackageManager mPm;
/** Command line arguments */
private String[] mArgs;
+
/** Current argument being parsed */
private int mNextArg;
+
/** Data of current argument */
private String mCurArgData;
/** Monitor /data/tombstones and stop the monkey if new files appear. */
private boolean mMonitorNativeCrashes;
- /** Send no events. Use with long throttle-time to watch user operations */
+ /** Send no events. Use with long throttle-time to watch user operations */
private boolean mSendNoEvents;
/** This is set when we would like to abort the running of the monkey. */
private boolean mAbort;
- /** This is set by the ActivityController thread to request collection of ANR trace files */
+ /**
+ * Count each event as a cycle. Set to false for scripts so that each time
+ * through the script increments the count.
+ */
+ private boolean mCountEvents = true;
+
+ /**
+ * This is set by the ActivityController thread to request collection of ANR
+ * trace files
+ */
private boolean mRequestAnrTraces = false;
- /** This is set by the ActivityController thread to request a "dumpsys meminfo" */
+ /**
+ * This is set by the ActivityController thread to request a
+ * "dumpsys meminfo"
+ */
private boolean mRequestDumpsysMemInfo = false;
/** Kill the process after a timeout or crash. */
/** Packages we are allowed to run, or empty if no restriction. */
private HashSet<String> mValidPackages = new HashSet<String>();
+
/** Categories we are allowed to launch **/
- ArrayList<String> mMainCategories = new ArrayList<String>();
+ private ArrayList<String> mMainCategories = new ArrayList<String>();
+
/** Applications we can switch to. */
private ArrayList<ComponentName> mMainApps = new ArrayList<ComponentName>();
/** Dropped-event statistics **/
long mDroppedKeyEvents = 0;
+
long mDroppedPointerEvents = 0;
+
long mDroppedTrackballEvents = 0;
+
long mDroppedFlipEvents = 0;
- /** a filename to the script (if any) **/
- private String mScriptFileName = null;
+ /** a filename to the setup script (if any) */
+ private String mSetupFileName = null;
+
+ /** filenames of the script (if any) */
+ private ArrayList<String> mScriptFileNames = new ArrayList<String>();
/** a TCP port to listen on for remote commands. */
private int mServerPort = -1;
private static final File TOMBSTONES_PATH = new File("/data/tombstones");
+
private HashSet<String> mTombstones = null;
float[] mFactors = new float[MonkeySourceRandom.FACTORZ_COUNT];
+
MonkeyEventSource mEventSource;
+
private MonkeyNetworkMonitor mNetworkMonitor = new MonkeyNetworkMonitor();
// information on the current activity.
public static Intent currentIntent;
+
public static String currentPackage;
/**
public boolean activityStarting(Intent intent, String pkg) {
boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_STARTS != 0);
if (mVerbose > 0) {
- System.out.println(" // " + (allow ? "Allowing" : "Rejecting")
- + " start of " + intent + " in package " + pkg);
+ System.out.println(" // " + (allow ? "Allowing" : "Rejecting") + " start of "
+ + intent + " in package " + pkg);
}
currentPackage = pkg;
currentIntent = intent;
if (!allow) {
if (mVerbose > 0) {
System.out.println(" // " + (allow ? "Allowing" : "Rejecting")
- + " resume of package " + pkg);
+ + " resume of package " + pkg);
}
}
currentPackage = pkg;
if (pkg == null) {
return true;
}
- // preflight the hash lookup to avoid the cost of hash key generation
+ // preflight the hash lookup to avoid the cost of hash key
+ // generation
if (mValidPackages.size() == 0) {
return true;
} else {
}
}
- public boolean appCrashed(String processName, int pid, String shortMsg,
- String longMsg, byte[] crashData) {
- System.err.println("// CRASH: " + processName + " (pid " + pid
- + ")");
+ public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
+ byte[] crashData) {
+ System.err.println("// CRASH: " + processName + " (pid " + pid + ")");
System.err.println("// Short Msg: " + shortMsg);
System.err.println("// Long Msg: " + longMsg);
if (crashData != null) {
try {
- CrashData cd = new CrashData(new DataInputStream(
- new ByteArrayInputStream(crashData)));
- System.err.println("// Build Label: "
- + cd.getBuildData().getFingerprint());
+ CrashData cd = new CrashData(new DataInputStream(new ByteArrayInputStream(
+ crashData)));
+ System.err.println("// Build Label: " + cd.getBuildData().getFingerprint());
System.err.println("// Build Changelist: "
+ cd.getBuildData().getIncrementalVersion());
- System.err.println("// Build Time: "
- + cd.getBuildData().getTime());
+ System.err.println("// Build Time: " + cd.getBuildData().getTime());
System.err.println("// ID: " + cd.getId());
System.err.println("// Tag: " + cd.getActivity());
- System.err.println(cd.getThrowableData().toString(
- "// "));
+ System.err.println(cd.getThrowableData().toString("// "));
} catch (IOException e) {
System.err.println("// BAD STACK CRAWL");
}
return false;
}
- public int appNotResponding(String processName, int pid,
- String processStats) {
- System.err.println("// NOT RESPONDING: " + processName
- + " (pid " + pid + ")");
+ public int appNotResponding(String processName, int pid, String processStats) {
+ System.err.println("// NOT RESPONDING: " + processName + " (pid " + pid + ")");
System.err.println(processStats);
reportProcRank();
synchronized (Monkey.this) {
}
/**
- * Run the procrank tool to insert system status information into the debug report.
+ * Run the procrank tool to insert system status information into the debug
+ * report.
*/
private void reportProcRank() {
- commandLineReport("procrank", "procrank");
+ commandLineReport("procrank", "procrank");
}
/**
- * Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the asynchronous
- * report writing complete.
+ * Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the
+ * asynchronous report writing complete.
*/
private void reportAnrTraces() {
try {
/**
* Run "dumpsys meminfo"
- *
- * NOTE: You cannot perform a dumpsys call from the ActivityController callback, as it will
- * deadlock. This should only be called from the main loop of the monkey.
+ * <p>
+ * NOTE: You cannot perform a dumpsys call from the ActivityController
+ * callback, as it will deadlock. This should only be called from the main
+ * loop of the monkey.
*/
private void reportDumpsysMemInfo() {
commandLineReport("meminfo", "dumpsys meminfo");
/**
* Print report from a single command line.
- * @param reportName Simple tag that will print before the report and in various annotations.
+ * <p>
+ * TODO: Use ProcessBuilder & redirectErrorStream(true) to capture both
+ * streams (might be important for some command lines)
+ *
+ * @param reportName Simple tag that will print before the report and in
+ * various annotations.
* @param command Command line to execute.
- * TODO: Use ProcessBuilder & redirectErrorStream(true) to capture both streams (might be
- * important for some command lines)
*/
private void commandLineReport(String reportName, String command) {
System.err.println(reportName + ":");
Runtime rt = Runtime.getRuntime();
try {
- // Process must be fully qualified here because android.os.Process is used elsewhere
+ // Process must be fully qualified here because android.os.Process
+ // is used elsewhere
java.lang.Process p = Runtime.getRuntime().exec(command);
// pipe everything from process stdout -> System.err
* Run the command!
*
* @param args The command-line arguments
- * @return Returns a posix-style result code. 0 for no error.
+ * @return Returns a posix-style result code. 0 for no error.
*/
private int run(String[] args) {
// Super-early debugger wait
mArgs = args;
mNextArg = 0;
- //set a positive value, indicating none of the factors is provided yet
+ // set a positive value, indicating none of the factors is provided yet
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
mFactors[i] = 1.0f;
}
return -4;
}
- if (mScriptFileName != null) {
+ if (mScriptFileNames != null && mScriptFileNames.size() == 1) {
// script mode, ignore other options
- mEventSource = new MonkeySourceScript(mScriptFileName, mThrottle);
+ mEventSource = new MonkeySourceScript(mScriptFileNames.get(0), mThrottle);
mEventSource.setVerbose(mVerbose);
+
+ mCountEvents = false;
+ } else if (mScriptFileNames != null && mScriptFileNames.size() > 1) {
+ if (mSetupFileName != null) {
+ mEventSource = new MonkeySourceRandomScript(mSetupFileName, mScriptFileNames,
+ mThrottle, mSeed);
+ mCount++;
+ } else {
+ mEventSource = new MonkeySourceRandomScript(mScriptFileNames, mThrottle, mSeed);
+ }
+ mEventSource.setVerbose(mVerbose);
+ mCountEvents = false;
} else if (mServerPort != -1) {
try {
mEventSource = new MonkeySourceNetwork(mServerPort);
mCount = Integer.MAX_VALUE;
} else {
// random source by default
- if (mVerbose >= 2) { // check seeding performance
+ if (mVerbose >= 2) { // check seeding performance
System.out.println("// Seeded: " + mSeed);
}
mEventSource = new MonkeySourceRandom(mSeed, mMainApps, mThrottle);
mEventSource.setVerbose(mVerbose);
- //set any of the factors that has been set
+ // set any of the factors that has been set
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
if (mFactors[i] <= 0.0f) {
((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
}
}
- //in random mode, we start with a random activity
+ // in random mode, we start with a random activity
((MonkeySourceRandom) mEventSource).generateActivity();
}
- //validate source generator
+ // validate source generator
if (!mEventSource.validate()) {
return -5;
}
- if (mScriptFileName != null) {
- // in random mode, count is the number of single events
- // while in script mode, count is the number of repetition
- // for a sequence of events, so we need do multiply the length of
- // that sequence
- mCount = mCount * ((MonkeySourceScript) mEventSource)
- .getOneRoundEventCount();
- }
-
- // If we're profiling, do it immediately before/after the main monkey loop
+ // If we're profiling, do it immediately before/after the main monkey
+ // loop
if (mGenerateHprof) {
signalPersistentProcesses();
}
mNetworkMonitor.dump();
if (crashedAtCycle < mCount - 1) {
- System.err.println("** System appears to have crashed at event "
- + crashedAtCycle + " of " + mCount + " using seed " + mSeed);
- return crashedAtCycle;
+ System.err.println("** System appears to have crashed at event " + crashedAtCycle
+ + " of " + mCount + " using seed " + mSeed);
+ return crashedAtCycle;
} else {
if (mVerbose > 0) {
System.out.println("// Monkey finished");
} else if (opt.equals("--hprof")) {
mGenerateHprof = true;
} else if (opt.equals("--pct-touch")) {
- mFactors[MonkeySourceRandom.FACTOR_TOUCH] =
- -nextOptionLong("touch events percentage");
+ int i = MonkeySourceRandom.FACTOR_TOUCH;
+ mFactors[i] = -nextOptionLong("touch events percentage");
} else if (opt.equals("--pct-motion")) {
- mFactors[MonkeySourceRandom.FACTOR_MOTION] =
- -nextOptionLong("motion events percentage");
+ int i = MonkeySourceRandom.FACTOR_MOTION;
+ mFactors[i] = -nextOptionLong("motion events percentage");
} else if (opt.equals("--pct-trackball")) {
- mFactors[MonkeySourceRandom.FACTOR_TRACKBALL] =
- -nextOptionLong("trackball events percentage");
+ int i = MonkeySourceRandom.FACTOR_TRACKBALL;
+ mFactors[i] = -nextOptionLong("trackball events percentage");
} else if (opt.equals("--pct-nav")) {
- mFactors[MonkeySourceRandom.FACTOR_NAV] =
- -nextOptionLong("nav events percentage");
+ int i = MonkeySourceRandom.FACTOR_NAV;
+ mFactors[i] = -nextOptionLong("nav events percentage");
} else if (opt.equals("--pct-majornav")) {
- mFactors[MonkeySourceRandom.FACTOR_MAJORNAV] =
- -nextOptionLong("major nav events percentage");
+ int i = MonkeySourceRandom.FACTOR_MAJORNAV;
+ mFactors[i] = -nextOptionLong("major nav events percentage");
} else if (opt.equals("--pct-appswitch")) {
- mFactors[MonkeySourceRandom.FACTOR_APPSWITCH] =
- -nextOptionLong("app switch events percentage");
+ int i = MonkeySourceRandom.FACTOR_APPSWITCH;
+ mFactors[i] = -nextOptionLong("app switch events percentage");
} else if (opt.equals("--pct-flip")) {
- mFactors[MonkeySourceRandom.FACTOR_FLIP] =
- -nextOptionLong("keyboard flip percentage");
+ int i = MonkeySourceRandom.FACTOR_FLIP;
+ mFactors[i] = -nextOptionLong("keyboard flip percentage");
} else if (opt.equals("--pct-anyevent")) {
- mFactors[MonkeySourceRandom.FACTOR_ANYTHING] =
- -nextOptionLong("any events percentage");
+ int i = MonkeySourceRandom.FACTOR_ANYTHING;
+ mFactors[i] = -nextOptionLong("any events percentage");
} else if (opt.equals("--throttle")) {
mThrottle = nextOptionLong("delay (in milliseconds) to wait between events");
} else if (opt.equals("--wait-dbg")) {
mSendNoEvents = true;
} else if (opt.equals("--port")) {
mServerPort = (int) nextOptionLong("Server port to listen on for commands");
+ } else if (opt.equals("--setup")) {
+ mSetupFileName = nextOptionData();
} else if (opt.equals("-f")) {
- mScriptFileName = nextOptionData();
+ mScriptFileNames.add(nextOptionData());
} else if (opt.equals("-h")) {
showUsage();
return false;
private boolean getSystemInterfaces() {
mAm = ActivityManagerNative.getDefault();
if (mAm == null) {
- System.err.println("** Error: Unable to connect to activity manager; is the system running?");
+ System.err.println("** Error: Unable to connect to activity manager; is the system "
+ + "running?");
return false;
}
mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
if (mWm == null) {
- System.err.println("** Error: Unable to connect to window manager; is the system running?");
+ System.err.println("** Error: Unable to connect to window manager; is the system "
+ + "running?");
return false;
}
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
if (mPm == null) {
- System.err.println("** Error: Unable to connect to package manager; is the system running?");
+ System.err.println("** Error: Unable to connect to package manager; is the system "
+ + "running?");
return false;
}
}
/**
- * Using the restrictions provided (categories & packages), generate a list of activities
- * that we can actually switch to.
+ * Using the restrictions provided (categories & packages), generate a list
+ * of activities that we can actually switch to.
*
- * @return Returns true if it could successfully build a list of target activities
+ * @return Returns true if it could successfully build a list of target
+ * activities
*/
private boolean getMainApps() {
try {
final int N = mMainCategories.size();
- for (int i = 0; i< N; i++) {
+ for (int i = 0; i < N; i++) {
Intent intent = new Intent(Intent.ACTION_MAIN);
String category = mMainCategories.get(i);
if (category.length() > 0) {
System.err.println("// Warning: no activities found for category " + category);
continue;
}
- if (mVerbose >= 2) { // very verbose
+ if (mVerbose >= 2) { // very verbose
System.out.println("// Selecting main activities from category " + category);
}
final int NA = mainApps.size();
for (int a = 0; a < NA; a++) {
ResolveInfo r = mainApps.get(a);
- if (mValidPackages.size() == 0 ||
- mValidPackages.contains(r.activityInfo.applicationInfo.packageName)) {
- if (mVerbose >= 2) { // very verbose
- System.out.println("// + Using main activity "
- + r.activityInfo.name
+ if (mValidPackages.size() == 0
+ || mValidPackages.contains(r.activityInfo.applicationInfo.packageName)) {
+ if (mVerbose >= 2) { // very verbose
+ System.out.println("// + Using main activity " + r.activityInfo.name
+ " (from package "
- + r.activityInfo.applicationInfo.packageName
- + ")");
+ + r.activityInfo.applicationInfo.packageName + ")");
}
- mMainApps.add(new ComponentName(
- r.activityInfo.applicationInfo.packageName,
+ mMainApps.add(new ComponentName(r.activityInfo.applicationInfo.packageName,
r.activityInfo.name));
} else {
- if (mVerbose >= 3) { // very very verbose
+ if (mVerbose >= 3) { // very very verbose
System.out.println("// - NOT USING main activity "
- + r.activityInfo.name
- + " (from package "
- + r.activityInfo.applicationInfo.packageName
- + ")");
+ + r.activityInfo.name + " (from package "
+ + r.activityInfo.applicationInfo.packageName + ")");
}
}
}
/**
* Run mCount cycles and see if we hit any crashers.
- *
+ * <p>
* TODO: Meta state on keys
*
- * @return Returns the last cycle which executed. If the value == mCount, no errors detected.
+ * @return Returns the last cycle which executed. If the value == mCount, no
+ * errors detected.
*/
private int runMonkeyCycles() {
- int i = 0;
- int lastKey = 0;
+
+ int eventCounter = 0;
+ int cycleCounter = 0;
boolean systemCrashed = false;
- while (!systemCrashed && i < mCount) {
+ while (!systemCrashed && cycleCounter < mCount) {
synchronized (this) {
if (mRequestAnrTraces) {
reportAnrTraces();
mRequestDumpsysMemInfo = false;
}
if (mMonitorNativeCrashes) {
- // first time through, when i == 0, just set up the watcher (ignore the error)
- if (checkNativeCrashes() && (i > 0)) {
+ // first time through, when eventCounter == 0, just set up
+ // the watcher (ignore the error)
+ if (checkNativeCrashes() && (eventCounter > 0)) {
System.out.println("** New native crash detected.");
mAbort = mAbort || mKillProcessAfterError;
}
}
if (mAbort) {
System.out.println("** Monkey aborted due to error.");
- System.out.println("Events injected: " + i);
- return i;
+ System.out.println("Events injected: " + eventCounter);
+ return eventCounter;
}
}
- // In this debugging mode, we never send any events. This is primarily
- // here so you can manually test the package or category limits, while manually
- // exercising the system.
+ // In this debugging mode, we never send any events. This is
+ // primarily here so you can manually test the package or category
+ // limits, while manually exercising the system.
if (mSendNoEvents) {
- i++;
+ eventCounter++;
+ cycleCounter++;
continue;
}
- if ((mVerbose > 0) && (i % 100) == 0 && i != 0 && lastKey == 0) {
- System.out.println(" // Sending event #" + i);
+ if ((mVerbose > 0) && (eventCounter % 100) == 0 && eventCounter != 0) {
+ System.out.println(" // Sending event #" + eventCounter);
}
MonkeyEvent ev = mEventSource.getNextEvent();
if (ev != null) {
- // We don't want to count throttling as an event.
- if (!(ev instanceof MonkeyThrottleEvent)) {
- i++;
- }
+
int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
if (injectCode == MonkeyEvent.INJECT_FAIL) {
if (ev instanceof MonkeyKeyEvent) {
}
} else if (injectCode == MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION) {
systemCrashed = true;
+ System.err.println("** Error: RemoteException while injecting event.");
} else if (injectCode == MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION) {
systemCrashed = !mIgnoreSecurityExceptions;
+ if (systemCrashed) {
+ System.err.println("** Error: SecurityException while injecting event.");
+ }
+ }
+
+ // Don't count throttling as an event.
+ if (!(ev instanceof MonkeyThrottleEvent)) {
+ eventCounter++;
+ if (mCountEvents) {
+ cycleCounter++;
+ }
}
} else {
- // Event Source has signaled that we have no more events to process
- break;
+ if (!mCountEvents) {
+ cycleCounter++;
+ } else {
+ System.err.println("** Error: Event source exhausted.");
+ break;
+ }
}
}
+
// If we got this far, we succeeded!
- return mCount;
+ System.out.println("Events injected: " + eventCounter);
+ return eventCounter;
}
/**
- * Send SIGNAL_USR1 to all processes. This will generate large (5mb) profiling reports
- * in data/misc, so use with care.
+ * Send SIGNAL_USR1 to all processes. This will generate large (5mb)
+ * profiling reports in data/misc, so use with care.
*/
private void signalPersistentProcesses() {
try {
}
/**
- * Watch for appearance of new tombstone files, which indicate native crashes.
+ * Watch for appearance of new tombstone files, which indicate native
+ * crashes.
*
* @return Returns true if new files have appeared in the list
*/
private boolean checkNativeCrashes() {
String[] tombstones = TOMBSTONES_PATH.list();
- // shortcut path for usually empty directory, so we don't waste even more objects
+ // shortcut path for usually empty directory, so we don't waste even
+ // more objects
if ((tombstones == null) || (tombstones.length == 0)) {
mTombstones = null;
return false;
}
/**
- * Return the next command line option. This has a number of special cases which
- * closely, but not exactly, follow the POSIX command line options patterns:
+ * Return the next command line option. This has a number of special cases
+ * which closely, but not exactly, follow the POSIX command line options
+ * patterns:
*
+ * <pre>
* -- means to stop processing additional options
* -z means option z
* -z ARGS means option z with (non-optional) arguments ARGS
* -zARGS means option z with (optional) arguments ARGS
* --zz means option zz
* --zz ARGS means option zz with (non-optional) arguments ARGS
+ * </pre>
*
- * Note that you cannot combine single letter options; -abc != -a -b -c
+ * Note that you cannot combine single letter options; -abc != -a -b -c
*
* @return Returns the option string, or null if there are no more options.
*/
}
/**
- * Returns a long converted from the next data argument, with error handling if not available.
+ * Returns a long converted from the next data argument, with error handling
+ * if not available.
*
* @param opt The name of the option.
* @return Returns a long converted from the argument.
* Print how to use this command.
*/
private void showUsage() {
- System.err.println("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]");
- System.err.println(" [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]");
- System.err.println(" [--ignore-crashes] [--ignore-timeouts]");
- System.err.println(" [--ignore-security-exceptions] [--monitor-native-crashes]");
- System.err.println(" [--kill-process-after-error] [--hprof]");
- System.err.println(" [--pct-touch PERCENT] [--pct-motion PERCENT]");
- System.err.println(" [--pct-trackball PERCENT] [--pct-syskeys PERCENT]");
- System.err.println(" [--pct-nav PERCENT] [--pct-majornav PERCENT]");
- System.err.println(" [--pct-appswitch PERCENT] [--pct-flip PERCENT]");
- System.err.println(" [--pct-anyevent PERCENT]");
- System.err.println(" [--wait-dbg] [--dbg-no-events] [-f scriptfile]");
- System.err.println(" [--port port]");
- System.err.println(" [-s SEED] [-v [-v] ...] [--throttle MILLISEC]");
- System.err.println(" COUNT");
- }
+ StringBuffer usage = new StringBuffer();
+ usage.append("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]\n");
+ usage.append(" [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]\n");
+ usage.append(" [--ignore-crashes] [--ignore-timeouts]\n");
+ usage.append(" [--ignore-security-exceptions] [--monitor-native-crashes]\n");
+ usage.append(" [--kill-process-after-error] [--hprof]\n");
+ usage.append(" [--pct-touch PERCENT] [--pct-motion PERCENT]\n");
+ usage.append(" [--pct-trackball PERCENT] [--pct-syskeys PERCENT]\n");
+ usage.append(" [--pct-nav PERCENT] [--pct-majornav PERCENT]\n");
+ usage.append(" [--pct-appswitch PERCENT] [--pct-flip PERCENT]\n");
+ usage.append(" [--pct-anyevent PERCENT]\n");
+ usage.append(" [--wait-dbg] [--dbg-no-events]\n");
+ usage.append(" [--setup scriptfile] [-f scriptfile [-f scriptfile] ...]\n");
+ usage.append(" [--port port]\n");
+ usage.append(" [-s SEED] [-v [-v] ...] [--throttle MILLISEC]\n");
+ usage.append(" COUNT");
+ System.err.println(usage.toString());
+ }
}
/**
* event source interface
*/
-public interface MonkeyEventSource {
+public interface MonkeyEventSource {
/**
- *
* @return the next monkey event from the source
*/
public MonkeyEvent getNextEvent();
-
+
/**
* set verbose to allow different level of log
+ *
* @param verbose output mode? 1= verbose, 2=very verbose
*/
public void setVerbose(int verbose);
-
+
/**
* check whether precondition is satisfied
- * @return false if something fails, e.g. factor failure in random source
- * or file can not open from script source etc
+ *
+ * @return false if something fails, e.g. factor failure in random source or
+ * file can not open from script source etc
*/
public boolean validate();
}
/**
* set the factors
*
- * @param factors: percentages for each type of event
+ * @param factors percentages for each type of event
*/
public void setFactors(float factors[]) {
int c = FACTORZ_COUNT;
*/
public MonkeyEvent getNextEvent() {
if (mQ.isEmpty()) {
- generateEvents();
+ generateEvents();
}
mEventCount++;
MonkeyEvent e = mQ.getFirst();
--- /dev/null
+/*
+ * 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.commands.monkey;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+
+/**
+ * Class for generating MonkeyEvents from multiple scripts.
+ */
+public class MonkeySourceRandomScript implements MonkeyEventSource {
+ /** The verbose level of the source (currently not used) */
+ private int mVerbose = 0;
+
+ /** The source for the setup script if it exists */
+ private MonkeySourceScript mSetupSource = null;
+
+ /** The list of MonkeySourceScript instances to be played in random order */
+ private ArrayList<MonkeySourceScript> mScriptSources = new ArrayList<MonkeySourceScript>();
+
+ /** The current source, set to the setup source and then a random script */
+ private MonkeySourceScript mCurrentSource = null;
+
+ /** The random number generator */
+ private SecureRandom mRandom;
+
+ /**
+ * Creates a MonkeySourceRandomScript instance with an additional setup script.
+ *
+ * @param setupFileName The name of the setup script file on the device.
+ * @param scriptFileNames An ArrayList of the names of the script files to be run randomly.
+ * @param throttle The amount of time to sleep in ms between events.
+ * @param seed The seed of the random number generator.
+ */
+ public MonkeySourceRandomScript(String setupFileName, ArrayList<String> scriptFileNames,
+ long throttle, long seed) {
+ if (setupFileName != null) {
+ mSetupSource = new MonkeySourceScript(setupFileName, throttle);
+ mCurrentSource = mSetupSource;
+ }
+
+ for (String fileName: scriptFileNames) {
+ mScriptSources.add(new MonkeySourceScript(fileName, throttle));
+ }
+
+ mRandom = new SecureRandom();
+ mRandom.setSeed((seed == 0) ? -1 : seed);
+ }
+
+ /**
+ * Creates a MonkeySourceRandomScript instance without an additional setup script.
+ *
+ * @param scriptFileNames An ArrayList of the names of the script files to be run randomly.
+ * @param throttle The amount of time to sleep in ms between events.
+ * @param seed The seed of the random number generator.
+ */
+ public MonkeySourceRandomScript(ArrayList<String> scriptFileNames, long throttle, long seed) {
+ this(null, scriptFileNames, throttle, seed);
+ }
+
+ /**
+ * Gets the next event from the current event source. If the event source is null, a new
+ * script event source is chosen randomly from the list of script sources and the next event is
+ * chosen from that.
+ *
+ * @return The first event in the event queue or null if the end of the file
+ * is reached or if an error is encountered reading the file.
+ */
+ public MonkeyEvent getNextEvent() {
+ if (mCurrentSource == null) {
+ int numSources = mScriptSources.size();
+ if (numSources == 1) {
+ mCurrentSource = mScriptSources.get(0);
+ } else if (numSources > 1) {
+ mCurrentSource = mScriptSources.get(mRandom.nextInt(numSources));
+ }
+ }
+
+ if (mCurrentSource != null) {
+ MonkeyEvent nextEvent = mCurrentSource.getNextEvent();
+ if (nextEvent == null) {
+ mCurrentSource = null;
+ }
+ return nextEvent;
+ }
+ return null;
+ }
+
+ /**
+ * Sets the verbosity for the source as well as all sub event sources.
+ *
+ * @param verbose The verbose level.
+ */
+ public void setVerbose(int verbose) {
+ mVerbose = verbose;
+
+ if (mSetupSource != null) {
+ mSetupSource.setVerbose(verbose);
+ }
+
+ for (MonkeySourceScript source: mScriptSources) {
+ source.setVerbose(verbose);
+ }
+ }
+
+ /**
+ * Validates that all the underlying event sources are valid
+ *
+ * @return True if all the script files are valid.
+ *
+ * @see MonkeySourceScript#validate()
+ */
+ public boolean validate() {
+ if (mSetupSource != null && !mSetupSource.validate()) {
+ return false;
+ }
+
+ for (MonkeySourceScript source: mScriptSources) {
+ if (!source.validate()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.util.LinkedList;
-import java.util.StringTokenizer;
+import java.util.NoSuchElementException;
-import android.view.KeyEvent;
/**
- * monkey event queue. It takes a script to produce events
- *
- * sample script format:
- * type= raw events
- * count= 10
- * speed= 1.0
- * start data >>
- * captureDispatchPointer(5109520,5109520,0,230.75429,458.1814,0.20784314,
- * 0.06666667,0,0.0,0.0,65539,0)
- * captureDispatchKey(5113146,5113146,0,20,0,0,0,0)
- * captureDispatchFlip(true)
- * ...
+ * monkey event queue. It takes a script to produce events sample script format:
+ *
+ * <pre>
+ * type= raw events
+ * count= 10
+ * speed= 1.0
+ * start data >>
+ * captureDispatchPointer(5109520,5109520,0,230.75429,458.1814,0.20784314,0.06666667,0,0.0,0.0,65539,0)
+ * captureDispatchKey(5113146,5113146,0,20,0,0,0,0)
+ * captureDispatchFlip(true)
+ * ...
+ * </pre>
*/
-public class MonkeySourceScript implements MonkeyEventSource {
- private int mEventCountInScript = 0; //total number of events in the file
+public class MonkeySourceScript implements MonkeyEventSource {
+ private int mEventCountInScript = 0; // total number of events in the file
+
private int mVerbose = 0;
+
private double mSpeed = 1.0;
- private String mScriptFileName;
+
+ private String mScriptFileName;
+
private MonkeyEventQueue mQ;
-
- private static final String HEADER_TYPE = "type=";
+
private static final String HEADER_COUNT = "count=";
+
private static final String HEADER_SPEED = "speed=";
- // New script type
- private static final String USER_EVENT_TYPE = "user";
-
- private long mLastRecordedDownTimeKey = 0;
+
+ private long mLastRecordedDownTimeKey = 0;
+
private long mLastRecordedDownTimeMotion = 0;
+
private long mLastExportDownTimeKey = 0;
+
private long mLastExportDownTimeMotion = 0;
+
private long mLastExportEventTime = -1;
+
private long mLastRecordedEventTime = -1;
- private String mScriptType = USER_EVENT_TYPE;
-
+
private static final boolean THIS_DEBUG = false;
- // a parameter that compensates the difference of real elapsed time and
+
+ // a parameter that compensates the difference of real elapsed time and
// time in theory
- private static final long SLEEP_COMPENSATE_DIFF = 16;
-
+ private static final long SLEEP_COMPENSATE_DIFF = 16;
+
// maximum number of events that we read at one time
private static final int MAX_ONE_TIME_READS = 100;
-
- // number of additional events added to the script
- // add HOME_KEY down and up events to make start UI consistent in each round
- private static final int POLICY_ADDITIONAL_EVENT_COUNT = 2;
- // event key word in the capture log
+ // event key word in the capture log
private static final String EVENT_KEYWORD_POINTER = "DispatchPointer";
+
private static final String EVENT_KEYWORD_TRACKBALL = "DispatchTrackball";
+
private static final String EVENT_KEYWORD_KEY = "DispatchKey";
+
private static final String EVENT_KEYWORD_FLIP = "DispatchFlip";
+
private static final String EVENT_KEYWORD_KEYPRESS = "DispatchPress";
+
private static final String EVENT_KEYWORD_ACTIVITY = "LaunchActivity";
+
private static final String EVENT_KEYWORD_WAIT = "UserWait";
+
private static final String EVENT_KEYWORD_LONGPRESS = "LongPress";
// a line at the end of the header
- private static final String STARTING_DATA_LINE = "start data >>";
- private boolean mFileOpened = false;
- private static int LONGPRESS_WAIT_TIME = 2000; // wait time for the long press
-
+ private static final String STARTING_DATA_LINE = "start data >>";
+
+ private boolean mFileOpened = false;
+
+ private static int LONGPRESS_WAIT_TIME = 2000; // wait time for the long
+
+ // press
+
FileInputStream mFStream;
+
DataInputStream mInputStream;
- BufferedReader mBufferReader;
-
+
+ BufferedReader mBufferedReader;
+
+ /**
+ * Creates a MonkeySourceScript instance.
+ *
+ * @param filename The filename of the script (on the device).
+ * @param throttle The amount of time in ms to sleep between events.
+ */
public MonkeySourceScript(String filename, long throttle) {
mScriptFileName = filename;
mQ = new MonkeyEventQueue(throttle);
}
-
+
/**
- *
- * @return the number of total events that will be generated in a round
+ * Resets the globals used to timeshift events.
*/
- public int getOneRoundEventCount() {
- //plus one home key down and up event
- return mEventCountInScript + POLICY_ADDITIONAL_EVENT_COUNT;
- }
-
private void resetValue() {
- mLastRecordedDownTimeKey = 0;
+ mLastRecordedDownTimeKey = 0;
mLastRecordedDownTimeMotion = 0;
+ mLastRecordedEventTime = -1;
mLastExportDownTimeKey = 0;
- mLastExportDownTimeMotion = 0;
- mLastRecordedEventTime = -1;
+ mLastExportDownTimeMotion = 0;
mLastExportEventTime = -1;
}
-
- private boolean readScriptHeader() {
- mEventCountInScript = -1;
- mFileOpened = false;
- try {
- if (THIS_DEBUG) {
- System.out.println("reading script header");
- }
-
- mFStream = new FileInputStream(mScriptFileName);
- mInputStream = new DataInputStream(mFStream);
- mBufferReader = new BufferedReader(
- new InputStreamReader(mInputStream));
- String sLine;
- while ((sLine = mBufferReader.readLine()) != null) {
- sLine = sLine.trim();
- if (sLine.indexOf(HEADER_TYPE) >= 0) {
- mScriptType = sLine.substring(HEADER_TYPE.length() + 1).trim();
- } else if (sLine.indexOf(HEADER_COUNT) >= 0) {
- try {
- mEventCountInScript = Integer.parseInt(sLine.substring(
- HEADER_COUNT.length() + 1).trim());
- } catch (NumberFormatException e) {
- System.err.println(e);
- }
- } else if (sLine.indexOf(HEADER_SPEED) >= 0) {
- try {
- mSpeed = Double.parseDouble(sLine.substring(
- HEADER_SPEED.length() + 1).trim());
-
- } catch (NumberFormatException e) {
- System.err.println(e);
- }
- } else if (sLine.indexOf(STARTING_DATA_LINE) >= 0) {
- // header ends until we read the start data mark
- mFileOpened = true;
- if (THIS_DEBUG) {
- System.out.println("read script header success");
- }
- return true;
+
+ /**
+ * Reads the header of the script file.
+ *
+ * @return True if the file header could be parsed, and false otherwise.
+ * @throws IOException If there was an error reading the file.
+ */
+ private boolean readHeader() throws IOException {
+ mFileOpened = true;
+
+ mFStream = new FileInputStream(mScriptFileName);
+ mInputStream = new DataInputStream(mFStream);
+ mBufferedReader = new BufferedReader(new InputStreamReader(mInputStream));
+
+ String line;
+
+ while ((line = mBufferedReader.readLine()) != null) {
+ line = line.trim();
+
+ if (line.indexOf(HEADER_COUNT) >= 0) {
+ try {
+ String value = line.substring(HEADER_COUNT.length() + 1).trim();
+ mEventCountInScript = Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ System.err.println(e);
+ return false;
}
- }
- } catch (FileNotFoundException e) {
- System.err.println(e);
- } catch (IOException e) {
- System.err.println(e);
+ } else if (line.indexOf(HEADER_SPEED) >= 0) {
+ try {
+ String value = line.substring(HEADER_COUNT.length() + 1).trim();
+ mSpeed = Double.parseDouble(value);
+ } catch (NumberFormatException e) {
+ System.err.println(e);
+ return false;
+ }
+ } else if (line.indexOf(STARTING_DATA_LINE) >= 0) {
+ return true;
+ }
}
-
- if (THIS_DEBUG) {
- System.out.println("Error in reading script header");
- }
- return false;
- }
-
- private void handleRawEvent(String s, StringTokenizer st) {
- if (s.indexOf(EVENT_KEYWORD_KEY) >= 0) {
- // key events
+
+ return false;
+ }
+
+ /**
+ * Reads a number of lines and passes the lines to be processed.
+ *
+ * @return The number of lines read.
+ * @throws IOException If there was an error reading the file.
+ */
+ private int readLines() throws IOException {
+ String line;
+ for (int i = 0; i < MAX_ONE_TIME_READS; i++) {
+ line = mBufferedReader.readLine();
+ if (line == null) {
+ return i;
+ }
+ line.trim();
+ processLine(line);
+ }
+ return MAX_ONE_TIME_READS;
+ }
+
+ /**
+ * Creates an event and adds it to the event queue. If the parameters are
+ * not understood, they are ignored and no events are added.
+ *
+ * @param s The entire string from the script file.
+ * @param args An array of arguments extracted from the script file line.
+ */
+ private void handleEvent(String s, String[] args) {
+ // Handle key event
+ if (s.indexOf(EVENT_KEYWORD_KEY) >= 0 && args.length == 8) {
try {
System.out.println(" old key\n");
- long downTime = Long.parseLong(st.nextToken());
- long eventTime = Long.parseLong(st.nextToken());
- int action = Integer.parseInt(st.nextToken());
- int code = Integer.parseInt(st.nextToken());
- int repeat = Integer.parseInt(st.nextToken());
- int metaState = Integer.parseInt(st.nextToken());
- int device = Integer.parseInt(st.nextToken());
- int scancode = Integer.parseInt(st.nextToken());
-
- MonkeyKeyEvent e =
- new MonkeyKeyEvent(downTime, eventTime, action, code, repeat, metaState,
- device, scancode);
+ long downTime = Long.parseLong(args[0]);
+ long eventTime = Long.parseLong(args[1]);
+ int action = Integer.parseInt(args[2]);
+ int code = Integer.parseInt(args[3]);
+ int repeat = Integer.parseInt(args[4]);
+ int metaState = Integer.parseInt(args[5]);
+ int device = Integer.parseInt(args[6]);
+ int scancode = Integer.parseInt(args[7]);
+
+ MonkeyKeyEvent e = new MonkeyKeyEvent(downTime, eventTime, action, code, repeat,
+ metaState, device, scancode);
System.out.println(" Key code " + code + "\n");
mQ.addLast(e);
System.out.println("Added key up \n");
-
} catch (NumberFormatException e) {
- // something wrong with this line in the script
}
- } else if (s.indexOf(EVENT_KEYWORD_POINTER) >= 0 ||
- s.indexOf(EVENT_KEYWORD_TRACKBALL) >= 0) {
- // trackball/pointer event
+ return;
+ }
+
+ // Handle trackball or pointer events
+ if ((s.indexOf(EVENT_KEYWORD_POINTER) >= 0 || s.indexOf(EVENT_KEYWORD_TRACKBALL) >= 0)
+ && args.length == 12) {
try {
- long downTime = Long.parseLong(st.nextToken());
- long eventTime = Long.parseLong(st.nextToken());
- int action = Integer.parseInt(st.nextToken());
- float x = Float.parseFloat(st.nextToken());
- float y = Float.parseFloat(st.nextToken());
- float pressure = Float.parseFloat(st.nextToken());
- float size = Float.parseFloat(st.nextToken());
- int metaState = Integer.parseInt(st.nextToken());
- float xPrecision = Float.parseFloat(st.nextToken());
- float yPrecision = Float.parseFloat(st.nextToken());
- int device = Integer.parseInt(st.nextToken());
- int edgeFlags = Integer.parseInt(st.nextToken());
+ long downTime = Long.parseLong(args[0]);
+ long eventTime = Long.parseLong(args[1]);
+ int action = Integer.parseInt(args[2]);
+ float x = Float.parseFloat(args[3]);
+ float y = Float.parseFloat(args[4]);
+ float pressure = Float.parseFloat(args[5]);
+ float size = Float.parseFloat(args[6]);
+ int metaState = Integer.parseInt(args[7]);
+ float xPrecision = Float.parseFloat(args[8]);
+ float yPrecision = Float.parseFloat(args[9]);
+ int device = Integer.parseInt(args[10]);
+ int edgeFlags = Integer.parseInt(args[11]);
int type = MonkeyEvent.EVENT_TYPE_TRACKBALL;
if (s.indexOf("Pointer") > 0) {
type = MonkeyEvent.EVENT_TYPE_POINTER;
}
- MonkeyMotionEvent e =
- new MonkeyMotionEvent(type, downTime, eventTime, action, x, y, pressure,
- size, metaState, xPrecision, yPrecision, device, edgeFlags);
+ MonkeyMotionEvent e = new MonkeyMotionEvent(type, downTime, eventTime, action, x,
+ y, pressure, size, metaState, xPrecision, yPrecision, device, edgeFlags);
mQ.addLast(e);
} catch (NumberFormatException e) {
- // we ignore this event
}
- } else if (s.indexOf(EVENT_KEYWORD_FLIP) >= 0) {
- boolean keyboardOpen = Boolean.parseBoolean(st.nextToken());
+ return;
+ }
+
+ // Handle flip events
+ if (s.indexOf(EVENT_KEYWORD_FLIP) >= 0 && args.length == 1) {
+ boolean keyboardOpen = Boolean.parseBoolean(args[0]);
MonkeyFlipEvent e = new MonkeyFlipEvent(keyboardOpen);
mQ.addLast(e);
}
- }
-
- private void handleUserEvent(String s, StringTokenizer st) {
- if (s.indexOf(EVENT_KEYWORD_ACTIVITY) >= 0) {
- String pkg_name = st.nextToken();
- String cl_name = st.nextToken();
+ // Handle launch events
+ if (s.indexOf(EVENT_KEYWORD_ACTIVITY) >= 0 && args.length == 2) {
+ String pkg_name = args[0];
+ String cl_name = args[1];
ComponentName mApp = new ComponentName(pkg_name, cl_name);
MonkeyActivityEvent e = new MonkeyActivityEvent(mApp);
mQ.addLast(e);
+ return;
+ }
- } else if (s.indexOf(EVENT_KEYWORD_WAIT) >= 0) {
- long sleeptime = Integer.parseInt(st.nextToken());
- MonkeyWaitEvent e = new MonkeyWaitEvent(sleeptime);
- mQ.addLast(e);
+ // Handle wait events
+ if (s.indexOf(EVENT_KEYWORD_WAIT) >= 0 && args.length == 1) {
+ try {
+ long sleeptime = Integer.parseInt(args[0]);
+ MonkeyWaitEvent e = new MonkeyWaitEvent(sleeptime);
+ mQ.addLast(e);
+ } catch (NumberFormatException e) {
+ }
+ return;
+ }
- } else if (s.indexOf(EVENT_KEYWORD_KEYPRESS) >= 0) {
- String key_name = st.nextToken();
+ // Handle keypress events
+ if (s.indexOf(EVENT_KEYWORD_KEYPRESS) >= 0 && args.length == 1) {
+ String key_name = args[0];
int keyCode = MonkeySourceRandom.getKeyCode(key_name);
MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, keyCode);
mQ.addLast(e);
e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, keyCode);
mQ.addLast(e);
- } else if (s.indexOf(EVENT_KEYWORD_LONGPRESS) >= 0) {
- // handle the long press
- MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN,
- KeyEvent.KEYCODE_DPAD_CENTER);
+ return;
+ }
+
+ // Handle longpress events
+ if (s.indexOf(EVENT_KEYWORD_LONGPRESS) >= 0) {
+ MonkeyKeyEvent e;
+ e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER);
mQ.addLast(e);
MonkeyWaitEvent we = new MonkeyWaitEvent(LONGPRESS_WAIT_TIME);
mQ.addLast(we);
- e = new MonkeyKeyEvent(KeyEvent.ACTION_UP,
- KeyEvent.KEYCODE_DPAD_CENTER);
+ e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER);
mQ.addLast(e);
}
}
- private void processLine(String s) {
- int index1 = s.indexOf('(');
- int index2 = s.indexOf(')');
+ /**
+ * Extracts an event and a list of arguments from a line. If the line does
+ * not match the format required, it is ignored.
+ *
+ * @param line A string in the form {@code cmd(arg1,arg2,arg3)}.
+ */
+ private void processLine(String line) {
+ int index1 = line.indexOf('(');
+ int index2 = line.indexOf(')');
if (index1 < 0 || index2 < 0) {
return;
}
- StringTokenizer st = new StringTokenizer(
- s.substring(index1 + 1, index2), ",");
- if (mScriptType.compareTo(USER_EVENT_TYPE) == 0) {
- // User event type
- handleUserEvent(s, st);
- } else {
- // Raw type
- handleRawEvent(s,st);
+ String[] args = line.substring(index1 + 1, index2).split(",");
+
+ for (int i = 0; i < args.length; i++) {
+ args[i] = args[i].trim();
}
+
+ handleEvent(line, args);
}
- private void closeFile() {
- mFileOpened = false;
- if (THIS_DEBUG) {
- System.out.println("closing script file");
- }
-
+ /**
+ * Closes the script file.
+ *
+ * @throws IOException If there was an error closing the file.
+ */
+ private void closeFile() throws IOException {
+ mFileOpened = false;
+
try {
mFStream.close();
mInputStream.close();
- } catch (IOException e) {
- System.out.println(e);
+ } catch (NullPointerException e) {
+ // File was never opened so it can't be closed.
}
}
-
- /**
- * add home key press/release event to the queue
- */
- private void addHomeKeyEvent() {
- MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN,
- KeyEvent.KEYCODE_HOME);
- mQ.addLast(e);
- e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HOME);
- mQ.addLast(e);
- }
-
+
/**
- * read next batch of events from the provided script file
- * @return true if success
+ * Read next batch of events from the script file into the event queue.
+ * Checks if the script is open and then reads the next MAX_ONE_TIME_READS
+ * events or reads until the end of the file. If no events are read, then
+ * the script is closed.
+ *
+ * @throws IOException If there was an error reading the file.
*/
- private boolean readNextBatch() {
- /*
- * The script should restore the original state when it run multiple
- * times.
- */
- String sLine = null;
- int readCount = 0;
+ private void readNextBatch() throws IOException {
+ int linesRead = 0;
if (THIS_DEBUG) {
System.out.println("readNextBatch(): reading next batch of events");
}
if (!mFileOpened) {
- if (!readScriptHeader()) {
- closeFile();
- return false;
- }
resetValue();
+ readHeader();
}
-
- try {
- while (readCount++ < MAX_ONE_TIME_READS &&
- (sLine = mBufferReader.readLine()) != null) {
- sLine = sLine.trim();
- processLine(sLine);
- }
- } catch (IOException e) {
- System.err.println(e);
- return false;
- }
-
- if (sLine == null) {
- // to the end of the file
- if (THIS_DEBUG) {
- System.out.println("readNextBatch(): to the end of file");
- }
+
+ linesRead = readLines();
+
+ if (linesRead == 0) {
closeFile();
- }
- return true;
+ }
}
-
+
/**
- * sleep for a period of given time, introducing latency among events
- * @param time to sleep in millisecond
+ * Sleep for a period of given time. Used to introduce latency between
+ * events.
+ *
+ * @param time The amount of time to sleep in ms
*/
- private void needSleep(long time) {
+ private void needSleep(long time) {
if (time < 1) {
- return;
- }
+ return;
+ }
try {
Thread.sleep(time);
- } catch (InterruptedException e) {
- }
- }
-
+ } catch (InterruptedException e) {
+ }
+ }
+
/**
- * check whether we can successfully read the header of the script file
+ * Checks if the file can be opened and if the header is valid.
+ *
+ * @return True if the file exists and the header is valid, false otherwise.
*/
public boolean validate() {
- boolean b = readNextBatch();
+ boolean validHeader;
+ try {
+ validHeader = readHeader();
+ closeFile();
+ } catch (IOException e) {
+ return false;
+ }
+
if (mVerbose > 0) {
- System.out.println("Replaying " + mEventCountInScript +
- " events with speed " + mSpeed);
+ System.out.println("Replaying " + mEventCountInScript + " events with speed " + mSpeed);
}
- return b;
+ return validHeader;
}
-
+
public void setVerbose(int verbose) {
mVerbose = verbose;
}
-
+
/**
- * adjust key downtime and eventtime according to both
- * recorded values and current system time
- * @param e KeyEvent
+ * Adjust key downtime and eventtime according to both recorded values and
+ * current system time.
+ *
+ * @param e A KeyEvent
*/
private void adjustKeyEventTime(MonkeyKeyEvent e) {
if (e.getEventTime() < 0) {
return;
- }
+ }
long thisDownTime = 0;
long thisEventTime = 0;
long expectedDelay = 0;
-
+
if (mLastRecordedEventTime <= 0) {
- // first time event
+ // first time event
thisDownTime = SystemClock.uptimeMillis();
thisEventTime = thisDownTime;
- } else {
+ } else {
if (e.getDownTime() != mLastRecordedDownTimeKey) {
thisDownTime = e.getDownTime();
} else {
thisDownTime = mLastExportDownTimeKey;
- }
- expectedDelay = (long) ((e.getEventTime() -
- mLastRecordedEventTime) * mSpeed);
+ }
+ expectedDelay = (long) ((e.getEventTime() - mLastRecordedEventTime) * mSpeed);
thisEventTime = mLastExportEventTime + expectedDelay;
// add sleep to simulate everything in recording
needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF);
- }
+ }
mLastRecordedDownTimeKey = e.getDownTime();
- mLastRecordedEventTime = e.getEventTime();
+ mLastRecordedEventTime = e.getEventTime();
e.setDownTime(thisDownTime);
- e.setEventTime(thisEventTime);
+ e.setEventTime(thisEventTime);
mLastExportDownTimeKey = thisDownTime;
- mLastExportEventTime = thisEventTime;
+ mLastExportEventTime = thisEventTime;
}
-
+
/**
- * adjust motion downtime and eventtime according to both
- * recorded values and current system time
- * @param e KeyEvent
+ * Adjust motion downtime and eventtime according to both recorded values
+ * and current system time.
+ *
+ * @param e A KeyEvent
*/
private void adjustMotionEventTime(MonkeyMotionEvent e) {
if (e.getEventTime() < 0) {
return;
- }
+ }
long thisDownTime = 0;
long thisEventTime = 0;
long expectedDelay = 0;
-
+
if (mLastRecordedEventTime <= 0) {
- // first time event
+ // first time event
thisDownTime = SystemClock.uptimeMillis();
thisEventTime = thisDownTime;
- } else {
+ } else {
if (e.getDownTime() != mLastRecordedDownTimeMotion) {
thisDownTime = e.getDownTime();
} else {
thisDownTime = mLastExportDownTimeMotion;
- }
- expectedDelay = (long) ((e.getEventTime() -
- mLastRecordedEventTime) * mSpeed);
+ }
+ expectedDelay = (long) ((e.getEventTime() - mLastRecordedEventTime) * mSpeed);
thisEventTime = mLastExportEventTime + expectedDelay;
// add sleep to simulate everything in recording
needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF);
}
-
+
mLastRecordedDownTimeMotion = e.getDownTime();
- mLastRecordedEventTime = e.getEventTime();
+ mLastRecordedEventTime = e.getEventTime();
e.setDownTime(thisDownTime);
- e.setEventTime(thisEventTime);
+ e.setEventTime(thisEventTime);
mLastExportDownTimeMotion = thisDownTime;
- mLastExportEventTime = thisEventTime;
- }
-
+ mLastExportEventTime = thisEventTime;
+ }
+
/**
- * if the queue is empty, we generate events first
- * @return the first event in the queue, if null, indicating the system crashes
+ * Gets the next event to be injected from the script. If the event queue is
+ * empty, reads the next n events from the script into the queue, where n is
+ * the lesser of the number of remaining events and the value specified by
+ * MAX_ONE_TIME_READS. If the end of the file is reached, no events are
+ * added to the queue and null is returned.
+ *
+ * @return The first event in the event queue or null if the end of the file
+ * is reached or if an error is encountered reading the file.
*/
public MonkeyEvent getNextEvent() {
long recordedEventTime = -1;
-
+ MonkeyEvent ev;
+
if (mQ.isEmpty()) {
- readNextBatch();
+ try {
+ readNextBatch();
+ } catch (IOException e) {
+ return null;
+ }
}
- MonkeyEvent e = mQ.getFirst();
- mQ.removeFirst();
- if (e.getEventType() == MonkeyEvent.EVENT_TYPE_KEY) {
- adjustKeyEventTime((MonkeyKeyEvent) e);
- } else if (e.getEventType() == MonkeyEvent.EVENT_TYPE_POINTER ||
- e.getEventType() == MonkeyEvent.EVENT_TYPE_TRACKBALL) {
- adjustMotionEventTime((MonkeyMotionEvent) e);
+
+ try {
+ ev = mQ.getFirst();
+ mQ.removeFirst();
+ } catch (NoSuchElementException e) {
+ return null;
+ }
+
+ if (ev.getEventType() == MonkeyEvent.EVENT_TYPE_KEY) {
+ adjustKeyEventTime((MonkeyKeyEvent) ev);
+ } else if (ev.getEventType() == MonkeyEvent.EVENT_TYPE_POINTER
+ || ev.getEventType() == MonkeyEvent.EVENT_TYPE_TRACKBALL) {
+ adjustMotionEventTime((MonkeyMotionEvent) ev);
}
- return e;
+ return ev;
}
}
%CompositeAdbInterface% = USB_Install, USB\VID_18D1&PID_0D02&MI_01\r
%SingleAdbInterface% = USB_Install, USB\VID_18D1&PID_4E11
%CompositeAdbInterface% = USB_Install, USB\VID_18D1&PID_4E12&MI_01
+;
+; Dell's Mini5
+%CompositeAdbInterface% = USB_Install, USB\VID_413C&PID_B007&MI_01
\r
[Google.NTamd64]\r
; HTC Dream\r
%CompositeAdbInterface% = USB_Install, USB\VID_18D1&PID_0D02&MI_01
%SingleAdbInterface% = USB_Install, USB\VID_18D1&PID_4E11\r
%CompositeAdbInterface% = USB_Install, USB\VID_18D1&PID_4E12&MI_01
+;
+; Dell's Mini5
+%CompositeAdbInterface% = USB_Install, USB\VID_413C&PID_B007&MI_01
\r
[USB_Install]\r
Include = winusb.inf\r
Android NDK ChangeLog:
-------------------------------------------------------------------------------
-current version
+android-ndk-r3
IMPORTANT BUG FIXES:
folder corresponding to the old ABI.
+- Updated the STABLE-APIS.TXT document to clarify the OpenGL ES 1.0/1.1/2.0
+ issues regarding specific devices (i.e. 1.0 supported everywhere, 1.1 and
+ 2.0 on specific devices only, need for <uses-feature> tag in manifest).
+
+
OTHER FIXES & CHANGES:
- Actually use the awk version detected by host-setup.sh during the build.
LOCAL_LDLIBS := -lGLESv1_CM.so
+
+The '1.x' here refers to both versions 1.0 and 1.1 of the OpenGL ES APIs.
+Please note that:
+
+ - OpenGL ES 1.0 is supported on *all* Android-based devices.
+ - OpenGL ES 1.1 is fully supported only on specific devices that
+ have the corresponding GPU.
+
+This is because Android comes with a 1.0-capable software renderer that can
+be used on GPU-less devices.
+
+Developers should query the OpenGL ES version string and extension string
+to know if the current device supports the features they need. See the
+description of glGetString() in the specification to see how to do that:
+
+ http://www.khronos.org/opengles/sdk/1.1/docs/man/glGetString.xml
+
+Additionally, developers must put a <uses-feature> tag in their manifest
+file to indicate which version of OpenGL ES their application requires. See
+the documentation linked below for details:
+
+ http://developer.android.com/guide/topics/manifest/uses-feature-element.html
+
Please note that, at the moment, native headers and libraries for the EGL APIs
are *not* available. EGL is used to perform surface creation and flipping
(instead of rendering). The corresponding operations must be performed in your
LOCAL_LDLIBS := -lGLESv2.so
+Not all devices support OpenGL ES 2.0, developers should thus query the
+implementation's version and extension strings, and put a <uses-feature>
+tag in their Android manifest. See Section III above for details.
+
Please note that, at the moment, native headers and libraries for the EGL APIs
are *not* available. EGL is used to perform surface creation and flipping
(instead of rendering). The corresponding operations must be performed in your
--- /dev/null
+<p>A sample application that demonstrates how to launch the built-in contact
+picker from within an activity. This sample also uses reflection to ensure
+that the correct version of the contacts API is used, depending on which API
+level the application is running under.</p>
+
+<img alt="Screenshot #1 of the Business Card application" src="../images/BusinessCard1.png" />
+<img alt="Screenshot #2 of the Business Card application" src="../images/BusinessCard2.png" />
--- /dev/null
+Pkg.UserSrc=false
+Platform.Version=2.1
+Pkg.Revision=1
+AndroidVersion.ApiLevel=7
+
--- /dev/null
+# Copyright 2009 Google Inc. All Rights Reserved.
+#
+# Android.mk for etc1tool
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := etc1tool.cpp
+
+LOCAL_C_INCLUDES += external/libpng
+LOCAL_C_INCLUDES += external/zlib
+LOCAL_C_INCLUDES += build/libs/host/include
+LOCAL_C_INCLUDES += frameworks/base/opengl/include
+
+#LOCAL_WHOLE_STATIC_LIBRARIES :=
+LOCAL_STATIC_LIBRARIES := \
+ libhost \
+ libutils \
+ libcutils \
+ libexpat \
+ libpng \
+ libETC1
+
+LOCAL_LDLIBS := -lz
+
+ifeq ($(HOST_OS),linux)
+LOCAL_LDLIBS += -lrt
+endif
+
+ifeq ($(HOST_OS),windows)
+ifeq ($(strip $(USE_CYGWIN),),)
+LOCAL_LDLIBS += -lws2_32
+endif
+endif
+
+LOCAL_MODULE := etc1tool
+
+include $(BUILD_HOST_EXECUTABLE)
--- /dev/null
+// Copyright 2009 Google Inc.
+//
+// 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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <png.h>
+#include <ETC1/etc1.h>
+
+
+int writePNGFile(const char* pOutput, png_uint_32 width, png_uint_32 height,
+ const png_bytep pImageData, png_uint_32 imageStride);
+
+const char* gpExeName;
+
+static
+void usage(char* message, ...) {
+ if (message) {
+ va_list ap;
+ va_start(ap, message);
+ vfprintf(stderr, message, ap);
+ va_end(ap);
+ fprintf(stderr, "\n\n");
+ fprintf(stderr, "usage:\n");
+ }
+ fprintf(
+ stderr,
+ "%s infile [--help | --encode | --encodeNoHeader | --decode] [--showDifference difffile] [-o outfile]\n",
+ gpExeName);
+ fprintf(stderr, "\tDefault is --encode\n");
+ fprintf(stderr, "\t\t--help print this usage information.\n");
+ fprintf(stderr,
+ "\t\t--encode create an ETC1 file from a PNG file.\n");
+ fprintf(
+ stderr,
+ "\t\t--encodeNoHeader create a raw ETC1 data file (without a header) from a PNG file.\n");
+ fprintf(stderr,
+ "\t\t--decode create a PNG file from an ETC1 file.\n");
+ fprintf(stderr,
+ "\t\t--showDifference difffile Write difference between original and encoded\n");
+ fprintf(stderr,
+ "\t\t image to difffile. (Only valid when encoding).\n");
+ fprintf(stderr,
+ "\tIf outfile is not specified, an outfile path is constructed from infile,\n");
+ fprintf(stderr, "\twith the apropriate suffix (.pkm or .png).\n");
+ exit(1);
+}
+
+// Returns non-zero if an error occured
+
+static
+int changeExtension(char* pPath, size_t pathCapacity, const char* pExtension) {
+ size_t pathLen = strlen(pPath);
+ size_t extensionLen = strlen(pExtension);
+ if (pathLen + extensionLen + 1 > pathCapacity) {
+ return -1;
+ }
+
+ // Check for '.' and '..'
+ if ((pathLen == 1 && pPath[0] == '.') || (pathLen == 2 && pPath[0] == '.'
+ && pPath[1] == '.') || (pathLen >= 2 && pPath[pathLen - 2] == '/'
+ && pPath[pathLen - 1] == '.') || (pathLen >= 3
+ && pPath[pathLen - 3] == '/' && pPath[pathLen - 2] == '.'
+ && pPath[pathLen - 1] == '.')) {
+ return -2;
+ }
+
+ int index;
+ for (index = pathLen - 1; index > 0; index--) {
+ char c = pPath[index];
+ if (c == '/') {
+ // No extension found. Append our extension.
+ strcpy(pPath + pathLen, pExtension);
+ return 0;
+ } else if (c == '.') {
+ strcpy(pPath + index, pExtension);
+ return 0;
+ }
+ }
+
+ // No extension or directory found. Append our extension
+ strcpy(pPath + pathLen, pExtension);
+ return 0;
+}
+
+void PNGAPI user_error_fn(png_structp png_ptr, png_const_charp message) {
+ fprintf(stderr, "PNG error: %s\n", message);
+}
+
+void PNGAPI user_warning_fn(png_structp png_ptr, png_const_charp message) {
+ fprintf(stderr, "PNG warning: %s\n", message);
+}
+
+// Return non-zero on error
+int fwrite_big_endian_uint16(png_uint_32 data, FILE* pOut) {
+ if (fputc(0xff & (data >> 8), pOut) == EOF) {
+ return -1;
+ }
+ if (fputc(0xff & data, pOut) == EOF) {
+ return -1;
+ }
+ return 0;
+}
+
+// Return non-zero on error
+int fread_big_endian_uint16(png_uint_32* data, FILE* pIn) {
+ int a, b;
+ if ((a = fgetc(pIn)) == EOF) {
+ return -1;
+ }
+ if ((b = fgetc(pIn)) == EOF) {
+ return -1;
+ }
+ *data = ((0xff & a) << 8) | (0xff & b);
+ return 0;
+}
+
+// Read a PNG file into a contiguous buffer.
+// Returns non-zero if an error occurred.
+// caller has to delete[] *ppImageData when done with the image.
+
+int read_PNG_File(const char* pInput, etc1_byte** ppImageData,
+ etc1_uint32* pWidth, etc1_uint32* pHeight) {
+ FILE* pIn = NULL;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ png_infop end_info = NULL;
+ png_bytep* row_pointers = NULL; // Does not need to be deallocated.
+ png_uint_32 width = 0;
+ png_uint_32 height = 0;
+ int result = -1;
+ etc1_byte* pSourceImage = 0;
+ png_uint_32 stride = 0;
+
+ if ((pIn = fopen(pInput, "rb")) == NULL) {
+ fprintf(stderr, "Could not open input file %s for reading: %d\n",
+ pInput, errno);
+ goto exit;
+ }
+
+ static const size_t PNG_HEADER_SIZE = 8;
+ png_byte pngHeader[PNG_HEADER_SIZE];
+ if (fread(pngHeader, 1, PNG_HEADER_SIZE, pIn) != PNG_HEADER_SIZE) {
+ fprintf(stderr, "Could not read PNG header from %s: %d\n", pInput,
+ errno);
+ goto exit;
+ }
+
+ if (png_sig_cmp(pngHeader, 0, PNG_HEADER_SIZE)) {
+ fprintf(stderr, "%s is not a PNG file.\n", pInput);
+ goto exit;
+ }
+
+ if (!(png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+ (png_voidp) NULL, user_error_fn, user_warning_fn))) {
+ fprintf(stderr, "Could not initialize png read struct.\n");
+ goto exit;
+ }
+
+ if (!(info_ptr = png_create_info_struct(png_ptr))) {
+ fprintf(stderr, "Could not create info struct.\n");
+ goto exit;
+ }
+ if (!(end_info = png_create_info_struct(png_ptr))) {
+ fprintf(stderr, "Could not create end_info struct.\n");
+ goto exit;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ goto exit;
+ }
+
+ png_init_io(png_ptr, pIn);
+ png_set_sig_bytes(png_ptr, PNG_HEADER_SIZE);
+ png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY
+ | PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_STRIP_ALPHA
+ | PNG_TRANSFORM_PACKING, NULL);
+
+ row_pointers = png_get_rows(png_ptr, info_ptr);
+ {
+ int bit_depth, color_type;
+ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
+ &color_type, NULL, NULL, NULL);
+ }
+
+ stride = 3 * width;
+
+ pSourceImage = new etc1_byte[stride * height];
+ if (! pSourceImage) {
+ fprintf(stderr, "Out of memory.\n");
+ goto exit;
+ }
+
+ for (etc1_uint32 y = 0; y < height; y++) {
+ memcpy(pSourceImage + y * stride, row_pointers[y], stride);
+ }
+
+ *pWidth = width;
+ *pHeight = height;
+ *ppImageData = pSourceImage;
+
+ result = 0;
+ exit:
+ if (result) {
+ delete[] pSourceImage;
+ }
+ if (png_ptr) {
+ png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+ }
+ if (pIn) {
+ fclose(pIn);
+ }
+
+ return result;
+}
+
+// Read a PNG file into a contiguous buffer.
+// Returns non-zero if an error occurred.
+// caller has to delete[] *ppImageData when done with the image.
+int readPKMFile(const char* pInput, etc1_byte** ppImageData,
+ etc1_uint32* pWidth, etc1_uint32* pHeight) {
+ int result = -1;
+ FILE* pIn = NULL;
+ etc1_byte header[ETC_PKM_HEADER_SIZE];
+ png_bytep pEncodedData = NULL;
+ png_bytep pImageData = NULL;
+
+ png_uint_32 width = 0;
+ png_uint_32 height = 0;
+ png_uint_32 stride = 0;
+ png_uint_32 encodedSize = 0;
+
+ if ((pIn = fopen(pInput, "rb")) == NULL) {
+ fprintf(stderr, "Could not open input file %s for reading: %d\n",
+ pInput, errno);
+ goto exit;
+ }
+
+ if (fread(header, sizeof(header), 1, pIn) != 1) {
+ fprintf(stderr, "Could not read header from input file %s: %d\n",
+ pInput, errno);
+ goto exit;
+ }
+
+ if (! etc1_pkm_is_valid(header)) {
+ fprintf(stderr, "Bad header PKM header for input file %s\n", pInput);
+ goto exit;
+ }
+
+ width = etc1_pkm_get_width(header);
+ height = etc1_pkm_get_height(header);
+ encodedSize = etc1_get_encoded_data_size(width, height);
+
+ pEncodedData = new png_byte[encodedSize];
+ if (!pEncodedData) {
+ fprintf(stderr, "Out of memory.\n");
+ goto exit;
+ }
+
+ if (fread(pEncodedData, encodedSize, 1, pIn) != 1) {
+ fprintf(stderr, "Could not read encoded data from input file %s: %d\n",
+ pInput, errno);
+ goto exit;
+ }
+
+ fclose(pIn);
+ pIn = NULL;
+
+ stride = width * 3;
+ pImageData = new png_byte[stride * height];
+ if (!pImageData) {
+ fprintf(stderr, "Out of memory.\n");
+ goto exit;
+ }
+
+ etc1_decode_image(pEncodedData, pImageData, width, height, 3, stride);
+
+ // Success
+ result = 0;
+ *ppImageData = pImageData;
+ pImageData = 0;
+ *pWidth = width;
+ *pHeight = height;
+
+ exit:
+ delete[] pEncodedData;
+ delete[] pImageData;
+ if (pIn) {
+ fclose(pIn);
+ }
+
+ return result;
+}
+
+
+// Encode the file.
+// Returns non-zero if an error occurred.
+
+int encode(const char* pInput, const char* pOutput, bool bEmitHeader, const char* pDiffFile) {
+ FILE* pOut = NULL;
+ etc1_uint32 width = 0;
+ etc1_uint32 height = 0;
+ etc1_uint32 encodedSize = 0;
+ int result = -1;
+ etc1_byte* pSourceImage = 0;
+ etc1_byte* pEncodedData = 0;
+ etc1_byte* pDiffImage = 0; // Used for differencing
+
+ if (read_PNG_File(pInput, &pSourceImage, &width, &height)) {
+ goto exit;
+ }
+
+ encodedSize = etc1_get_encoded_data_size(width, height);
+ pEncodedData = new etc1_byte[encodedSize];
+ if (!pEncodedData) {
+ fprintf(stderr, "Out of memory.\n");
+ goto exit;
+ }
+
+ etc1_encode_image(pSourceImage,
+ width, height, 3, width * 3, pEncodedData);
+
+ if ((pOut = fopen(pOutput, "wb")) == NULL) {
+ fprintf(stderr, "Could not open output file %s: %d\n", pOutput, errno);
+ goto exit;
+ }
+
+ if (bEmitHeader) {
+ etc1_byte header[ETC_PKM_HEADER_SIZE];
+ etc1_pkm_format_header(header, width, height);
+ if (fwrite(header, sizeof(header), 1, pOut) != 1) {
+ fprintf(stderr,
+ "Could not write header output file %s: %d\n",
+ pOutput, errno);
+ goto exit;
+ }
+ }
+
+ if (fwrite(pEncodedData, encodedSize, 1, pOut) != 1) {
+ fprintf(stderr,
+ "Could not write encoded data to output file %s: %d\n",
+ pOutput, errno);
+ goto exit;
+ }
+
+ fclose(pOut);
+ pOut = NULL;
+
+ if (pDiffFile) {
+ etc1_uint32 outWidth;
+ etc1_uint32 outHeight;
+ if (readPKMFile(pOutput, &pDiffImage, &outWidth, &outHeight)) {
+ goto exit;
+ }
+ if (outWidth != width || outHeight != height) {
+ fprintf(stderr, "Output file has incorrect bounds: %u, %u != %u, %u\n",
+ outWidth, outHeight, width, height);
+ goto exit;
+ }
+ const etc1_byte* pSrc = pSourceImage;
+ etc1_byte* pDest = pDiffImage;
+ etc1_uint32 size = width * height * 3;
+ for (etc1_uint32 i = 0; i < size; i++) {
+ int diff = *pSrc++ - *pDest;
+ diff *= diff;
+ diff <<= 3;
+ if (diff < 0) {
+ diff = 0;
+ } else if (diff > 255) {
+ diff = 255;
+ }
+ *pDest++ = (png_byte) diff;
+ }
+ writePNGFile(pDiffFile, outWidth, outHeight, pDiffImage, 3 * outWidth);
+ }
+
+ // Success
+ result = 0;
+
+ exit:
+ delete[] pSourceImage;
+ delete[] pEncodedData;
+ delete[] pDiffImage;
+
+ if (pOut) {
+ fclose(pOut);
+ }
+ return result;
+}
+
+int writePNGFile(const char* pOutput, png_uint_32 width, png_uint_32 height,
+ const png_bytep pImageData, png_uint_32 imageStride) {
+ int result = -1;
+ FILE* pOut = NULL;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+
+ if (!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+ (png_voidp) NULL, user_error_fn, user_warning_fn)) || !(info_ptr
+ = png_create_info_struct(png_ptr))) {
+ fprintf(stderr, "Could not initialize PNG library for writing.\n");
+ goto exit;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ goto exit;
+ }
+
+ if ((pOut = fopen(pOutput, "wb")) == NULL) {
+ fprintf(stderr, "Could not open output file %s: %d\n", pOutput, errno);
+ goto exit;
+ }
+
+ png_init_io(png_ptr, pOut);
+
+ png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ png_write_info(png_ptr, info_ptr);
+
+ for (png_uint_32 y = 0; y < height; y++) {
+ png_write_row(png_ptr, pImageData + y * imageStride);
+ }
+ png_write_end(png_ptr, info_ptr);
+
+ result = 0;
+
+ exit: if (png_ptr) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ }
+
+ if (pOut) {
+ fclose(pOut);
+ }
+ return result;
+}
+
+int decode(const char* pInput, const char* pOutput) {
+ int result = -1;
+ png_bytep pImageData = NULL;
+ etc1_uint32 width = 0;
+ etc1_uint32 height = 0;
+
+ if (readPKMFile(pInput, &pImageData, &width, &height)) {
+ goto exit;
+ }
+
+ if (writePNGFile(pOutput, width, height, pImageData, width * 3)) {
+ goto exit;
+ }
+
+ // Success
+ result = 0;
+
+ exit: delete[] pImageData;
+
+ return result;
+}
+
+void multipleEncodeDecodeCheck(bool* pbEncodeDecodeSeen) {
+ if (*pbEncodeDecodeSeen) {
+ usage("At most one occurrence of --encode --encodeNoHeader or --decode is allowed.\n");
+ }
+ *pbEncodeDecodeSeen = true;
+}
+
+int main(int argc, char** argv) {
+ gpExeName = argv[0];
+ const char* pInput = NULL;
+ const char* pOutput = NULL;
+ const char* pDiffFile = NULL;
+ char* pOutputFileBuff = NULL;
+
+ bool bEncodeDecodeSeen = false;
+ bool bEncode = false;
+ bool bEncodeHeader = false;
+ bool bDecode = false;
+ bool bShowDifference = false;
+
+ for (int i = 1; i < argc; i++) {
+ const char* pArg = argv[i];
+ if (pArg[0] == '-') {
+ char c = pArg[1];
+ switch (c) {
+ case 'o':
+ if (pOutput != NULL) {
+ usage("Only one -o flag allowed.");
+ }
+ if (i + 1 >= argc) {
+ usage("Expected outfile after -o");
+ }
+ pOutput = argv[++i];
+ break;
+ case '-':
+ if (strcmp(pArg, "--encode") == 0) {
+ multipleEncodeDecodeCheck(&bEncodeDecodeSeen);
+ bEncode = true;
+ bEncodeHeader = true;
+ } else if (strcmp(pArg, "--encodeNoHeader") == 0) {
+ multipleEncodeDecodeCheck(&bEncodeDecodeSeen);
+ bEncode = true;
+ bEncodeHeader = false;
+ } else if (strcmp(pArg, "--decode") == 0) {
+ multipleEncodeDecodeCheck(&bEncodeDecodeSeen);
+ bDecode = true;
+ } else if (strcmp(pArg, "--showDifference") == 0) {
+ if (bShowDifference) {
+ usage("Only one --showDifference option allowed.\n");
+ }
+ bShowDifference = true;
+ if (i + 1 >= argc) {
+ usage("Expected difffile after --showDifference");
+ }
+ pDiffFile = argv[++i];
+ } else if (strcmp(pArg, "--help") == 0) {
+ usage( NULL);
+ } else {
+ usage("Unknown flag %s", pArg);
+ }
+
+ break;
+ default:
+ usage("Unknown flag %s", pArg);
+ break;
+ }
+ } else {
+ if (pInput != NULL) {
+ usage(
+ "Only one input file allowed. Already have %s, now see %s",
+ pInput, pArg);
+ }
+ pInput = pArg;
+ }
+ }
+
+ if (!bEncodeDecodeSeen) {
+ bEncode = true;
+ bEncodeHeader = true;
+ }
+ if ((! bEncode) && bShowDifference) {
+ usage("--showDifference is only valid when encoding.");
+ }
+
+ if (!pInput) {
+ usage("Expected an input file.");
+ }
+
+ if (!pOutput) {
+ const char* kDefaultExtension = bEncode ? ".pkm" : ".png";
+ size_t buffSize = strlen(pInput) + strlen(kDefaultExtension) + 1;
+ pOutputFileBuff = new char[buffSize];
+ strcpy(pOutputFileBuff, pInput);
+ if (changeExtension(pOutputFileBuff, buffSize, kDefaultExtension)) {
+ usage("Could not change extension of input file name: %s\n", pInput);
+ }
+ pOutput = pOutputFileBuff;
+ }
+
+ if (bEncode) {
+ encode(pInput, pOutput, bEncodeHeader, pDiffFile);
+ } else {
+ decode(pInput, pOutput);
+ }
+
+ delete[] pOutputFileBuff;
+
+ return 0;
+}