Merge commit 'goog/readonly-p4-master'

This commit is contained in:
The Android Open Source Project
2009-03-31 21:34:24 -07:00
41 changed files with 1497 additions and 237 deletions

View File

@@ -24,6 +24,7 @@ bin/aidl platforms/${PLATFORM_NAME}/tools/aidl
bin/adb tools/adb bin/adb tools/adb
bin/sqlite3 tools/sqlite3 bin/sqlite3 tools/sqlite3
bin/dmtracedump tools/dmtracedump bin/dmtracedump tools/dmtracedump
bin/hprof-conv tools/hprof-conv
bin/mksdcard tools/mksdcard bin/mksdcard tools/mksdcard
# other tools # other tools

View File

@@ -74,7 +74,8 @@ function build() {
make -j 4 emulator || die "Build failed" make -j 4 emulator || die "Build failed"
# Disable parallel build: it generates "permission denied" issues when # Disable parallel build: it generates "permission denied" issues when
# multiple "ar.exe" are running in parallel. # multiple "ar.exe" are running in parallel.
make prebuilt adb fastboot aidl aapt dexdump dmtracedump mksdcard sqlite3 || die "Build failed" make prebuilt adb fastboot aidl aapt dexdump dmtracedump hprof-conv mksdcard sqlite3 \
|| die "Build failed"
} }
function package() { function package() {
@@ -114,7 +115,7 @@ function package() {
# Remove obsolete stuff from tools & platform # Remove obsolete stuff from tools & platform
TOOLS="$DEST/tools" TOOLS="$DEST/tools"
LIB="$DEST/tools/lib" LIB="$DEST/tools/lib"
rm -v "$TOOLS"/{adb,emulator,traceview,draw9patch,hierarchyviewer,apkbuilder,ddms,dmtracedump,mksdcard,sqlite3,android} rm -v "$TOOLS"/{adb,emulator,traceview,draw9patch,hierarchyviewer,apkbuilder,ddms,dmtracedump,hprof-conv,mksdcard,sqlite3,android}
rm -v --force "$LIB"/*.so "$LIB"/*.jnilib rm -v --force "$LIB"/*.so "$LIB"/*.jnilib
rm -v "$PLATFORM_TOOLS"/{aapt,aidl,dx,dexdump} rm -v "$PLATFORM_TOOLS"/{aapt,aidl,dx,dexdump}

280
docs/howto_build_SDK.txt Normal file
View File

@@ -0,0 +1,280 @@
Subject: How to build an Android SDK & ADT Eclipse plugin.
Date: 2009/03/27
Table of content:
0- License
1- Foreword
2- Building an SDK for MacOS and Linux
3- Building an SDK for Windows
4- Building an ADT plugin for Eclipse
5- Conclusion
----------
0- License
----------
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.
-----------
1- Foreword
-----------
This document explains how to build the Android SDK and the ADT Eclipse plugin.
It is designed for advanced users which are proficient with command-line
operations and know how to setup the pre-required software.
Basically it's not trivial yet when done right it's not that complicated.
--------------------------------------
2- Building an SDK for MacOS and Linux
--------------------------------------
First, setup your development environment and get the Android source code from
git as explained here:
http://source.android.com/download
For example for the cupcake branch:
$ mkdir ~/my-android-git
$ cd ~/my-android-git
$ repo init -u git://android.git.kernel.org/platform/manifest.git -b cupcake
$ repo sync
Then once you have all the source, simply build the SDK using:
$ cd ~/my-android-git
$ . build/envsetup.sh
$ make sdk
This will take a while, maybe between 20 minutes and several hours depending on
your machine. After a while you'll see this in the output:
Package SDK: out/host/darwin-x86/sdk/android-sdk_eng.<build-id>_mac-x86.zip
Some options:
- Depending on your machine you can tell 'make' to build more things in
parallel, e.g. if you have a dual core, use "make -j4 sdk" to build faster.
- You can define "BUILD_NUMBER" to control the build identifier that gets
incorporated in the resulting archive. The default is to use your username.
One suggestion is to include the date, e.g.:
$ export BUILD_NUMBER=${USER}-`date +%Y%m%d-%H%M%S`
There are certain characters you should avoid in the build number, typically
everything that might confuse 'make' or your shell. So for example avoid
punctuation and characters like $ & : / \ < > , and .
------------------------------
3- Building an SDK for Windows
------------------------------
A- SDK pre-requisite
--------------------
First you need to build an SDK for MacOS and Linux. The Windows build works by
updating an existing MacOS or Linux SDK zip file and replacing the unix
binaries by Windows binaries.
B- Cygwin pre-requisite & code checkout
---------------------------------------
Second you need to install Cygwin and configure it:
- Get the installer at http://sources.redhat.com/cygwin/
- When installing Cygwin, set Default Text File Type to Unix/binary, not DOS/text.
This is really important, otherwise you will get errors when trying to
checkout code using git.
- Packages that you must install or not:
- Required packages: autoconf, bison, curl, flex, gcc, g++, git, gnupg, make,
mingw-zlib, python, zip, unzip.
- Suggested extra packages: diffutils, emacs, openssh, rsync, vim, wget.
- Packages that must not be installed: readline.
Once you installed Cygwin properly, checkout the code from git as you did
for MacOS or Linux. Make sure to get the same branch, and if possible keep
it as close to the other one as possible:
$ mkdir ~/my-android-git
$ cd ~/my-android-git
$ repo init -u git://android.git.kernel.org/platform/manifest.git -b cupcake
$ repo sync
C- Building the Windows SDK
---------------------------
Now it's time to build that Windows SDK. You need:
- The path to the MacOS or Linux SDK zip.
- A directory where to place the final SDK. It will also hold some temporary
files.
- The build number will be extracted from the SDK zip filename, but this will
only work if that build number has no underscores in it. It is suggested you
just define SDK_NUMBER (and not BUILD_NUMBER!) on the command line before
invoking the script.
Note that the "SDK number" is really a free identifier of your choice. It
doesn't need to be strictly a number. As always it is suggested you avoid
too much punctuation and special shell/make characters. Underscores cannot
be used.
To summarize, the steps on the command line would be something like this:
$ mkdir ~/mysdk
$ export SDK_NUMBER=${USER}-`date +%Y%m%d-%H%M%S`
$ cd ~/my-android-git
$ development/build/tools/make_windows_sdk.sh /path/to/macos/or/linux/sdk.zip ~/mysdk
This will take a while to build some Windows-specific binaries, including the
emulator, unzip the previous zip, rename & replace things and rezip the final
Windows SDK zip file. A typical build time should be around 5-10 minutes.
-------------------------------------
4- Building an ADT plugin for Eclipse
-------------------------------------
Requirements:
- You can currently only build an ADT plugin for Eclipse under Linux.
- You must have a working version of Eclipse 3.4 "ganymede" RCP installed.
- You need X11 to run Eclipse at least once.
- You need a lot of patience. The trick is to do the initial setup correctly
once, after it's a piece of cake.
A- Pre-requisites
-----------------
Note for Ubuntu or Debian users: your apt repository probably only has Eclipse
3.2 available and it's probably not suitable to build plugins in the first
place. Forget that and install a working 3.4 manually as described below.
- Visit http://www.eclipse.org/downloads/ to grab the
"Eclipse for RCP/Plug-in Developers (176 MB)" download for Linux.
32-bit and 64-bit versions are available, depending on your Linux installation.
Note: we've always used a 32-bit one, so use the 64-bit one at your own risk.
Note: Eclipse comes in various editions. Do yourself a favor and just stick
to the RCP for building this plugin. For example the J2EE contains too many
useless features that will get in the way, and the "Java" version lacks some
plugins you need to build other plugins. Please just use the RCP one.
- Unpack "eclipse-rcp-ganymede-SR2-linux-gtk.tar.gz" in the directory of
your choice, e.g.:
$ mkdir ~/eclipse-3.4
$ cd ~/eclipse-3.4
$ tar xvzf eclipse-rcp-ganymede-SR2-linux-gtk.tar.gz
This will create an "eclipse" directory in the current directory.
- Set ECLIPSE_HOME to that "eclipse" directory:
$ export ECLIPSE_HOME=~/eclipse-3.4/eclipse
Note: it is important you set ECLIPSE_HOME before starting the build.
Otherwise the build process will try to download and install its own Eclipse
installation in /buildroot, which is probably limited to root.
- Now, before you can build anything, it is important that you start Eclipse
*manually* once using the same user that you will use to build later. That's
because your Eclipse installation is not finished: Eclipse must be run at
least once to create some files in ~/.eclipse/. So run Eclipse now:
$ ~/eclipse-3.4/eclipse/eclipse &
Wait for it load, create a workspace when requested and then simply quit
using the File > Quit menu. That's it. You won't need to run it manually
again.
B- Building ADT
---------------
Finally, you have Eclipse, it's installed and it created its own config files,
so now you can build your ADT plugin. To do that you'll change directories to
your git repository and invoke the build script by giving it a destination
directory and an optional build number:
$ mkdir ~/mysdk
$ cd ~/my-android-git # <-- this is where you did your "repo sync"
$ development/tools/eclipse/scripts/build_server.sh ~/mysdk $USER
The first argument is the destination directory. It must be absolute. Do not
give a relative destination directory such as "../mysdk". This will make the
Eclipse build fail with a cryptic message:
BUILD SUCCESSFUL
Total time: 1 minute 5 seconds
**** Package in ../mysdk
Error: Build failed to produce ../mysdk/android-eclipse
Aborting
The second argument is the build "number". The example used "$USER" but it
really is a free identifier of your choice. It cannot contain spaces nor
periods (dashes are ok.) If the build number is missing, a build timestamp will
be used instead in the filename.
The build should take something like 5-10 minutes.
When the build succeeds, you'll see something like this at the end of the
output:
ZIP of Update site available at ~/mysdk/android-eclipse-v200903272328.zip
or
ZIP of Update site available at ~/mysdk/android-eclipse-<buildnumber>.zip
When you load the plugin in Eclipse, its feature and plugin name will look like
"com.android.ide.eclipse.adt_0.9.0.v200903272328-<buildnumber>.jar". The
internal plugin ID is always composed of the package, the build timestamp and
then your own build identifier (a.k.a. the "build number"), if provided. This
means successive builds with the same build identifier are incremental and
Eclipse will know how to update to more recent ones.
-------------
5- Conclusion
-------------
This completes the howto guide on building your own SDK and ADT plugin.
Feedback is welcome on the public Android Open Source forums:
http://source.android.com/discuss
If you are upgrading from a pre-cupcake to a cupcake or later SDK please read
the accompanying document "howto_use_cupcake_sdk.txt".
-end-

View File

@@ -0,0 +1,371 @@
Subject: How to build use a Cupcake Android SDK & ADT Eclipse plugin.
Date: 2009/03/27
Table of content:
0- License
1- Foreword
2- Installation steps
3- For Eclipse users
4- For Ant users
5- Targets, AVDs, Emulator changes
6- Conclusion
----------
0- License
----------
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.
-----------
1- Foreword
-----------
This explains how to use the "new" SDK provided starting with cupcake.
The new SDK has as a different structure than the pre-cupcake ones.
This means:
- The new SDK does not work with older Eclipse plugins (ADT 0.8)
- The old SDKs (1.0 and 1.1) do NOT work with this Eclipse plugin (ADT 0.9)
----------------------
2- Installation steps
----------------------
First you will need to grab the zip of the SDK for your platform or build it
yourself. Please refer to the accompanying document "howto_build_SDK.txt" if
needed.
Unzip the SDK somewhere. We'll call that directory "SDK" in command-line
examples.
Grab the new ADT Eclipse plugin zip file or build it yourself. Keep it
somewhere (no need to unzip).
--------------------
3- For Eclipse users
--------------------
Below we'll explain how you can upgrade your Eclipse install to the new plugin.
If you already have a working Eclipse installation with a pre-0.9 ADT,
another suggestion is to simply install a new copy of Eclipse and create a
new empty workspace. This is just a precaution. The update process should
be otherwise harmless.
A- Setting up Eclipse
---------------------
- You must have Eclipse 3.3 or 3.4. Eclipse 3.2 is not longer supported.
There are many flavors, or "editions", of Eclipse. To develop, we'd recommend
the "Java" edition. The "RCP" one is totally suitable too. The J2EE one is
probably overkill.
- If updating an existing Eclipse, use Help > Software Update and please
uninstall the two features of the previous ADT: the "editors" feature and the
ADT feature itself.
=> If you don't you will get a conflict on editors when installing
the new one.
- Using Help > Software Update, add a new "archived site", point it to the new
adt.zip (e.g. android-eclipse-<some-id>.zip), select the "Install" button at
the top right and restart eclipse as needed.
- After it restarts, please use Window > Preferences > Android and select
the new SDK folder that you unzipped in paragraph 2.
B- Updating older projects
--------------------------
If you have pre-0.9 projects in your Eclipse workspace, or if you import them
from your code repository, these projects will fail to build at first.
First right-click on the project and select "Properties":
- In the properties, open the Android panel and select the platform to use.
The SDK comes with a 1.5 platform. Select it and close the properties panel.
- Do a clean build.
The new plugin creates a "gen" folder in your project where it puts the R.java
and all automatically generated AIDL java files. If you get an error such as:
"The type R is already defined"
that means you must check to see if your old R.java or your old auto-generated
AIDL Java files are still present in the "src" folder. If yes, remove them.
Note: this does not apply to your own hand-crafted parcelable AIDL java files.
Note: if you want to reuse the project with an older Eclipse ADT install,
simply remove the "gen" folder from the build path of the project.
C- New Wizards
--------------
The "New Android Project" wizard has been expanded to use the multi-platform
capabilities of the new SDK.
There is now a "New XML File" wizard that lets you create skeleton XML resource
files for your Android projects. This makes it easier to create a new layout, a
new strings file, etc.
Both wizard are available via File > New... as well as new icons in the main
icon bar. If you do not see the new icons, you may need to use Window > Reset
Perspective on your Java perspective.
Please see step 5 "Emulator changes" below for important details on how to run
the emulator.
----------------
4- For Ant users
----------------
A- build.xml has changed
------------------------
You must re-create your build.xml file.
First if you had customized your build.xml, make a copy of it:
$ cd my-project
$ cp build.xml build.xml.old
Then use the new "android" tool to create a new build.xml:
$ SDK/tools/android update project --path /path/to/my-project
or
$ cd my-project
$ SDK/tools/android update project --path .
A "gen" folder will be created the first time you build and your R.java and
your AIDL Java files will be generated in this "gen" folder. You MUST remove
the old R.java and old auto-generated AIDL java files manually. (Note: this
does not apply to your own hand-crafted parcelabe AIDL java files.)
B- Where is activitycreator?
----------------------------
Note that the "activitycreator" tool has been replaced by the new "android"
tool too. Example of how to create a new Ant project:
$ SDK/tools/android create project --path /path/to/my/project --name ProjectName
--package com.mycompany.myapp --activity MyActivityClass
--target 1 --mode activity
Please see paragraph 5 below for important details on how to run the emulator
and the meaning of that "--target 1" parameter.
----------------------------------
5- Targets, AVDs, Emulator changes
----------------------------------
This applies to BOTH Eclipse and Ant users.
One major change with the emulator is that now you must pre-create an "Android
Virtual Device" (a.k.a "AVD") before you run the emulator.
A- What is an AVD and why do I need one?
----------------------------------------
What is an "AVD"? If you forget, just run:
$ SDK/tools/emulator -help-virtual-device
An Android Virtual Device (AVD) models a single virtual device running the
Android platform that has, at least, its own kernel, system image and data
partition.
There is a lot more explanation given by the emulator. Please run the help
command given above to read the rest.
The bottom line is that you can create many emulator configurations, or "AVDs",
each with their own system image and most important each with their own user
data and SD card data. Then you tell Eclipse or the emulator which one to use
to debug or run your applications.
Note for Eclipse users: eventually there will be a user interface to do all of
these operations. For right now, please use the command line interface.
B- Listing targets and AVDs
---------------------------
There is a new tool called "android" in the SDK that lets you know which
"target" and AVDs you can use.
A target is a specific version of Android that you can use. By default the SDK
comes with an "Android 1.5" target, codenamed "cupcake". In the future there
will be more versions of Android to use, e.g. "Android 2.0" or specific add-ons
provided by hardware manufacturers. When you want to run an emulator, you need
to specify a given flavor of Android: this is the "target".
To learn about available targets in your SDK, use this command:
$ SDK/tools/android list targets
This will give you an output such as:
Available Android targets:
[1] Android 1.5
API level: 3
Skins: HVGA (default), HVGA-L, HVGA-P, QVGA-L, QVGA-P
Note the "[1]". Later you will need to reference this as "--target 1" on the
command line.
Similarly you can list the available AVDs:
$ SDK/tools/android list avds
Which might output something as:
Available Android Virtual Devices:
Name: my_avd
Path: C:\Users\<username>\.android\avd\my_avd.avd
Target: Android 1.5 (API level 3)
Skin: 320x480
Sdcard: 16M
C- Creating an AVD
------------------
To create a configuration:
$ SDK/tools/android create avd --name my_avd_name --target 1
where "target 1" is the index of a target listed by "android list targets".
The AVD name is purely an identifier used to refer to the AVD later.
Since it is used as directory name, please avoid using shell or path specific
characters.
To learn the various options available when creating an AVD, simply type:
$ SDK/tools/android create avd
The android tool will automatically print an explanation of required arguments.
D- Invoking an AVD from the command-line
----------------------------------------
To use this AVD in the emulator from the command-line, type:
$ SDK/tools/emulator @my_avd_name
For more options, please consult the emulator help:
$ SDK/tools/emulator -help-virtual-device
E- Invoking an AVD from Eclipse
-------------------------------
By default Android projects in Eclipse have an "automatic target" mode.
In this mode, when a project is deployed in debug or run, it checks:
- If there's one running device or emulator, this is used for deployment.
- If there's more than one running device or emulator, a "device chooser" is
shown to let the user select which one to use.
- If there are no running devices or emulators, ADT looks at available AVDs.
If one matches the project configuration (e.g. same API level), it is
automatically used.
Alternatively you can edit the "launch configuration" on your Android project
in Eclipse by selecting the menu Run > Run Configurations. In the "target" tab
of the configuration, you can choose:
- Manual or automatic targetting mode.
- Manual means to always present the device chooser.
- Automatic is the behavior explained above.
- In automatic mode, which AVD is preferred. If none is selected, the first
suitable is used.
F- AVD concurrency
------------------
You can no longer run several emulators at the same time on the same
configuration.
Before this used to put the second or more emulators in a transient read-only
mode that would not save user data.
Now you just need to create as many AVDs as you want to run emulators.
For example if you are working on a client/server application for Android, you
could create a "client" AVD and a "server" AVD then run them both at once. The
emulator window will show you the AVD name so that you know which one is which.
Example:
$ SDK/tools/android create avd --name client --target 1 --sdcard 16M --skin HVGA
$ SDK/tools/android create avd --name server --target 1 --sdcard 32M --skin HVGA-P
$ SDK/tools/emulator @server &
$ SDK/tools/emulator @client &
-------------
6- Conclusion
-------------
This completes the howto guide on how to use the new Cupcake SDK.
Feedback is welcome on the public Android Open Source forums:
http://source.android.com/discuss
-end-

View File

@@ -89,7 +89,10 @@ public class ExampleAppWidgetConfigure extends Activity {
saveTitlePref(ExampleAppWidgetConfigure.this, mAppWidgetId, saveTitlePref(ExampleAppWidgetConfigure.this, mAppWidgetId,
mAppWidgetPrefix.getText().toString()); mAppWidgetPrefix.getText().toString());
setResult(RESULT_OK); // Make sure we pass back the original appWidgetId
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish(); finish();
} }
}; };

View File

@@ -10,7 +10,7 @@ LOCAL_PATH := $(call my-dir)
######################## ########################
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := tests.xml LOCAL_MODULE := test_defs.xml
LOCAL_MODULE_TAGS := tests LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_CLASS := ETC LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(local_target_dir) LOCAL_MODULE_PATH := $(local_target_dir)

View File

@@ -297,8 +297,7 @@ class AdbInterface:
WaitForResponseTimedOutError if wait_time elapses and pm still does not WaitForResponseTimedOutError if wait_time elapses and pm still does not
respond. respond.
""" """
logger.Log("Waiting for device package manager for %s seconds..." logger.Log("Waiting for device package manager...")
% wait_time)
self.SendCommand("wait-for-device") self.SendCommand("wait-for-device")
# Now the device is there, but may not be running. # Now the device is there, but may not be running.
# Query the package manager with a basic command # Query the package manager with a basic command
@@ -315,7 +314,8 @@ class AdbInterface:
time.sleep(wait_period) time.sleep(wait_period)
attempts += 1 attempts += 1
if not pm_found: if not pm_found:
raise errors.WaitForResponseTimedOutError raise errors.WaitForResponseTimedOutError(
"Package manager did not respond after %s seconds" % wait_time)
def Sync(self, retry_count=3): def Sync(self, retry_count=3):
"""Perform a adb sync. """Perform a adb sync.
@@ -331,13 +331,12 @@ class AdbInterface:
output = self.SendCommand("sync", retry_count=retry_count) output = self.SendCommand("sync", retry_count=retry_count)
if "Read-only file system" in output: if "Read-only file system" in output:
logger.SilentLog(output) logger.SilentLog(output)
logger.Log("adb sync failed due to read only fs, retrying") logger.Log("Remounting read-only filesystem")
self.SendCommand("remount") self.SendCommand("remount")
output = self.SendCommand("sync", retry_count=retry_count) output = self.SendCommand("sync", retry_count=retry_count)
if "No space left on device" in output: if "No space left on device" in output:
logger.SilentLog(output) logger.SilentLog(output)
logger.Log("adb sync failed due to no space on device, trying shell" + logger.Log("Restarting device runtime")
" start/stop")
self.SendShellCommand("stop", retry_count=retry_count) self.SendShellCommand("stop", retry_count=retry_count)
output = self.SendCommand("sync", retry_count=retry_count) output = self.SendCommand("sync", retry_count=retry_count)
self.SendShellCommand("start", retry_count=retry_count) self.SendShellCommand("start", retry_count=retry_count)
@@ -345,3 +344,15 @@ class AdbInterface:
logger.SilentLog(output) logger.SilentLog(output)
self.WaitForDevicePm() self.WaitForDevicePm()
return output return output
def IsDevicePresent(self):
"""Check if targeted device is present.
Returns:
True if device is present, False otherwise.
"""
output = self.SendShellCommand("ls", retry_count=0)
if output.startswith("error:"):
return False
else:
return True

View File

@@ -70,6 +70,27 @@ class CoverageGenerator(object):
def EnableCoverageBuild(self): def EnableCoverageBuild(self):
"""Enable building an Android target with code coverage instrumentation.""" """Enable building an Android target with code coverage instrumentation."""
os.environ[self._EMMA_BUILD_FLAG] = "true" os.environ[self._EMMA_BUILD_FLAG] = "true"
#TODO: can emma.jar automagically be added to bootclasspath here?
def TestDeviceCoverageSupport(self):
"""Check if device has support for generating code coverage metrics.
Currently this will check if the emma.jar file is on the device's boot
classpath.
Returns:
True if device can support code coverage. False otherwise.
"""
output = self._adb.SendShellCommand("cat init.rc | grep BOOTCLASSPATH | "
"grep emma.jar")
if len(output) > 0:
return True
else:
logger.Log("Error: Targeted device does not have emma.jar on its "
"BOOTCLASSPATH.")
logger.Log("Modify the BOOTCLASSPATH entry in system/core/rootdir/init.rc"
" to add emma.jar")
return False
def ExtractReport(self, test_suite, def ExtractReport(self, test_suite,
device_coverage_path=_DEVICE_COVERAGE_PATH, device_coverage_path=_DEVICE_COVERAGE_PATH,

View File

@@ -25,6 +25,7 @@ import datetime
_LOG_FILE = None _LOG_FILE = None
_verbose = False _verbose = False
_log_time = True
def Init(log_file_path): def Init(log_file_path):
"""Set the path to the log file""" """Set the path to the log file"""
@@ -57,8 +58,13 @@ def _WriteLog(msg):
def _PrependTimeStamp(log_string): def _PrependTimeStamp(log_string):
"""Returns the log_string prepended with current timestamp """ """Returns the log_string prepended with current timestamp """
global _log_time
if _log_time:
return "# %s: %s" % (datetime.datetime.now().strftime("%m/%d/%y %H:%M:%S"), return "# %s: %s" % (datetime.datetime.now().strftime("%m/%d/%y %H:%M:%S"),
log_string) log_string)
else:
# timestamp logging disabled
return log_string
def SilentLog(new_str): def SilentLog(new_str):
"""Silently log new_str. Unless verbose mode is enabled, will log new_str """Silently log new_str. Unless verbose mode is enabled, will log new_str
@@ -78,6 +84,11 @@ def SetVerbose(new_verbose=True):
global _verbose global _verbose
_verbose = new_verbose _verbose = new_verbose
def SetTimestampLogging(new_timestamp=True):
""" Enable or disable outputting a timestamp with each log entry"""
global _log_time
_log_time = new_timestamp
def main(): def main():
pass pass

View File

@@ -41,23 +41,27 @@ class TestRunner(object):
# file path to android core platform tests, relative to android build root # file path to android core platform tests, relative to android build root
# TODO move these test data files to another directory # TODO move these test data files to another directory
_CORE_TEST_PATH = os.path.join("development", "testrunner", "tests.xml") _CORE_TEST_PATH = os.path.join("development", "testrunner", "test_defs.xml")
# vendor glob file path patterns to tests, relative to android # vendor glob file path patterns to tests, relative to android
# build root # build root
_VENDOR_TEST_PATH = os.path.join("vendor", "*", "tests", "testinfo", _VENDOR_TEST_PATH = os.path.join("vendor", "*", "tests", "testinfo",
"tests.xml") "test_defs.xml")
_RUNTEST_USAGE = ( _RUNTEST_USAGE = (
"usage: runtest.py [options] short-test-name[s]\n\n" "usage: runtest.py [options] short-test-name[s]\n\n"
"The runtest script works in two ways. You can query it " "The runtest script works in two ways. You can query it "
"for a list of tests, or you can launch one or more tests.") "for a list of tests, or you can launch one or more tests.")
def __init__(self):
# disable logging of timestamp
logger.SetTimestampLogging(False)
def _ProcessOptions(self): def _ProcessOptions(self):
"""Processes command-line options.""" """Processes command-line options."""
# TODO error messages on once-only or mutually-exclusive options. # TODO error messages on once-only or mutually-exclusive options.
user_test_default = os.path.join(os.environ.get("HOME"), ".android", user_test_default = os.path.join(os.environ.get("HOME"), ".android",
"tests.xml") "test_defs.xml")
parser = optparse.OptionParser(usage=self._RUNTEST_USAGE) parser = optparse.OptionParser(usage=self._RUNTEST_USAGE)
@@ -149,7 +153,7 @@ class TestRunner(object):
try: try:
known_tests = test_defs.TestDefinitions() known_tests = test_defs.TestDefinitions()
known_tests.Parse(core_test_path) known_tests.Parse(core_test_path)
# read all <android root>/vendor/*/tests/testinfo/tests.xml paths # read all <android root>/vendor/*/tests/testinfo/test_defs.xml paths
vendor_tests_pattern = os.path.join(self._root_path, vendor_tests_pattern = os.path.join(self._root_path,
self._VENDOR_TEST_PATH) self._VENDOR_TEST_PATH)
test_file_paths = glob.glob(vendor_tests_pattern) test_file_paths = glob.glob(vendor_tests_pattern)
@@ -178,10 +182,14 @@ class TestRunner(object):
self._coverage_gen.EnableCoverageBuild() self._coverage_gen.EnableCoverageBuild()
self._AddBuildTarget(self._coverage_gen.GetEmmaBuildPath(), target_set) self._AddBuildTarget(self._coverage_gen.GetEmmaBuildPath(), target_set)
target_build_string = " ".join(list(target_set)) target_build_string = " ".join(list(target_set))
logger.Log("Building %s" % target_build_string) logger.Log("mmm %s" % target_build_string)
cmd = 'ONE_SHOT_MAKEFILE="%s" make -C "%s" files' % (target_build_string, cmd = 'ONE_SHOT_MAKEFILE="%s" make -C "%s" files' % (target_build_string,
self._root_path) self._root_path)
if not self._options.preview: if self._options.preview:
# in preview mode, just display to the user what command would have been
# run
logger.Log("adb sync")
else:
run_command.RunCommand(cmd, return_output=False) run_command.RunCommand(cmd, return_output=False)
logger.Log("Syncing to device...") logger.Log("Syncing to device...")
self._adb.Sync() self._adb.Sync()
@@ -259,6 +267,10 @@ class TestRunner(object):
self._DumpTests() self._DumpTests()
return return
if not self._adb.IsDevicePresent():
logger.Log("Error: specified device cannot be found")
return
if not self._options.skip_build: if not self._options.skip_build:
self._DoBuild() self._DoBuild()

View File

@@ -49,6 +49,7 @@ public class RemoteAndroidTestRunner {
private static final String LOG_ARG_NAME = "log"; private static final String LOG_ARG_NAME = "log";
private static final String DEBUG_ARG_NAME = "debug"; private static final String DEBUG_ARG_NAME = "debug";
private static final String COVERAGE_ARG_NAME = "coverage"; private static final String COVERAGE_ARG_NAME = "coverage";
private static final String PACKAGE_ARG_NAME = "package";
/** /**
* Creates a remote Android test runner. * Creates a remote Android test runner.
@@ -145,6 +146,16 @@ public class RemoteAndroidTestRunner {
setClassName(className + METHOD_SEPARATOR + testName); setClassName(className + METHOD_SEPARATOR + testName);
} }
/**
* Sets to run all tests in specified package
* Must be called before 'run'.
*
* @param packageName fully qualified package name (eg x.y.z)
*/
public void setTestPackageName(String packageName) {
addInstrumentationArg(PACKAGE_ARG_NAME, packageName);
}
/** /**
* Adds a argument to include in instrumentation command. * Adds a argument to include in instrumentation command.
* <p/> * <p/>

View File

@@ -17,16 +17,17 @@
package com.android.ddmlib.testrunner; package com.android.ddmlib.testrunner;
import com.android.ddmlib.Client; import com.android.ddmlib.Client;
import com.android.ddmlib.Device.DeviceState;
import com.android.ddmlib.FileListingService; import com.android.ddmlib.FileListingService;
import com.android.ddmlib.IDevice; import com.android.ddmlib.IDevice;
import com.android.ddmlib.IShellOutputReceiver; import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.log.LogReceiver;
import com.android.ddmlib.RawImage; import com.android.ddmlib.RawImage;
import com.android.ddmlib.SyncService; import com.android.ddmlib.SyncService;
import com.android.ddmlib.Device.DeviceState;
import com.android.ddmlib.log.LogReceiver;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import junit.framework.TestCase; import junit.framework.TestCase;
/** /**
@@ -80,6 +81,17 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
testName, TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand()); testName, TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand());
} }
/**
* Test the building of the instrumentation runner command with test package set.
*/
public void testRunWithPackage() {
final String packageName = "foo.test";
mRunner.setTestPackageName(packageName);
mRunner.run(new EmptyListener());
assertStringsEquals(String.format("am instrument -w -r -e package %s %s/%s", packageName,
TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand());
}
/** /**
* Test the building of the instrumentation runner command with extra argument added. * Test the building of the instrumentation runner command with extra argument added.
*/ */

View File

@@ -43,7 +43,8 @@ Require-Bundle: com.android.ide.eclipse.ddms,
org.eclipse.jdt.junit, org.eclipse.jdt.junit,
org.eclipse.jdt.junit.runtime, org.eclipse.jdt.junit.runtime,
org.eclipse.ltk.core.refactoring, org.eclipse.ltk.core.refactoring,
org.eclipse.ltk.ui.refactoring org.eclipse.ltk.ui.refactoring,
org.eclipse.core.expressions
Eclipse-LazyStart: true Eclipse-LazyStart: true
Export-Package: com.android.ide.eclipse.adt;x-friends:="com.android.ide.eclipse.tests", Export-Package: com.android.ide.eclipse.adt;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
@@ -52,6 +53,7 @@ Export-Package: com.android.ide.eclipse.adt;x-friends:="com.android.ide.eclipse.
com.android.ide.eclipse.adt.project.internal;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.project.internal;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.sdk;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.sdk;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.wizards.newproject;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.wizards.newproject;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.ui;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.common;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.common;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.common.project;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.common.project;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.common.resources;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.common.resources;x-friends:="com.android.ide.eclipse.tests",
@@ -77,7 +79,6 @@ Export-Package: com.android.ide.eclipse.adt;x-friends:="com.android.ide.eclipse.
com.android.ide.eclipse.editors.ui;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.editors.ui;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.ui.tree;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.editors.ui.tree;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.uimodel;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.editors.uimodel;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.wizards;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.xml;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.editors.xml;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.xml.descriptors;x-friends:="com.android.ide.eclipse.tests" com.android.ide.eclipse.editors.xml.descriptors;x-friends:="com.android.ide.eclipse.tests"

View File

@@ -551,7 +551,7 @@
point="org.eclipse.debug.ui.launchShortcuts"> point="org.eclipse.debug.ui.launchShortcuts">
<shortcut <shortcut
class="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitLaunchShortcut" class="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitLaunchShortcut"
icon="icons/android.png" icon="icons/androidjunit.png"
id="com.android.ide.eclipse.adt.junit.launchShortcut" id="com.android.ide.eclipse.adt.junit.launchShortcut"
label="Android JUnit Test" label="Android JUnit Test"
modes="run,debug"> modes="run,debug">
@@ -563,7 +563,7 @@
<adapt type="org.eclipse.jdt.core.IJavaElement"> <adapt type="org.eclipse.jdt.core.IJavaElement">
<test property="org.eclipse.jdt.core.isInJavaProjectWithNature" value="com.android.ide.eclipse.adt.AndroidNature"/> <test property="org.eclipse.jdt.core.isInJavaProjectWithNature" value="com.android.ide.eclipse.adt.AndroidNature"/>
<test property="org.eclipse.jdt.core.hasTypeOnClasspath" value="junit.framework.Test"/> <test property="org.eclipse.jdt.core.hasTypeOnClasspath" value="junit.framework.Test"/>
<test property="org.eclipse.jdt.junit.canLaunchAsJUnit" forcePluginActivation="true"/> <test property="com.android.ide.eclipse.adt.canLaunchAsJUnit"/>
</adapt> </adapt>
</iterate> </iterate>
</with> </with>
@@ -595,4 +595,14 @@
id="com.android.ide.eclipse.adt.refactoring.extract.string"> id="com.android.ide.eclipse.adt.refactoring.extract.string">
</contribution> </contribution>
</extension> </extension>
<extension
point="org.eclipse.core.expressions.propertyTesters">
<propertyTester
properties="isTest,canLaunchAsJUnit"
namespace="com.android.ide.eclipse.adt"
type="org.eclipse.core.runtime.IAdaptable"
class="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitPropertyTester"
id="com.android.ide.eclipse.adt.AndroidJUnitPropertyTester">
</propertyTester>
</extension>
</plugin> </plugin>

View File

@@ -29,8 +29,8 @@ import com.android.ide.eclipse.adt.sdk.AndroidTargetParser;
import com.android.ide.eclipse.adt.sdk.LoadStatus; import com.android.ide.eclipse.adt.sdk.LoadStatus;
import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener; import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.adt.ui.EclipseUiHelper;
import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.EclipseUiHelper;
import com.android.ide.eclipse.common.SdkStatsHelper; import com.android.ide.eclipse.common.SdkStatsHelper;
import com.android.ide.eclipse.common.StreamHelper; import com.android.ide.eclipse.common.StreamHelper;
import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.common.project.BaseProjectHelper;

View File

@@ -24,7 +24,6 @@ import com.android.ide.eclipse.adt.launch.junit.runtime.RemoteAdtTestRunner;
import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.ILaunchManager;
@@ -39,18 +38,15 @@ import org.eclipse.jdt.launching.VMRunnerConfiguration;
*/ */
class AndroidJUnitLaunchAction implements IAndroidLaunchAction { class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
private String mTestPackage; private final AndroidJUnitLaunchInfo mLaunchInfo;
private String mRunner;
/** /**
* Creates a AndroidJUnitLaunchAction. * Creates a AndroidJUnitLaunchAction.
* *
* @param testPackage the Android application package that contains the tests to run * @param launchInfo the {@link AndroidJUnitLaunchInfo} for the JUnit run
* @param runner the InstrumentationTestRunner that will execute the tests
*/ */
public AndroidJUnitLaunchAction(String testPackage, String runner) { public AndroidJUnitLaunchAction(AndroidJUnitLaunchInfo launchInfo) {
mTestPackage = testPackage; mLaunchInfo = launchInfo;
mRunner = runner;
} }
/** /**
@@ -60,14 +56,18 @@ class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
* @see IAndroidLaunchAction#doLaunchAction(DelayedLaunchInfo, IDevice) * @see IAndroidLaunchAction#doLaunchAction(DelayedLaunchInfo, IDevice)
*/ */
public boolean doLaunchAction(DelayedLaunchInfo info, IDevice device) { public boolean doLaunchAction(DelayedLaunchInfo info, IDevice device) {
String msg = String.format("Launching instrumentation %s on device %s", mRunner, String msg = String.format("Launching instrumentation %s on device %s",
device.getSerialNumber()); mLaunchInfo.getRunner(), device.getSerialNumber());
AdtPlugin.printToConsole(info.getProject(), msg); AdtPlugin.printToConsole(info.getProject(), msg);
try { try {
JUnitLaunchDelegate junitDelegate = new JUnitLaunchDelegate(info, device); mLaunchInfo.setDebugMode(info.isDebugMode());
mLaunchInfo.setDevice(info.getDevice());
mLaunchInfo.setLaunch(info.getLaunch());
JUnitLaunchDelegate junitDelegate = new JUnitLaunchDelegate(mLaunchInfo);
final String mode = info.isDebugMode() ? ILaunchManager.DEBUG_MODE : final String mode = info.isDebugMode() ? ILaunchManager.DEBUG_MODE :
ILaunchManager.RUN_MODE; ILaunchManager.RUN_MODE;
junitDelegate.launch(info.getLaunch().getLaunchConfiguration(), mode, info.getLaunch(), junitDelegate.launch(info.getLaunch().getLaunchConfiguration(), mode, info.getLaunch(),
info.getMonitor()); info.getMonitor());
@@ -82,20 +82,18 @@ class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
* {@inheritDoc} * {@inheritDoc}
*/ */
public String getLaunchDescription() { public String getLaunchDescription() {
return String.format("%s JUnit launch", mRunner); return String.format("%s JUnit launch", mLaunchInfo.getRunner());
} }
/** /**
* Extends the JDT JUnit launch delegate to allow for JUnit UI reuse. * Extends the JDT JUnit launch delegate to allow for JUnit UI reuse.
*/ */
private class JUnitLaunchDelegate extends JUnitLaunchConfigurationDelegate { private static class JUnitLaunchDelegate extends JUnitLaunchConfigurationDelegate {
private IDevice mDevice; private AndroidJUnitLaunchInfo mLaunchInfo;
private DelayedLaunchInfo mLaunchInfo;
public JUnitLaunchDelegate(DelayedLaunchInfo info, IDevice device) { public JUnitLaunchDelegate(AndroidJUnitLaunchInfo launchInfo) {
mLaunchInfo = info; mLaunchInfo = launchInfo;
mDevice = device;
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -110,34 +108,28 @@ class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
/** /**
* {@inheritDoc} * {@inheritDoc}
* @throws CoreException
* @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#verifyMainTypeName(org.eclipse.debug.core.ILaunchConfiguration) * @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#verifyMainTypeName(org.eclipse.debug.core.ILaunchConfiguration)
*/ */
@Override @Override
public String verifyMainTypeName(ILaunchConfiguration configuration) throws CoreException { public String verifyMainTypeName(ILaunchConfiguration configuration) {
return "com.android.ide.eclipse.adt.junit.internal.runner.RemoteAndroidTestRunner"; //$NON-NLS-1$ return "com.android.ide.eclipse.adt.junit.internal.runner.RemoteAndroidTestRunner"; //$NON-NLS-1$
} }
/** /**
* Overrides parent to return a VM Runner implementation which launches a thread, rather * Overrides parent to return a VM Runner implementation which launches a thread, rather
* than a separate VM process * than a separate VM process
* @throws CoreException
*/ */
@Override @Override
public IVMRunner getVMRunner(ILaunchConfiguration configuration, String mode) public IVMRunner getVMRunner(ILaunchConfiguration configuration, String mode) {
throws CoreException { return new VMTestRunner(mLaunchInfo);
return new VMTestRunner(new AndroidJUnitLaunchInfo(mLaunchInfo.getProject(),
mTestPackage, mRunner, mLaunchInfo.isDebugMode(), mDevice));
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* @throws CoreException
* @see org.eclipse.debug.core.model.LaunchConfigurationDelegate#getLaunch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String) * @see org.eclipse.debug.core.model.LaunchConfigurationDelegate#getLaunch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String)
*/ */
@Override @Override
public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) {
throws CoreException {
return mLaunchInfo.getLaunch(); return mLaunchInfo.getLaunch();
} }
} }
@@ -161,7 +153,7 @@ class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
IProgressMonitor monitor) throws CoreException { IProgressMonitor monitor) throws CoreException {
TestRunnerProcess runnerProcess = TestRunnerProcess runnerProcess =
new TestRunnerProcess(config, launch, mJUnitInfo); new TestRunnerProcess(config, mJUnitInfo);
runnerProcess.start(); runnerProcess.start();
launch.addProcess(runnerProcess); launch.addProcess(runnerProcess);
} }
@@ -173,15 +165,12 @@ class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
private static class TestRunnerProcess extends Thread implements IProcess { private static class TestRunnerProcess extends Thread implements IProcess {
private final VMRunnerConfiguration mRunConfig; private final VMRunnerConfiguration mRunConfig;
private final ILaunch mLaunch;
private final AndroidJUnitLaunchInfo mJUnitInfo; private final AndroidJUnitLaunchInfo mJUnitInfo;
private RemoteAdtTestRunner mTestRunner = null; private RemoteAdtTestRunner mTestRunner = null;
private boolean mIsTerminated = false; private boolean mIsTerminated = false;
TestRunnerProcess(VMRunnerConfiguration runConfig, ILaunch launch, TestRunnerProcess(VMRunnerConfiguration runConfig, AndroidJUnitLaunchInfo info) {
AndroidJUnitLaunchInfo info) {
mRunConfig = runConfig; mRunConfig = runConfig;
mLaunch = launch;
mJUnitInfo = info; mJUnitInfo = info;
} }
@@ -194,10 +183,9 @@ class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
/** /**
* {@inheritDoc} * {@inheritDoc}
* @throws DebugException
* @see org.eclipse.debug.core.model.IProcess#getExitValue() * @see org.eclipse.debug.core.model.IProcess#getExitValue()
*/ */
public int getExitValue() throws DebugException { public int getExitValue() {
return 0; return 0;
} }
@@ -205,14 +193,14 @@ class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
* @see org.eclipse.debug.core.model.IProcess#getLabel() * @see org.eclipse.debug.core.model.IProcess#getLabel()
*/ */
public String getLabel() { public String getLabel() {
return mLaunch.getLaunchMode(); return mJUnitInfo.getLaunch().getLaunchMode();
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#getLaunch() * @see org.eclipse.debug.core.model.IProcess#getLaunch()
*/ */
public ILaunch getLaunch() { public ILaunch getLaunch() {
return mLaunch; return mJUnitInfo.getLaunch();
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -254,10 +242,9 @@ class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
/** /**
* {@inheritDoc} * {@inheritDoc}
* @throws DebugException
* @see org.eclipse.debug.core.model.ITerminate#terminate() * @see org.eclipse.debug.core.model.ITerminate#terminate()
*/ */
public void terminate() throws DebugException { public void terminate() {
if (mTestRunner != null) { if (mTestRunner != null) {
mTestRunner.terminate(); mTestRunner.terminate();
} }
@@ -274,3 +261,4 @@ class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
} }
} }
} }

View File

@@ -22,6 +22,7 @@ import com.android.ide.eclipse.adt.launch.AndroidLaunchConfiguration;
import com.android.ide.eclipse.adt.launch.AndroidLaunchController; import com.android.ide.eclipse.adt.launch.AndroidLaunchController;
import com.android.ide.eclipse.adt.launch.IAndroidLaunchAction; import com.android.ide.eclipse.adt.launch.IAndroidLaunchAction;
import com.android.ide.eclipse.adt.launch.LaunchConfigDelegate; import com.android.ide.eclipse.adt.launch.LaunchConfigDelegate;
import com.android.ide.eclipse.adt.launch.junit.runtime.AndroidJUnitLaunchInfo;
import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.common.project.BaseProjectHelper;
@@ -32,8 +33,11 @@ import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants; import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry; import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
/** /**
* Run configuration that can execute JUnit tests on an Android platform. * Run configuration that can execute JUnit tests on an Android platform.
@@ -47,6 +51,7 @@ public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate {
/** Launch config attribute that stores instrumentation runner. */ /** Launch config attribute that stores instrumentation runner. */
static final String ATTR_INSTR_NAME = AdtPlugin.PLUGIN_ID + ".instrumentation"; //$NON-NLS-1$ static final String ATTR_INSTR_NAME = AdtPlugin.PLUGIN_ID + ".instrumentation"; //$NON-NLS-1$
private static final String EMPTY_STRING = ""; //$NON-NLS-1$ private static final String EMPTY_STRING = ""; //$NON-NLS-1$
@Override @Override
@@ -55,7 +60,7 @@ public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate {
AndroidLaunchConfiguration config, AndroidLaunchController controller, AndroidLaunchConfiguration config, AndroidLaunchController controller,
IFile applicationPackage, AndroidManifestParser manifestParser) { IFile applicationPackage, AndroidManifestParser manifestParser) {
String testPackage = manifestParser.getPackage(); String appPackage = manifestParser.getPackage();
String runner = getRunner(project, configuration, manifestParser); String runner = getRunner(project, configuration, manifestParser);
if (runner == null) { if (runner == null) {
AdtPlugin.displayError("Android Launch", AdtPlugin.displayError("Android Launch",
@@ -63,14 +68,62 @@ public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate {
androidLaunch.stopLaunch(); androidLaunch.stopLaunch();
return; return;
} }
AndroidJUnitLaunchInfo junitLaunchInfo = new AndroidJUnitLaunchInfo(project, appPackage,
runner);
junitLaunchInfo.setTestClass(getTestClass(configuration));
junitLaunchInfo.setTestPackage(getTestPackage(configuration));
junitLaunchInfo.setTestMethod(getTestMethod(configuration));
IAndroidLaunchAction junitLaunch = new AndroidJUnitLaunchAction(testPackage, runner); IAndroidLaunchAction junitLaunch = new AndroidJUnitLaunchAction(junitLaunchInfo);
controller.launch(project, mode, applicationPackage, manifestParser.getPackage(), controller.launch(project, mode, applicationPackage, manifestParser.getPackage(),
manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(), manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(),
junitLaunch, config, androidLaunch, monitor); junitLaunch, config, androidLaunch, monitor);
} }
/**
* Returns the test package stored in the launch configuration, or <code>null</code> if not
* specified.
*
* @param configuration the {@link ILaunchConfiguration} to retrieve the test package info from
* @return the test package or <code>null</code>.
*/
private String getTestPackage(ILaunchConfiguration configuration) {
// try to retrieve a package name from the JUnit container attribute
String containerHandle = getStringLaunchAttribute(
JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, configuration);
if (containerHandle != null && containerHandle.length() > 0) {
IJavaElement element = JavaCore.create(containerHandle);
// containerHandle could be a IProject, check if its a java package
if (element.getElementType() == IJavaElement.PACKAGE_FRAGMENT) {
return element.getElementName();
}
}
return null;
}
/**
* Returns the test class stored in the launch configuration.
*
* @param configuration the {@link ILaunchConfiguration} to retrieve the test class info from
* @return the test class. <code>null</code> if not specified.
*/
private String getTestClass(ILaunchConfiguration configuration) {
return getStringLaunchAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
configuration);
}
/**
* Returns the test method stored in the launch configuration.
*
* @param configuration the {@link ILaunchConfiguration} to retrieve the test method info from
* @return the test method. <code>null</code> if not specified.
*/
private String getTestMethod(ILaunchConfiguration configuration) {
return getStringLaunchAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME,
configuration);
}
/** /**
* Gets a instrumentation runner for the launch. * Gets a instrumentation runner for the launch.
* <p/> * <p/>
@@ -114,11 +167,29 @@ public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate {
} }
private String getRunnerFromConfig(ILaunchConfiguration configuration) throws CoreException { private String getRunnerFromConfig(ILaunchConfiguration configuration) throws CoreException {
String runner = configuration.getAttribute(ATTR_INSTR_NAME, EMPTY_STRING); return getStringLaunchAttribute(ATTR_INSTR_NAME, configuration);
if (runner.length() < 1) { }
/**
* Helper method to retrieve a string attribute from the launch configuration
*
* @param attributeName name of the launch attribute
* @param configuration the {@link ILaunchConfiguration} to retrieve the attribute from
* @return the attribute's value. <code>null</code> if not found.
*/
private String getStringLaunchAttribute(String attributeName,
ILaunchConfiguration configuration) {
try {
String attrValue = configuration.getAttribute(attributeName, EMPTY_STRING);
if (attrValue.length() < 1) {
return null; return null;
} }
return runner; return attrValue;
} catch (CoreException e) {
AdtPlugin.log(e, String.format("Error when retrieving launch info %1$s", //$NON-NLS-1$
attributeName));
}
return null;
} }
/** /**

View File

@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.launch.junit;
import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.launch.MainLaunchConfigTab; import com.android.ide.eclipse.adt.launch.MainLaunchConfigTab;
import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.common.project.ProjectChooserHelper; import com.android.ide.eclipse.common.project.ProjectChooserHelper;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
@@ -241,7 +242,7 @@ public class AndroidJUnitLaunchConfigurationTab extends AbstractLaunchConfigurat
private void createTestContainerSelectionGroup(Composite comp) { private void createTestContainerSelectionGroup(Composite comp) {
mTestContainerRadioButton = new Button(comp, SWT.RADIO); mTestContainerRadioButton = new Button(comp, SWT.RADIO);
mTestContainerRadioButton.setText( mTestContainerRadioButton.setText(
JUnitMessages.JUnitLaunchConfigurationTab_label_containerTest); "Run all tests in the selected project, or package");
GridData gd = new GridData(); GridData gd = new GridData();
gd.horizontalSpan = 3; gd.horizontalSpan = 3;
mTestContainerRadioButton.setLayoutData(gd); mTestContainerRadioButton.setLayoutData(gd);
@@ -821,7 +822,7 @@ public class AndroidJUnitLaunchConfigurationTab extends AbstractLaunchConfigurat
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private IJavaElement chooseContainer(IJavaElement initElement) { private IJavaElement chooseContainer(IJavaElement initElement) {
Class[] acceptedClasses = new Class[] { IPackageFragmentRoot.class, IJavaProject.class, Class[] acceptedClasses = new Class[] { IJavaProject.class,
IPackageFragment.class }; IPackageFragment.class };
TypedElementSelectionValidator validator = new TypedElementSelectionValidator( TypedElementSelectionValidator validator = new TypedElementSelectionValidator(
acceptedClasses, false) { acceptedClasses, false) {
@@ -852,7 +853,7 @@ public class AndroidJUnitLaunchConfigurationTab extends AbstractLaunchConfigurat
} }
}; };
StandardJavaElementContentProvider provider = new StandardJavaElementContentProvider(); AndroidJavaElementContentProvider provider = new AndroidJavaElementContentProvider();
ILabelProvider labelProvider = new JavaElementLabelProvider( ILabelProvider labelProvider = new JavaElementLabelProvider(
JavaElementLabelProvider.SHOW_DEFAULT); JavaElementLabelProvider.SHOW_DEFAULT);
ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(), ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(),
@@ -974,4 +975,23 @@ public class AndroidJUnitLaunchConfigurationTab extends AbstractLaunchConfigurat
mInstrumentations = null; mInstrumentations = null;
mInstrumentationCombo.removeAll(); mInstrumentationCombo.removeAll();
} }
/**
* Overrides the {@link StandardJavaElementContentProvider} to only display Android projects
*/
private static class AndroidJavaElementContentProvider
extends StandardJavaElementContentProvider {
/**
* Override parent to return only Android projects if at the root. Otherwise, use parent
* functionality.
*/
@Override
public Object[] getChildren(Object element) {
if (element instanceof IJavaModel) {
return BaseProjectHelper.getAndroidProjects((IJavaModel) element);
}
return super.getChildren(element);
}
}
} }

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2009 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.ide.eclipse.adt.launch.junit;
import org.eclipse.core.expressions.PropertyTester;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.junit.util.TestSearchEngine;
/**
* A {@link PropertyTester} that checks if selected elements can be run as Android
* JUnit tests.
* <p/>
* Based on org.eclipse.jdt.internal.junit.JUnitPropertyTester. The only substantial difference in
* this implementation is source folders cannot be run as Android JUnit.
*/
@SuppressWarnings("restriction")
public class AndroidJUnitPropertyTester extends PropertyTester {
private static final String PROPERTY_IS_TEST = "isTest"; //$NON-NLS-1$
private static final String PROPERTY_CAN_LAUNCH_AS_JUNIT_TEST = "canLaunchAsJUnit"; //$NON-NLS-1$
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.corext.refactoring.participants.properties.IPropertyEvaluator#test(java.lang.Object, java.lang.String, java.lang.String)
*/
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
if (!(receiver instanceof IAdaptable)) {
final String elementName = (receiver == null ? "null" : //$NON-NLS-1$
receiver.getClass().getName());
throw new IllegalArgumentException(
String.format("Element must be of type IAdaptable, is %s", //$NON-NLS-1$
elementName));
}
IJavaElement element;
if (receiver instanceof IJavaElement) {
element = (IJavaElement) receiver;
} else if (receiver instanceof IResource) {
element = JavaCore.create((IResource) receiver);
if (element == null) {
return false;
}
} else { // is IAdaptable
element= (IJavaElement) ((IAdaptable) receiver).getAdapter(IJavaElement.class);
if (element == null) {
IResource resource = (IResource) ((IAdaptable) receiver).getAdapter(
IResource.class);
element = JavaCore.create(resource);
if (element == null) {
return false;
}
}
}
if (PROPERTY_IS_TEST.equals(property)) {
return isJUnitTest(element);
} else if (PROPERTY_CAN_LAUNCH_AS_JUNIT_TEST.equals(property)) {
return canLaunchAsJUnitTest(element);
}
throw new IllegalArgumentException(
String.format("Unknown test property '%s'", property)); //$NON-NLS-1$
}
private boolean canLaunchAsJUnitTest(IJavaElement element) {
try {
switch (element.getElementType()) {
case IJavaElement.JAVA_PROJECT:
return true; // can run, let JDT detect if there are tests
case IJavaElement.PACKAGE_FRAGMENT_ROOT:
return false; // not supported by Android test runner
case IJavaElement.PACKAGE_FRAGMENT:
return ((IPackageFragment) element).hasChildren();
case IJavaElement.COMPILATION_UNIT:
case IJavaElement.CLASS_FILE:
case IJavaElement.TYPE:
case IJavaElement.METHOD:
return isJUnitTest(element);
default:
return false;
}
} catch (JavaModelException e) {
return false;
}
}
/**
* Return whether the target resource is a JUnit test.
*/
private boolean isJUnitTest(IJavaElement element) {
try {
IType testType = null;
if (element instanceof ICompilationUnit) {
testType = (((ICompilationUnit) element)).findPrimaryType();
} else if (element instanceof IClassFile) {
testType = (((IClassFile) element)).getType();
} else if (element instanceof IType) {
testType = (IType) element;
} else if (element instanceof IMember) {
testType = ((IMember) element).getDeclaringType();
}
if (testType != null && testType.exists()) {
return TestSearchEngine.isTestOrTestSuite(testType);
}
} catch (CoreException e) {
// ignore, return false
}
return false;
}
}

View File

@@ -15,35 +15,38 @@
*/ */
package com.android.ide.eclipse.adt.launch.junit.runtime; package com.android.ide.eclipse.adt.launch.junit.runtime;
import org.eclipse.core.resources.IProject;
import com.android.ddmlib.IDevice; import com.android.ddmlib.IDevice;
import org.eclipse.core.resources.IProject;
import org.eclipse.debug.core.ILaunch;
/** /**
* Contains info about Android JUnit launch * Contains info about Android JUnit launch
*/ */
public class AndroidJUnitLaunchInfo { public class AndroidJUnitLaunchInfo {
private final IProject mProject; private final IProject mProject;
private final String mTestPackage; private final String mAppPackage;
private final String mRunner; private final String mRunner;
private final boolean mDebugMode;
private final IDevice mDevice;
public AndroidJUnitLaunchInfo(IProject project, String testPackage, String runner, private boolean mDebugMode = false;
boolean debugMode, IDevice device) { private IDevice mDevice = null;
private String mTestPackage = null;
private String mTestClass = null;
private String mTestMethod = null;
private ILaunch mLaunch = null;
public AndroidJUnitLaunchInfo(IProject project, String appPackage, String runner) {
mProject = project; mProject = project;
mTestPackage = testPackage; mAppPackage = appPackage;
mRunner = runner; mRunner = runner;
mDebugMode = debugMode;
mDevice = device;
} }
public IProject getProject() { public IProject getProject() {
return mProject; return mProject;
} }
public String getTestPackage() { public String getAppPackage() {
return mTestPackage; return mAppPackage;
} }
public String getRunner() { public String getRunner() {
@@ -54,7 +57,79 @@ public class AndroidJUnitLaunchInfo {
return mDebugMode; return mDebugMode;
} }
public void setDebugMode(boolean debugMode) {
mDebugMode = debugMode;
}
public IDevice getDevice() { public IDevice getDevice() {
return mDevice; return mDevice;
} }
public void setDevice(IDevice device) {
mDevice = device;
}
/**
* Specify to run all tests within given package.
*
* @param testPackage fully qualified java package
*/
public void setTestPackage(String testPackage) {
mTestPackage = testPackage;
}
/**
* Return the package of tests to run.
*
* @return fully qualified java package. <code>null</code> if not specified.
*/
public String getTestPackage() {
return mTestPackage;
}
/**
* Sets the test class to run.
*
* @param testClass fully qualfied test class to run
* Expected format: x.y.x.testclass
*/
public void setTestClass(String testClass) {
mTestClass = testClass;
}
/**
* Returns the test class to run.
*
* @return fully qualfied test class to run.
* <code>null</code> if not specified.
*/
public String getTestClass() {
return mTestClass;
}
/**
* Sets the test method to run. testClass must also be set.
*
* @param testMethod test method to run
*/
public void setTestMethod(String testMethod) {
mTestMethod = testMethod;
}
/**
* Returns the test method to run.
*
* @return test method to run. <code>null</code> if not specified.
*/
public String getTestMethod() {
return mTestMethod;
}
public ILaunch getLaunch() {
return mLaunch;
}
public void setLaunch(ILaunch launch) {
mLaunch = launch;
}
} }

View File

@@ -69,8 +69,9 @@ public class RemoteAdtTestRunner extends RemoteTestRunner {
* executing the tests, and send it back to JDT JUnit. The second is the actual test execution, * executing the tests, and send it back to JDT JUnit. The second is the actual test execution,
* whose results will be communicated back in real-time to JDT JUnit. * whose results will be communicated back in real-time to JDT JUnit.
* *
* @param testClassNames array of fully qualified test class names to execute. Cannot be empty. * @param testClassNames ignored - the AndroidJUnitLaunchInfo will be used to determine which
* @param testName test to execute. If null, will be ignored. * tests to run.
* @param testName ignored
* @param execution used to report test progress * @param execution used to report test progress
*/ */
@Override @Override
@@ -78,16 +79,21 @@ public class RemoteAdtTestRunner extends RemoteTestRunner {
// hold onto this execution reference so it can be used to report test progress // hold onto this execution reference so it can be used to report test progress
mExecution = execution; mExecution = execution;
RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mLaunchInfo.getTestPackage(), RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mLaunchInfo.getAppPackage(),
mLaunchInfo.getRunner(), mLaunchInfo.getDevice()); mLaunchInfo.getRunner(), mLaunchInfo.getDevice());
if (testClassNames != null && testClassNames.length > 0) { if (mLaunchInfo.getTestClass() != null) {
if (testName != null) { if (mLaunchInfo.getTestMethod() != null) {
runner.setMethodName(testClassNames[0], testName); runner.setMethodName(mLaunchInfo.getTestClass(), mLaunchInfo.getTestMethod());
} else { } else {
runner.setClassNames(testClassNames); runner.setClassName(mLaunchInfo.getTestClass());
} }
} }
if (mLaunchInfo.getTestPackage() != null) {
runner.setTestPackageName(mLaunchInfo.getTestPackage());
}
// set log only to first collect test case info, so Eclipse has correct test case count/ // set log only to first collect test case info, so Eclipse has correct test case count/
// tree info // tree info
runner.setLogOnly(true); runner.setLogOnly(true);

View File

@@ -17,10 +17,10 @@
package com.android.ide.eclipse.adt.refactorings.extractstring; package com.android.ide.eclipse.adt.refactorings.extractstring;
import com.android.ide.eclipse.adt.ui.ConfigurationSelector;
import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration;
import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType;
import com.android.ide.eclipse.editors.wizards.ConfigurationSelector;
import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IFolder;

View File

@@ -100,7 +100,7 @@ public class Sdk implements IProjectListener {
ISdkLog log = new ISdkLog() { ISdkLog log = new ISdkLog() {
public void error(Throwable throwable, String errorFormat, Object... arg) { public void error(Throwable throwable, String errorFormat, Object... arg) {
if (errorFormat != null) { if (errorFormat != null) {
logMessages.add(String.format(errorFormat, arg)); logMessages.add(String.format("Error: " + errorFormat, arg));
} }
if (throwable != null) { if (throwable != null) {
@@ -109,7 +109,7 @@ public class Sdk implements IProjectListener {
} }
public void warning(String warningFormat, Object... arg) { public void warning(String warningFormat, Object... arg) {
logMessages.add(String.format(warningFormat, arg)); logMessages.add(String.format("Warning: " + warningFormat, arg));
} }
public void printf(String msgFormat, Object... arg) { public void printf(String msgFormat, Object... arg) {

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.ide.eclipse.editors.wizards; package com.android.ide.eclipse.adt.ui;
import com.android.ide.eclipse.editors.resources.configurations.CountryCodeQualifier; import com.android.ide.eclipse.editors.resources.configurations.CountryCodeQualifier;
import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.ide.eclipse.common; package com.android.ide.eclipse.adt.ui;
import org.eclipse.ui.IViewPart; import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPage;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.ide.eclipse.editors.wizards; package com.android.ide.eclipse.adt.ui;
import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.resources.IResourceRepository; import com.android.ide.eclipse.common.resources.IResourceRepository;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.ide.eclipse.editors.wizards; package com.android.ide.eclipse.adt.ui;
import com.android.ide.eclipse.common.resources.IResourceRepository; import com.android.ide.eclipse.common.resources.IResourceRepository;
import com.android.ide.eclipse.common.resources.ResourceItem; import com.android.ide.eclipse.common.resources.ResourceItem;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.ide.eclipse.editors.wizards; package com.android.ide.eclipse.adt.ui;
import com.android.ide.eclipse.common.resources.IResourceRepository; import com.android.ide.eclipse.common.resources.IResourceRepository;
import com.android.ide.eclipse.common.resources.ResourceItem; import com.android.ide.eclipse.common.resources.ResourceItem;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.ide.eclipse.editors.wizards; package com.android.ide.eclipse.adt.ui;
import com.android.ide.eclipse.common.resources.IIdResourceItem; import com.android.ide.eclipse.common.resources.IIdResourceItem;
import com.android.ide.eclipse.common.resources.ResourceItem; import com.android.ide.eclipse.common.resources.ResourceItem;

View File

@@ -21,6 +21,8 @@ import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener; import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.adt.ui.ConfigurationSelector;
import com.android.ide.eclipse.adt.ui.ConfigurationSelector.ConfigurationState;
import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.ProjectChooserHelper; import com.android.ide.eclipse.common.project.ProjectChooserHelper;
import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor;
@@ -31,8 +33,6 @@ import com.android.ide.eclipse.editors.resources.configurations.FolderConfigurat
import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier;
import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors; import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors;
import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType;
import com.android.ide.eclipse.editors.wizards.ConfigurationSelector;
import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.ConfigurationState;
import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkConstants;

View File

@@ -22,6 +22,10 @@ import com.android.ide.eclipse.adt.sdk.LoadStatus;
import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge; import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge;
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener; import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.adt.ui.ConfigurationSelector.DensityVerifier;
import com.android.ide.eclipse.adt.ui.ConfigurationSelector.DimensionVerifier;
import com.android.ide.eclipse.adt.ui.ConfigurationSelector.LanguageRegionVerifier;
import com.android.ide.eclipse.adt.ui.ConfigurationSelector.MobileCodeVerifier;
import com.android.ide.eclipse.common.resources.ResourceType; import com.android.ide.eclipse.common.resources.ResourceType;
import com.android.ide.eclipse.editors.IconFactory; import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.layout.LayoutEditor.UiEditorActions; import com.android.ide.eclipse.editors.layout.LayoutEditor.UiEditorActions;
@@ -55,10 +59,6 @@ import com.android.ide.eclipse.editors.ui.tree.CopyCutAction;
import com.android.ide.eclipse.editors.ui.tree.PasteAction; import com.android.ide.eclipse.editors.ui.tree.PasteAction;
import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; import com.android.ide.eclipse.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.editors.uimodel.UiElementNode; import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.DensityVerifier;
import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.DimensionVerifier;
import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.LanguageRegionVerifier;
import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.MobileCodeVerifier;
import com.android.layoutlib.api.ILayoutLog; import com.android.layoutlib.api.ILayoutLog;
import com.android.layoutlib.api.ILayoutResult; import com.android.layoutlib.api.ILayoutResult;
import com.android.layoutlib.api.IProjectCallback; import com.android.layoutlib.api.IProjectCallback;

View File

@@ -16,12 +16,12 @@
package com.android.ide.eclipse.editors.layout; package com.android.ide.eclipse.editors.layout;
import com.android.ide.eclipse.adt.ui.ConfigurationSelector;
import com.android.ide.eclipse.adt.ui.ConfigurationSelector.ConfigurationState;
import com.android.ide.eclipse.editors.IconFactory; import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration;
import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier;
import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType;
import com.android.ide.eclipse.editors.wizards.ConfigurationSelector;
import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.ConfigurationState;
import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.TrayDialog; import org.eclipse.jface.dialogs.TrayDialog;

View File

@@ -18,8 +18,8 @@ package com.android.ide.eclipse.editors.layout;
import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.ui.EclipseUiHelper;
import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.EclipseUiHelper;
import com.android.ide.eclipse.editors.AndroidEditor; import com.android.ide.eclipse.editors.AndroidEditor;
import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor;
import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; import com.android.ide.eclipse.editors.resources.manager.ResourceFolder;

View File

@@ -17,7 +17,7 @@
package com.android.ide.eclipse.editors.layout; package com.android.ide.eclipse.editors.layout;
import com.android.ide.eclipse.common.EclipseUiHelper; import com.android.ide.eclipse.adt.ui.EclipseUiHelper;
import com.android.ide.eclipse.editors.IconFactory; import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.layout.parts.UiDocumentTreeEditPart; import com.android.ide.eclipse.editors.layout.parts.UiDocumentTreeEditPart;
import com.android.ide.eclipse.editors.layout.parts.UiElementTreeEditPart; import com.android.ide.eclipse.editors.layout.parts.UiElementTreeEditPart;

View File

@@ -17,14 +17,14 @@
package com.android.ide.eclipse.editors.resources.explorer; package com.android.ide.eclipse.editors.resources.explorer;
import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.ui.ResourceContentProvider;
import com.android.ide.eclipse.adt.ui.ResourceLabelProvider;
import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.editors.resources.manager.ProjectResourceItem; import com.android.ide.eclipse.editors.resources.manager.ProjectResourceItem;
import com.android.ide.eclipse.editors.resources.manager.ProjectResources; import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
import com.android.ide.eclipse.editors.resources.manager.ResourceFile; import com.android.ide.eclipse.editors.resources.manager.ResourceFile;
import com.android.ide.eclipse.editors.resources.manager.ResourceManager; import com.android.ide.eclipse.editors.resources.manager.ResourceManager;
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener; import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener;
import com.android.ide.eclipse.editors.wizards.ResourceContentProvider;
import com.android.ide.eclipse.editors.wizards.ResourceLabelProvider;
import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;

View File

@@ -17,6 +17,8 @@
package com.android.ide.eclipse.editors.uimodel; package com.android.ide.eclipse.editors.uimodel;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.ui.ReferenceChooserDialog;
import com.android.ide.eclipse.adt.ui.ResourceChooser;
import com.android.ide.eclipse.common.resources.IResourceRepository; import com.android.ide.eclipse.common.resources.IResourceRepository;
import com.android.ide.eclipse.common.resources.ResourceItem; import com.android.ide.eclipse.common.resources.ResourceItem;
import com.android.ide.eclipse.common.resources.ResourceType; import com.android.ide.eclipse.common.resources.ResourceType;
@@ -26,8 +28,6 @@ import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor;
import com.android.ide.eclipse.editors.resources.manager.ResourceManager; import com.android.ide.eclipse.editors.resources.manager.ResourceManager;
import com.android.ide.eclipse.editors.ui.SectionHelper; import com.android.ide.eclipse.editors.ui.SectionHelper;
import com.android.ide.eclipse.editors.wizards.ReferenceChooserDialog;
import com.android.ide.eclipse.editors.wizards.ResourceChooser;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
import org.eclipse.jface.window.Window; import org.eclipse.jface.window.Window;

View File

@@ -45,6 +45,10 @@ public class MakeBinaryDictionary {
public static final String TAG_WORD = "w"; public static final String TAG_WORD = "w";
public static final String ATTR_FREQ = "f"; public static final String ATTR_FREQ = "f";
private static final int FLAG_ADDRESS_MASK = 0x400000;
private static final int FLAG_TERMINAL_MASK = 0x800000;
private static final int ADDRESS_MASK = 0x3FFFFF;
public static final CharNode EMPTY_NODE = new CharNode(); public static final CharNode EMPTY_NODE = new CharNode();
List<CharNode> roots; List<CharNode> roots;
@@ -179,7 +183,7 @@ public class MakeBinaryDictionary {
parent.children.add(child); parent.children.add(child);
} }
child.data = data; child.data = data;
child.freq += occur; if (child.freq == 0) child.freq = occur;
if (word.length() > charAt + 1) { if (word.length() > charAt + 1) {
addWordRec(child, word, charAt + 1, occur); addWordRec(child, word, charAt + 1, occur);
} else { } else {
@@ -195,56 +199,76 @@ public class MakeBinaryDictionary {
static final int ADDR_WIDTH = 23; // Offset to children static final int ADDR_WIDTH = 23; // Offset to children
static final int FREQ_WIDTH_BYTES = 1; static final int FREQ_WIDTH_BYTES = 1;
static final int COUNT_WIDTH_BYTES = 1; static final int COUNT_WIDTH_BYTES = 1;
static final int NODE_SIZE_BYTES =
(CHAR_WIDTH + FLAGS_WIDTH + ADDR_WIDTH) / 8 + FREQ_WIDTH_BYTES;
private void addCount(int count) { private void addCount(int count) {
dict[dictSize++] = (byte) (0xFF & count); dict[dictSize++] = (byte) (0xFF & count);
} }
/* TODO: Allow characters to be beyond the 0-255 range. This is required for some latin
language not currently supported */
private void addNode(CharNode node) { private void addNode(CharNode node) {
int charData = 0xFFFF & node.data; int charData = 0xFFFF & node.data;
if (charData > 254) { if (charData > 254) {
System.out.println("WARNING: Non-ASCII character encountered : " + node.data + dict[dictSize++] = (byte) 255;
", value = " + charData); dict[dictSize++] = (byte) ((node.data >> 8) & 0xFF);
dict[dictSize++] = '@'; dict[dictSize++] = (byte) (node.data & 0xFF);
} else { } else {
dict[dictSize++] = (byte) (0xFF & node.data); dict[dictSize++] = (byte) (0xFF & node.data);
} }
if (node.children != null) {
dictSize += 3; // Space for children address dictSize += 3; // Space for children address
} else {
dictSize += 1; // Space for just the terminal/address flags
}
if ((0xFFFFFF & node.freq) > 255) { if ((0xFFFFFF & node.freq) > 255) {
node.freq = (byte) 255; node.freq = 255;
} }
dict[dictSize++] = (byte) (0xFF & node.freq); if (node.terminal) {
byte freq = (byte) (0xFF & node.freq);
dict[dictSize++] = freq;
} }
}
int nullChildrenCount = 0;
int notTerminalCount = 0;
private void updateNodeAddress(int nodeAddress, CharNode node, private void updateNodeAddress(int nodeAddress, CharNode node,
int childrenAddress) { int childrenAddress) {
childrenAddress = 0x7FFFFF & childrenAddress; if ((dict[nodeAddress] & 0xFF) == 0xFF) { // 3 byte character
nodeAddress += 2;
}
childrenAddress = ADDRESS_MASK & childrenAddress;
if (childrenAddress == 0) {
nullChildrenCount++;
} else {
childrenAddress |= FLAG_ADDRESS_MASK;
}
if (node.terminal) { if (node.terminal) {
childrenAddress |= 0x800000; childrenAddress |= FLAG_TERMINAL_MASK;
} else {
notTerminalCount++;
} }
dict[nodeAddress + 1] = (byte) (childrenAddress >> 16); dict[nodeAddress + 1] = (byte) (childrenAddress >> 16);
if ((childrenAddress & FLAG_ADDRESS_MASK) != 0) {
dict[nodeAddress + 2] = (byte) ((childrenAddress & 0xFF00) >> 8); dict[nodeAddress + 2] = (byte) ((childrenAddress & 0xFF00) >> 8);
dict[nodeAddress + 3] = (byte) ((childrenAddress & 0xFF)); dict[nodeAddress + 3] = (byte) ((childrenAddress & 0xFF));
} }
}
void writeWordsRec(List<CharNode> children) { void writeWordsRec(List<CharNode> children) {
if (children == null || children.size() == 0) { if (children == null || children.size() == 0) {
return; return;
} }
addCount(children.size()); final int childCount = children.size();
int childrenStart = dictSize; addCount(childCount);
for (int j = 0; j < children.size(); j++) { //int childrenStart = dictSize;
int[] childrenAddresses = new int[childCount];
for (int j = 0; j < childCount; j++) {
CharNode node = children.get(j); CharNode node = children.get(j);
childrenAddresses[j] = dictSize;
addNode(node); addNode(node);
} }
for (int j = 0; j < children.size(); j++) { for (int j = 0; j < childCount; j++) {
CharNode node = children.get(j); CharNode node = children.get(j);
// TODO: Fix this when child length becomes variable int nodeAddress = childrenAddresses[j];
int nodeAddress = childrenStart + NODE_SIZE_BYTES * j;
int cacheDictSize = dictSize; int cacheDictSize = dictSize;
writeWordsRec(node.children); writeWordsRec(node.children);
updateNodeAddress(nodeAddress, node, node.children != null updateNodeAddress(nodeAddress, node, node.children != null
@@ -253,8 +277,8 @@ public class MakeBinaryDictionary {
} }
void writeToDict(String dictFilename) { void writeToDict(String dictFilename) {
// 2MB max // 4MB max, 22-bit offsets
dict = new byte[2 * 1024 * 1024]; // 2MB upper limit. Actual is probably dict = new byte[4 * 1024 * 1024]; // 4MB upper limit. Actual is probably
// < 1MB in most cases, as there is a limit in the // < 1MB in most cases, as there is a limit in the
// resource size in apks. // resource size in apks.
dictSize = 0; dictSize = 0;
@@ -272,19 +296,29 @@ public class MakeBinaryDictionary {
void traverseDict(int pos, char[] word, int depth) { void traverseDict(int pos, char[] word, int depth) {
int count = dict[pos++] & 0xFF; int count = dict[pos++] & 0xFF;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
char c = (char) (dict[pos] & 0xFF); char c = (char) (dict[pos++] & 0xFF);
word[depth] = c; if (c == 0xFF) {
if ((dict[pos + 1] & 0x80) > 0) { c = (char) (((dict[pos] & 0xFF) << 8) | (dict[pos+1] & 0xFF));
showWord(word, depth + 1, dict[pos + 4] & 0xFF); pos += 2;
}
word[depth] = c;
boolean terminal = (dict[pos] & 0x80) > 0;
int address = 0;
if ((dict[pos] & (FLAG_ADDRESS_MASK >> 16)) > 0) {
address =
((dict[pos + 0] & (FLAG_ADDRESS_MASK >> 16)) << 16)
| ((dict[pos + 1] & 0xFF) << 8)
| ((dict[pos + 2] & 0xFF));
pos += 2;
}
pos++;
if (terminal) {
showWord(word, depth + 1, dict[pos] & 0xFF);
pos++;
} }
int address =
((dict[pos + 1] & 0x7F) << 16)
| ((dict[pos + 2] & 0xFF) << 8)
| ((dict[pos + 3] & 0xFF));
if (address != 0) { if (address != 0) {
traverseDict(address, word, depth + 1); traverseDict(address, word, depth + 1);
} }
pos += NODE_SIZE_BYTES;
} }
} }

View File

@@ -459,6 +459,25 @@ class Main {
mSdkLog.printf(" Sdcard: %s\n", sdcard); mSdkLog.printf(" Sdcard: %s\n", sdcard);
} }
} }
// Are there some unused AVDs?
List<AvdInfo> badAvds = avdManager.getUnavailableAvds();
if (badAvds == null || badAvds.size() == 0) {
return;
}
mSdkLog.printf("\nThe following Android Virtual Devices could not be loaded:\n");
boolean needSeparator = false;
for (AvdInfo info : badAvds) {
if (needSeparator) {
mSdkLog.printf("---------\n");
}
mSdkLog.printf(" Name: %s\n", info.getName() == null ? "--" : info.getName());
mSdkLog.printf(" Path: %s\n", info.getPath() == null ? "--" : info.getPath());
mSdkLog.printf(" Error: %s\n", info.getError() == null ? "--" : info.getError());
needSeparator = true;
}
} catch (AndroidLocationException e) { } catch (AndroidLocationException e) {
errorAndExit(e.getMessage()); errorAndExit(e.getMessage());
} }
@@ -573,7 +592,7 @@ class Main {
File dir = new File(oldAvdInfo.getPath()); File dir = new File(oldAvdInfo.getPath());
avdManager.recursiveDelete(dir); avdManager.recursiveDelete(dir);
dir.delete(); dir.delete();
// Remove old avd info from manager // Remove old AVD info from manager
avdManager.removeAvd(oldAvdInfo); avdManager.removeAvd(oldAvdInfo);
} }
@@ -583,7 +602,8 @@ class Main {
} }
/** /**
* Delete an AVD. * Delete an AVD. If the AVD name is not part of the available ones look for an
* invalid AVD (one not loaded due to some error) to remove it too.
*/ */
private void deleteAvd() { private void deleteAvd() {
try { try {
@@ -591,6 +611,19 @@ class Main {
AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog); AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
AvdInfo info = avdManager.getAvd(avdName); AvdInfo info = avdManager.getAvd(avdName);
if (info == null) {
// Look in unavailable AVDs
List<AvdInfo> badAvds = avdManager.getUnavailableAvds();
if (badAvds != null) {
for (AvdInfo i : badAvds) {
if (i.getName().equals(avdName)) {
info = i;
break;
}
}
}
}
if (info == null) { if (info == null) {
errorAndExit("There is no Android Virtual Device named '%s'.", avdName); errorAndExit("There is no Android Virtual Device named '%s'.", avdName);
return; return;

View File

@@ -26,8 +26,10 @@ public interface ISdkLog {
/** /**
* Prints a warning message on stdout. * Prints a warning message on stdout.
* <p/> * <p/>
* The message will be tagged with "Warning" on the output so the caller does not
* need to put such a prefix in the format string.
* <p/>
* Implementations should only display warnings in verbose mode. * Implementations should only display warnings in verbose mode.
* The message should be prefixed with "Warning:".
* *
* @param warningFormat is an optional error format. If non-null, it will be printed * @param warningFormat is an optional error format. If non-null, it will be printed
* using a {@link Formatter} with the provided arguments. * using a {@link Formatter} with the provided arguments.
@@ -38,8 +40,10 @@ public interface ISdkLog {
/** /**
* Prints an error message on stderr. * Prints an error message on stderr.
* <p/> * <p/>
* The message will be tagged with "Error" on the output so the caller does not
* need to put such a prefix in the format string.
* <p/>
* Implementation should always display errors, independent of verbose mode. * Implementation should always display errors, independent of verbose mode.
* The message should be prefixed with "Error:".
* *
* @param t is an optional {@link Throwable} or {@link Exception}. If non-null, it's * @param t is an optional {@link Throwable} or {@link Exception}. If non-null, it's
* message will be printed out. * message will be printed out.

View File

@@ -32,8 +32,11 @@ import java.io.FilenameFilter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeSet;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -118,15 +121,43 @@ public final class AvdManager {
private final String mPath; private final String mPath;
private final IAndroidTarget mTarget; private final IAndroidTarget mTarget;
private final Map<String, String> mProperties; private final Map<String, String> mProperties;
private final String mError;
/** Creates a new AVD info. Values are immutable. /**
* @param properties */ * Creates a new valid AVD info. Values are immutable.
* <p/>
* Such an AVD is available and can be used.
* The error string is set to null.
*
* @param name The name of the AVD (for display or reference)
* @param path The path to the config.ini file
* @param target The target. Cannot be null.
* @param properties The property map. Cannot be null.
*/
public AvdInfo(String name, String path, IAndroidTarget target, public AvdInfo(String name, String path, IAndroidTarget target,
Map<String, String> properties) { Map<String, String> properties) {
this(name, path, target, properties, null /*error*/);
}
/**
* Creates a new <em>invalid</em> AVD info. Values are immutable.
* <p/>
* Such an AVD is not complete and cannot be used.
* The error string must be non-null.
*
* @param name The name of the AVD (for display or reference)
* @param path The path to the config.ini file
* @param target The target. Can be null.
* @param properties The property map. Can be null.
* @param error The error describing why this AVD is invalid. Cannot be null.
*/
public AvdInfo(String name, String path, IAndroidTarget target,
Map<String, String> properties, String error) {
mName = name; mName = name;
mPath = path; mPath = path;
mTarget = target; mTarget = target;
mProperties = properties; mProperties = properties;
mError = error;
} }
/** Returns the name of the AVD. */ /** Returns the name of the AVD. */
@@ -144,6 +175,11 @@ public final class AvdManager {
return mTarget; return mTarget;
} }
/** Returns the error describing why an AVD failed to load. Always null for valid AVDs. */
public String getError() {
return mError;
}
/** /**
* Helper method that returns the .ini {@link File} for a given AVD name. * Helper method that returns the .ini {@link File} for a given AVD name.
* @throws AndroidLocationException if there's a problem getting android root directory. * @throws AndroidLocationException if there's a problem getting android root directory.
@@ -243,7 +279,7 @@ public final class AvdManager {
// AVD shouldn't already exist if removePrevious is false. // AVD shouldn't already exist if removePrevious is false.
if (log != null) { if (log != null) {
log.error(null, log.error(null,
"Folder %s is in the way. Use --force if you want to overwrite.", "Folder %1$s is in the way. Use --force if you want to overwrite.",
avdFolder.getAbsolutePath()); avdFolder.getAbsolutePath());
} }
return null; return null;
@@ -393,9 +429,9 @@ public final class AvdManager {
if (log != null) { if (log != null) {
if (target.isPlatform()) { if (target.isPlatform()) {
log.printf("Created AVD '%s' based on %s\n", name, target.getName()); log.printf("Created AVD '%1$s' based on %2$s\n", name, target.getName());
} else { } else {
log.printf("Created AVD '%s' based on %s (%s)\n", name, target.getName(), log.printf("Created AVD '%1$s' based on %2$s (%3$s)\n", name, target.getName(),
target.getVendor()); target.getVendor());
} }
} }
@@ -527,29 +563,49 @@ public final class AvdManager {
* <p/> * <p/>
* This also remove it from the manager's list, The caller does not need to * This also remove it from the manager's list, The caller does not need to
* call {@link #removeAvd(AvdInfo)} afterwards. * call {@link #removeAvd(AvdInfo)} afterwards.
* <p/>
* This method is designed to somehow work with an unavailable AVD, that is an AVD that
* could not be loaded due to some error. That means this method still tries to remove
* the AVD ini file or its folder if it can be found. An error will be output if any of
* these operations fail.
* *
* @param avdInfo the information on the AVD to delete * @param avdInfo the information on the AVD to delete
*/ */
public void deleteAvd(AvdInfo avdInfo, ISdkLog log) { public void deleteAvd(AvdInfo avdInfo, ISdkLog log) {
try { try {
boolean error = false;
File f = avdInfo.getIniFile(); File f = avdInfo.getIniFile();
if (f.exists()) { if (f != null && f.exists()) {
log.warning("Deleting file %s", f.getCanonicalPath()); log.warning("Deleting file %1$s", f.getCanonicalPath());
if (!f.delete()) { if (!f.delete()) {
log.error(null, "Failed to delete %s", f.getCanonicalPath()); log.error(null, "Failed to delete %1$s", f.getCanonicalPath());
error = true;
} }
} }
f = new File(avdInfo.getPath()); String path = avdInfo.getPath();
if (path != null) {
f = new File(path);
if (f.exists()) { if (f.exists()) {
log.warning("Deleting folder %s", f.getCanonicalPath()); log.warning("Deleting folder %1$s", f.getCanonicalPath());
recursiveDelete(f); recursiveDelete(f);
if (!f.delete()) { if (!f.delete()) {
log.error(null, "Failed to delete %s", f.getCanonicalPath()); log.error(null, "Failed to delete %1$s", f.getCanonicalPath());
error = true;
}
} }
} }
removeAvd(avdInfo); removeAvd(avdInfo);
if (error) {
log.printf("AVD '%1$s' deleted with errors. See warnings above.",
avdInfo.getName());
} else {
log.printf("AVD '%1$s' deleted.", avdInfo.getName());
}
} catch (AndroidLocationException e) { } catch (AndroidLocationException e) {
log.error(e, null); log.error(e, null);
} catch (IOException e) { } catch (IOException e) {
@@ -575,14 +631,14 @@ public final class AvdManager {
try { try {
if (paramFolderPath != null) { if (paramFolderPath != null) {
File f = new File(avdInfo.getPath()); File f = new File(avdInfo.getPath());
log.warning("Moving '%s' to '%s'.", avdInfo.getPath(), paramFolderPath); log.warning("Moving '%1$s' to '%2$s'.", avdInfo.getPath(), paramFolderPath);
if (!f.renameTo(new File(paramFolderPath))) { if (!f.renameTo(new File(paramFolderPath))) {
log.error(null, "Failed to move '%s' to '%s'.", log.error(null, "Failed to move '%1$s' to '%2$s'.",
avdInfo.getPath(), paramFolderPath); avdInfo.getPath(), paramFolderPath);
return false; return false;
} }
// update avd info // update AVD info
AvdInfo info = new AvdInfo(avdInfo.getName(), paramFolderPath, avdInfo.getTarget(), AvdInfo info = new AvdInfo(avdInfo.getName(), paramFolderPath, avdInfo.getTarget(),
avdInfo.getProperties()); avdInfo.getProperties());
mAvdList.remove(avdInfo); mAvdList.remove(avdInfo);
@@ -597,19 +653,22 @@ public final class AvdManager {
File oldIniFile = avdInfo.getIniFile(); File oldIniFile = avdInfo.getIniFile();
File newIniFile = AvdInfo.getIniFile(newName); File newIniFile = AvdInfo.getIniFile(newName);
log.warning("Moving '%s' to '%s'.", oldIniFile.getPath(), newIniFile.getPath()); log.warning("Moving '%1$s' to '%2$s'.", oldIniFile.getPath(), newIniFile.getPath());
if (!oldIniFile.renameTo(newIniFile)) { if (!oldIniFile.renameTo(newIniFile)) {
log.error(null, "Failed to move '%s' to '%s'.", log.error(null, "Failed to move '%1$s' to '%2$s'.",
oldIniFile.getPath(), newIniFile.getPath()); oldIniFile.getPath(), newIniFile.getPath());
return false; return false;
} }
// update avd info // update AVD info
AvdInfo info = new AvdInfo(newName, avdInfo.getPath(), avdInfo.getTarget(), AvdInfo info = new AvdInfo(newName, avdInfo.getPath(), avdInfo.getTarget(),
avdInfo.getProperties()); avdInfo.getProperties());
mAvdList.remove(avdInfo); mAvdList.remove(avdInfo);
mAvdList.add(info); mAvdList.add(info);
} }
log.printf("AVD '%1$s' moved.", avdInfo.getName());
} catch (AndroidLocationException e) { } catch (AndroidLocationException e) {
log.error(e, null); log.error(e, null);
} catch (IOException e) { } catch (IOException e) {
@@ -634,29 +693,28 @@ public final class AvdManager {
} }
} }
private void buildAvdList(ArrayList<AvdInfo> list) throws AndroidLocationException { /**
* Returns a list of files that are potential AVD ini files.
* <p/>
* This lists the $HOME/.android/avd/<name>.ini files.
* Such files are properties file than then indicate where the AVD folder is located.
*
* @return A new {@link File} array or null. The array might be empty.
* @throws AndroidLocationException if there's a problem getting android root directory.
*/
private File[] buildAvdFilesList() throws AndroidLocationException {
// get the Android prefs location. // get the Android prefs location.
String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD; String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
final boolean avdListDebug = System.getenv("AVD_LIST_DEBUG") != null;
if (avdListDebug) {
mSdkLog.printf("[AVD LIST DEBUG] AVD root: '%s'\n", avdRoot);
}
// ensure folder validity. // ensure folder validity.
File folder = new File(avdRoot); File folder = new File(avdRoot);
if (folder.isFile()) { if (folder.isFile()) {
if (avdListDebug) { throw new AndroidLocationException(
mSdkLog.printf("[AVD LIST DEBUG] AVD root is a file.\n"); String.format("%1$s is not a valid folder.", avdRoot));
}
throw new AndroidLocationException(String.format("%s is not a valid folder.", avdRoot));
} else if (folder.exists() == false) { } else if (folder.exists() == false) {
if (avdListDebug) {
mSdkLog.printf("[AVD LIST DEBUG] AVD root folder doesn't exist, creating.\n");
}
// folder is not there, we create it and return // folder is not there, we create it and return
folder.mkdirs(); folder.mkdirs();
return; return null;
} }
File[] avds = folder.listFiles(new FilenameFilter() { File[] avds = folder.listFiles(new FilenameFilter() {
@@ -664,10 +722,6 @@ public final class AvdManager {
if (INI_NAME_PATTERN.matcher(name).matches()) { if (INI_NAME_PATTERN.matcher(name).matches()) {
// check it's a file and not a folder // check it's a file and not a folder
boolean isFile = new File(parent, name).isFile(); boolean isFile = new File(parent, name).isFile();
if (avdListDebug) {
mSdkLog.printf("[AVD LIST DEBUG] Item '%s': %s\n",
name, isFile ? "accepted file" : "rejected");
}
return isFile; return isFile;
} }
@@ -675,63 +729,164 @@ public final class AvdManager {
} }
}); });
return avds;
}
/**
* Computes the internal list of available AVDs.
* This only contains AVDs that reference the target currently available.
*
* @param list An array list that will contain the list of AVDs.
* @throws AndroidLocationException if there's a problem getting android root directory.
*/
private void buildAvdList(ArrayList<AvdInfo> list) throws AndroidLocationException {
File[] avds = buildAvdFilesList();
for (File avd : avds) { for (File avd : avds) {
AvdInfo info = parseAvdInfo(avd); AvdInfo info = parseAvdInfo(avd, false /*acceptError*/);
if (info != null) { if (info != null) {
list.add(info); list.add(info);
if (avdListDebug) {
mSdkLog.printf("[AVD LIST DEBUG] Added AVD '%s'\n", info.getPath());
}
} else if (avdListDebug) {
mSdkLog.printf("[AVD LIST DEBUG] Failed to parse AVD '%s'\n", avd.getPath());
} }
} }
} }
private AvdInfo parseAvdInfo(File path) { /**
* Computes the internal list of <em>not</em> available AVDs.
* <p/>
* These are the AVDs that failed to load for some reason or another.
* You can retrieve the load error using {@link AvdInfo#getError()}.
* <p/>
* These {@link AvdInfo} must not be used for usual operations (e.g. instanciating
* an emulator) or trying to use them for anything else but {@link #deleteAvd(AvdInfo, ISdkLog)}
* will have unpredictable results -- that is most likely the operation will fail.
*
* @return A list of unavailable AVDs, all with errors. The list can be null or empty if there
* are no AVDs to return.
* @throws AndroidLocationException if there's a problem getting android root directory.
*/
public List<AvdInfo> getUnavailableAvds() throws AndroidLocationException {
AvdInfo[] avds = getAvds();
File[] allAvds = buildAvdFilesList();
if (allAvds == null || allAvds.length == 0) {
return null;
}
TreeSet<File> list = new TreeSet<File>(Arrays.asList(allAvds));
for (AvdInfo info : avds) {
if (list.remove(info.getIniFile())) {
if (list.size() == 0) {
return null;
}
}
}
ArrayList<AvdInfo> errorAvds = new ArrayList<AvdInfo>(list.size());
for (File file : list) {
errorAvds.add(parseAvdInfo(file, true /*acceptError*/));
}
return errorAvds;
}
/**
* Parses an AVD config.ini to create an {@link AvdInfo}.
*
* @param path The path to the AVD config.ini
* @param acceptError When false, an AVD that fails to load will be discarded and null will be
* returned. When true, such an AVD will be returned with an error description.
* @return A new {@link AvdInfo} or null if the file is not valid or null if the AVD is not
* valid and acceptError is false.
*/
private AvdInfo parseAvdInfo(File path, boolean acceptError) {
String error = null;
Map<String, String> map = SdkManager.parsePropertyFile(path, mSdkLog); Map<String, String> map = SdkManager.parsePropertyFile(path, mSdkLog);
String avdPath = map.get(AVD_INFO_PATH); String avdPath = map.get(AVD_INFO_PATH);
if (avdPath == null) {
return null;
}
String targetHash = map.get(AVD_INFO_TARGET); String targetHash = map.get(AVD_INFO_TARGET);
if (targetHash == null) {
return null; IAndroidTarget target = null;
File configIniFile = null;
Map<String, String> properties = null;
if (targetHash != null) {
target = mSdk.getTargetFromHashString(targetHash);
} }
IAndroidTarget target = mSdk.getTargetFromHashString(targetHash); // load the AVD properties.
if (target == null) { if (avdPath != null) {
return null; configIniFile = new File(avdPath, CONFIG_INI);
} }
// load the avd properties. if (configIniFile != null) {
File configIniFile = new File(avdPath, CONFIG_INI); properties = SdkManager.parsePropertyFile(configIniFile, mSdkLog);
Map<String, String> properties = SdkManager.parsePropertyFile(configIniFile, mSdkLog); }
// get name
String name = path.getName();
Matcher matcher = INI_NAME_PATTERN.matcher(path.getName()); Matcher matcher = INI_NAME_PATTERN.matcher(path.getName());
if (matcher.matches()) {
name = matcher.group(1);
}
if (!acceptError) {
if (avdPath == null ||
targetHash == null ||
target == null ||
configIniFile == null ||
properties == null) {
return null;
}
} else {
if (avdPath == null || configIniFile == null) {
error = String.format("Missing AVD 'path' property in %1$s", name);
} else if (targetHash == null) {
error = String.format("Missing 'target' property in %1$s", name);
} else if (target == null) {
error = String.format("Unknown target '%2$s' in %1$s", name, targetHash);
} else if (properties == null) {
error = String.format("Failed to parse properties from %1$s", avdPath);
}
}
AvdInfo info = new AvdInfo( AvdInfo info = new AvdInfo(
matcher.matches() ? matcher.group(1) : path.getName(), // should not happen name,
avdPath, avdPath,
target, target,
properties); properties,
error);
return info; return info;
} }
/**
* Writes a new AVD config.ini file from a set of properties.
*
* @param iniFile The file to generate.
* @param values THe properties to place in the ini file.
* @throws IOException if {@link FileWriter} fails to open, write or close the file.
*/
private static void createConfigIni(File iniFile, Map<String, String> values) private static void createConfigIni(File iniFile, Map<String, String> values)
throws IOException { throws IOException {
FileWriter writer = new FileWriter(iniFile); FileWriter writer = new FileWriter(iniFile);
for (Entry<String, String> entry : values.entrySet()) { for (Entry<String, String> entry : values.entrySet()) {
writer.write(String.format("%s=%s\n", entry.getKey(), entry.getValue())); writer.write(String.format("%1$s=%2$s\n", entry.getKey(), entry.getValue()));
} }
writer.close(); writer.close();
} }
/**
* Invokes the tool to create a new SD card image file.
*
* @param toolLocation The path to the mksdcard tool.
* @param size The size of the new SD Card, compatible with {@link #SDCARD_SIZE_PATTERN}.
* @param location The path of the new sdcard image file to generate.
* @param log The logger object, to report errors.
* @return True if the sdcard could be created.
*/
private boolean createSdCard(String toolLocation, String size, String location, ISdkLog log) { private boolean createSdCard(String toolLocation, String size, String location, ISdkLog log) {
try { try {
String[] command = new String[3]; String[] command = new String[3];
@@ -745,22 +900,21 @@ public final class AvdManager {
int status = grabProcessOutput(process, errorOutput, stdOutput, int status = grabProcessOutput(process, errorOutput, stdOutput,
true /* waitForReaders */); true /* waitForReaders */);
if (status != 0) { if (status == 0) {
log.error(null, "Failed to create the SD card."); return true;
} else {
for (String error : errorOutput) { for (String error : errorOutput) {
log.error(null, error); log.error(null, error);
} }
return false;
} }
return true;
} catch (InterruptedException e) { } catch (InterruptedException e) {
log.error(null, "Failed to create the SD card."); // pass, print error below
} catch (IOException e) { } catch (IOException e) {
log.error(null, "Failed to create the SD card."); // pass, print error below
} }
log.error(null, "Failed to create the SD card.");
return false; return false;
} }