am 51ecd212: merge from open-source master
diff --git a/apps/Development/AndroidManifest.xml b/apps/Development/AndroidManifest.xml
index c4774c2..389ac19 100644
--- a/apps/Development/AndroidManifest.xml
+++ b/apps/Development/AndroidManifest.xml
@@ -117,7 +117,7 @@
</intent-filter>
</activity>
- <activity android:name="MediaScannerActivity" android:label="Media Scanner">
+ <activity android:name="MediaScannerActivity" android:label="Media Provider">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.TEST" />
diff --git a/apps/Development/res/layout/media_scanner_activity.xml b/apps/Development/res/layout/media_scanner_activity.xml
index 974f683..4806843 100644
--- a/apps/Development/res/layout/media_scanner_activity.xml
+++ b/apps/Development/res/layout/media_scanner_activity.xml
@@ -4,9 +4,9 @@
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.
@@ -14,12 +14,45 @@
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <TextView android:id="@+id/title" android:textSize="16sp" android:textStyle="bold"
- android:layout_width="match_parent" android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/title"
+ android:textSize="16sp"
+ android:textStyle="bold"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <Button
+ android:layout_width="160dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:text="@string/scancard"
+ android:onClick="startScan" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:layout_marginTop="30dip">
+
+ <EditText
+ android:id="@+id/numsongs"
+ android:layout_width="150dip"
+ android:layout_height="wrap_content"
+ android:hint="@string/numsongs"
+ android:numeric="integer"
+ />
+
+ <Button
+ android:id="@+id/insertbutton"
+ android:layout_width="150dip"
+ android:layout_height="wrap_content"
+ android:onClick="insertItems" />
+
+ </LinearLayout>
</LinearLayout>
diff --git a/apps/Development/res/values/strings.xml b/apps/Development/res/values/strings.xml
index 1bb65cf..ec590a8 100644
--- a/apps/Development/res/values/strings.xml
+++ b/apps/Development/res/values/strings.xml
@@ -208,4 +208,9 @@
<string name="bad_behavior_anr_service_label">ANR starting a Service</string>
<string name="bad_behavior_anr_system_label">System ANR (in ActivityManager)</string>
<string name="bad_behavior_wedge_system_label">Wedge system (5 minute system ANR)</string>
+
+ <!-- MediaScannerActivity -->
+ <string name="scancard">Scan SD card</string>
+ <string name="numsongs"># of albums</string>
+ <string name="insertbutton">Insert %1s albums</string>
</resources>
diff --git a/apps/Development/src/com/android/development/MediaScannerActivity.java b/apps/Development/src/com/android/development/MediaScannerActivity.java
index f78910a..04f6414 100644
--- a/apps/Development/src/com/android/development/MediaScannerActivity.java
+++ b/apps/Development/src/com/android/development/MediaScannerActivity.java
@@ -17,56 +17,239 @@
package com.android.development;
import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
+import android.database.sqlite.SQLiteConstraintException;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
import android.widget.TextView;
+import java.util.Random;
+
public class MediaScannerActivity extends Activity
{
+ private TextView mTitle;
+ private int mNumToInsert = 20;
+ private int mArtists;
+ private int mAlbums;
+ private int mSongs;
+ private ContentResolver mResolver;
+ private Uri mAudioUri;
+ ContentValues mValues[] = new ContentValues[10];
+ Random mRandom = new Random();
+ StringBuilder mBuilder = new StringBuilder();
+
public MediaScannerActivity() {
}
-
+
/** Called when the activity is first created or resumed. */
@Override
- public void onResume() {
- super.onResume();
-
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
setContentView(R.layout.media_scanner_activity);
-
+
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
intentFilter.addDataScheme("file");
registerReceiver(mReceiver, intentFilter);
-
- sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
- + Environment.getExternalStorageDirectory())));
-
+
+ EditText t = (EditText) findViewById(R.id.numsongs);
+ t.addTextChangedListener(new TextWatcher() {
+
+ public void afterTextChanged(Editable s) {
+ String text = s.toString();
+ try {
+ mNumToInsert = Integer.valueOf(text);
+ } catch (NumberFormatException ex) {
+ mNumToInsert = 20;
+ }
+ setInsertButtonText();
+ }
+
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ });
mTitle = (TextView) findViewById(R.id.title);
- mTitle.setText("Sent ACTION_MEDIA_MOUNTED to trigger the Media Scanner.");
+ mResolver = getContentResolver();
+ mAudioUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+
+ for (int i = 0; i < 10; i++) {
+ mValues[i] = new ContentValues();
+ }
+ setInsertButtonText();
}
/** Called when the activity going into the background or being destroyed. */
@Override
- public void onPause() {
- super.onPause();
+ public void onDestroy() {
unregisterReceiver(mReceiver);
+ mInsertHandler.removeMessages(0);
+ super.onDestroy();
}
-
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
- mTitle.setText("Media Scanner started scanning " + intent.getData().getPath());
+ mTitle.setText("Media Scanner started scanning " + intent.getData().getPath());
}
else if (intent.getAction().equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
- mTitle.setText("Media Scanner finished scanning " + intent.getData().getPath());
+ mTitle.setText("Media Scanner finished scanning " + intent.getData().getPath());
}
}
};
- private TextView mTitle;
+ public void startScan(View v) {
+ sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
+ + Environment.getExternalStorageDirectory())));
+
+ mTitle.setText("Sent ACTION_MEDIA_MOUNTED to trigger the Media Scanner.");
+ }
+
+ private void setInsertButtonText() {
+ String label = getString(R.string.insertbutton, Integer.valueOf(mNumToInsert));
+ Button b = (Button) findViewById(R.id.insertbutton);
+ b.setText(label);
+ }
+
+
+ public void insertItems(View v) {
+ if (mInsertHandler.hasMessages(0)) {
+ mInsertHandler.removeMessages(0);
+ setInsertButtonText();
+ } else {
+ mInsertHandler.sendEmptyMessage(0);
+ }
+ }
+
+ Handler mInsertHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+
+ if (mNumToInsert-- > 0) {
+ addAlbum();
+ runOnUiThread(mDisplayUpdater);
+
+ if (!isFinishing()) {
+ sendEmptyMessage(0);
+ }
+ }
+ }
+ };
+
+ Runnable mDisplayUpdater = new Runnable() {
+ public void run() {
+ mTitle.setText("Added " + mArtists + " artists, " + mAlbums + " albums, "
+ + mSongs + " songs.");
+ }
+ };
+
+ // Add one more album (with 10 songs) to the database. This will be a compilation album,
+ // with one album artist for the album, and a separate artist for each song.
+ private void addAlbum() {
+ try {
+ String albumArtist = "Various Artists";
+ String albumName = getRandomWord(3);
+ int baseYear = 1969 + mRandom.nextInt(30);
+ for (int i = 0; i < 10; i++) {
+ mValues[i].clear();
+ String artist = getRandomName();
+ final ContentValues map = mValues[i];
+ map.put(MediaStore.MediaColumns.DATA,
+ "http://bogus/" + albumName + "/" + artist + "_" + i);
+ map.put(MediaStore.MediaColumns.TITLE,
+ getRandomWord(4) + " " + getRandomWord(2) + " " + (i + 1));
+ map.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
+
+ map.put(Audio.Media.ARTIST, artist);
+ map.put("album_artist", albumArtist);
+ map.put(Audio.Media.ALBUM, albumName);
+ map.put(Audio.Media.TRACK, i + 1);
+ map.put(Audio.Media.DURATION, 4*60*1000);
+ map.put(Audio.Media.IS_MUSIC, 1);
+ map.put(Audio.Media.YEAR, baseYear + mRandom.nextInt(10));
+ }
+ mResolver.bulkInsert(mAudioUri, mValues);
+ mSongs += 10;
+ mAlbums++;
+ mArtists += 11;
+ } catch (SQLiteConstraintException ex) {
+ Log.d("@@@@", "insert failed", ex);
+ }
+ }
+
+ /**
+ * Some code to generate random names. This just strings together random
+ * syllables, and randomly inserts a modifier between the first
+ * and last name.
+ */
+ private String[] elements = new String[] {
+ "ab", "am",
+ "bra", "bri",
+ "ci", "co",
+ "de", "di", "do",
+ "fa", "fi",
+ "ki",
+ "la", "li",
+ "ma", "me", "mi", "mo",
+ "na", "ni",
+ "pa",
+ "ta", "ti",
+ "vi", "vo"
+ };
+
+ private String getRandomWord(int len) {
+ int max = elements.length;
+ mBuilder.setLength(0);
+ for (int i = 0; i < len; i++) {
+ mBuilder.append(elements[mRandom.nextInt(max)]);
+ }
+ char c = mBuilder.charAt(0);
+ c = Character.toUpperCase(c);
+ mBuilder.setCharAt(0, c);
+ return mBuilder.toString();
+ }
+
+ private String getRandomName() {
+ boolean longfirst = mRandom.nextInt(5) < 3;
+ String first = getRandomWord(longfirst ? 3 : 2);
+ String last = getRandomWord(3);
+ switch (mRandom.nextInt(6)) {
+ case 1:
+ if (!last.startsWith("Di")) {
+ last = "di " + last;
+ }
+ break;
+ case 2:
+ last = "van " + last;
+ break;
+ case 3:
+ last = "de " + last;
+ break;
+ }
+ return first + " " + last;
+ }
+
+
+
}
diff --git a/build/sdk.atree b/build/sdk.atree
index 013a1a1..310027b 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -101,6 +101,8 @@
development/samples/SoftKeyboard samples/${PLATFORM_NAME}/SoftKeyboard
development/samples/JetBoy samples/${PLATFORM_NAME}/JetBoy
development/samples/SearchableDictionary samples/${PLATFORM_NAME}/SearchableDictionaryV2
+development/samples/Spinner samples/${PlATFORM_NAME}/Spinner
+development/samples/SpinnerTest samples/${PLATFORM_NAME}/SpinnerTest
development/samples/ContactManager samples/${PLATFORM_NAME}/ContactManager
development/samples/MultiResolution samples/${PLATFORM_NAME}/MultiResolution
development/samples/Wiktionary samples/${PLATFORM_NAME}/Wiktionary
diff --git a/pdk/Pdk.mk b/pdk/Pdk.mk
index 676f5dc..db6570a 100644
--- a/pdk/Pdk.mk
+++ b/pdk/Pdk.mk
@@ -77,7 +77,7 @@
# descriptions for the new headers and point to the new doxygen created html.
pdk_headers := \
$(pdk_legacy_hardware_dir)/AudioHardwareInterface.h \
- $(pdk_legacy_hardware_dir)/gps.h \
+ $(pdk_hardware_dir)/gps.h \
$(pdk_legacy_hardware_dir)/wifi.h \
$(pdk_camera_dir)/CameraHardwareInterface.h \
$(pdk_hardware_dir)/sensors.h \
diff --git a/pdk/docs/about/index.jd b/pdk/docs/about/index.jd
index 2f98b65..43d2a8d 100644
--- a/pdk/docs/about/index.jd
+++ b/pdk/docs/about/index.jd
@@ -3,10 +3,10 @@
doc.hidenav=true
@jd:body
<p>Android is an open-source software stack created for mobile phones and
-other devices. The Android Open Source Project (AOSP) is tasked with the
-maintenance and further development of Android. Many device manufacturers have
-brought to market devices running Android, and they are readibly available
-around the world.</p>
+other devices. The Android Open Source Project (AOSP), led by Google, is
+tasked with the maintenance and further development of Android. Many device
+manufacturers have brought to market devices running Android, and they are
+readibly available around the world.</p>
<p>Our primary purpose is to build an excellent software platform for everyday
users. A number of companies have committed many engineers to achieve this
goal, and the result is a full production quality consumer product whose
diff --git a/pdk/docs/about/philosophy.jd b/pdk/docs/about/philosophy.jd
index 1aa1ccf..1562e42 100644
--- a/pdk/docs/about/philosophy.jd
+++ b/pdk/docs/about/philosophy.jd
@@ -2,7 +2,7 @@
doc.type=about
doc.hidenav=true
@jd:body
-<p>Android is an open-source software stack for mobile phones and similar
+<p>Android is an open-source software stack for mobile phones and other
devices.</p>
<h2>Origin and Goal</h2>
<p>Android was originated by a group of companies known as the Open Handset
@@ -16,12 +16,11 @@
ideas a reality. We wanted to make sure that there was no central point of
failure, where one industry player could restrict or control the innovations
of any other. The solution we chose was an open and open-source platform.</p>
-<p>But the ultimate goal, of course, is to improve the mobile experience for
-real users by facilitating innovation. Accordingly, the primary goal of the
-AOSP is to make sure Android is a success as an end user product.</p>
+<p>The goal of the Android Open Source Project is to create a successful
+real-world product that improves the mobile experience for end users.</p>
<h2>Governance Philosophy</h2>
<p>The companies that have invested in Android have done so on its merits,
-because we collectively believe that an open platform is necessary. Android is
+because we believe that an open platform is necessary. Android is
intentionally and explicitly an open-source -- as opposed to free software --
effort: a group of organizations with shared needs has pooled
resources to collaborate on a single implementation of a shared product.
@@ -34,20 +33,19 @@
Anyone can (and will!) use the Android source code for any purpose, and we
welcome all such uses. However, in order to take part in the shared
ecosystem of applications that we are building around Android, device builders
-can take advantage of the Compatibility Program.</p>
+must participate in the Compatibility Program.</p>
<p>Though Android consists of multiple sub-projects, this is strictly a
project-management technique. We view and manage Android as a single,
holistic software product, not a "distribution", specification, or collection
-of replaceable parts. Conceptually, our notion is that device builders port
+of replaceable parts. Our intent is that device builders port
Android to a device; they don't implement a specification or curate a
distribution.</p>
<h2>How We Work</h2>
<p>We know that quality does not come without hard work. Along with many
partners, Google has contributed full-time engineers, product managers, UI
designers, Quality Assurance, and all the other roles required to bring
-modern devices to market. We integrate the open source administration and
+modern devices to market. We roll the open source administration and
maintenance into the larger product development cycle.</p>
-<p>In a nutshell:</p>
<ul>
<li>At any given moment, there is a current latest release of the Android
platform. This typically takes the form of a branch in the tree.</li>
@@ -56,18 +54,9 @@
features, and so on.</li>
<li>In parallel, Google works internally on the next version of the
Android platform and framework, working according to the product's needs and
-goals. Some of the work from the current latest tree will promoted into these
-releases.</li>
-<li>When the "n+1"th version is determined to be nearing completion, it will
-be published to the public source tree, and become the new latest
-release.</li>
-<li>Since Android is open source, nothing prevents device implementers from
-shipping devices on older (obsolete) Android builds. However, active work will
-be focused on the current platform release.</li>
+goals. We develop the next version of Android by working with a device partner
+on a flagship device whose specifications are chosen to push Android
+in the direction we believe it should go.</li>
+<li>When the "n+1"th version is ready, it will be published to the public
+source tree, and become the new latest release.</li>
</ul>
-<p>To meet our goals, Android needs to achieve widespread, compatible
-adoption. We believe that the best way to accomplish that is to make sure that
-we ship high-quality, flagship devices with an intense product and end-user
-focus. The "next release" of Android is driven by the product needs for the next
-generation of mobile devices; the resulting excellent product is then released
-to open source and becomes the new current version of the platform.</p>
diff --git a/pdk/docs/community/groups-charter.jd b/pdk/docs/community/groups-charter.jd
index 6d5b501..959917e 100644
--- a/pdk/docs/community/groups-charter.jd
+++ b/pdk/docs/community/groups-charter.jd
@@ -1,26 +1,66 @@
page.title=Android Discussion Groups Charter
doc.type=community
+doc.hidenav=true
@jd:body
-<h2>
-Audience
-</h2>
-<p>These discussion groups are intended for developers working with the Android platform. Everyone is welcome to join in, provided you follow our community's policies described below. Our users help each other, and many experts post to these groups, including members of the Open Handset Alliance.
+<h2>Audience</h2>
+<p>These discussion groups are intended for developers working with the
+Android platform. Everyone is welcome to join in, provided you follow our
+community's policies described below. Our users help each other, and many
+experts post to these groups, including members of the Open Handset Alliance.
</p>
-<p>No topic is off-limits, provided it relates to Android in some way. However, since these are very busy lists, search the archives before posting your question; you may find your question has already been answered.
+<p>No topic is off-limits, provided it relates to Android in some way.
+However, since these are very busy lists, search the archives before posting
+your question; you may find your question has already been answered.
</p>
-<h2>
-Mailing list rules
-</h2>
-<p>We love simplicity and hate restrictions, so we keep our policies minimal. The rules below describe what's expected of subscribers to the Android mailing lists.
+<h2>Mailing list rules</h2>
+<p>We love simplicity and hate restrictions, so we keep our policies minimal.
+The rules below describe what's expected of subscribers to the Android mailing
+lists.
</p>
<ul><li><b>Please be friendly</b>
-<br>Showing courtesy and respect to others is a vital part of the Android culture, and we expect everyone participating in the Android community to join us in accepting nothing less. Being courteous does not mean we can't constructively disagree with each other, but it does mean that we must be polite when we do so. There's never a reason to be antagonistic or dismissive toward anyone; if you think there is, think again before you post.<br><br>Mobile development is serious business, but it's also a lot of fun. Let's keep it that way. Let's strive to be one of the friendliest communities in all of open source.<br><br></li>
+<br>Showing courtesy and respect to others is a vital part of the Android
+culture, and we expect everyone participating in the Android community to join
+us in accepting nothing less. Being courteous does not mean we can't
+constructively disagree with each other, but it does mean that we must be
+polite when we do so. There's never a reason to be antagonistic or dismissive
+toward anyone; if you think there is, think again before you
+post.<br><br>Mobile development is serious business, but it's also a lot of
+fun. Let's keep it that way. Let's strive to be one of the friendliest
+communities in all of open source.<br><br></li>
+
<li><b>Allowed discussion topics</b>
-<br>Most topics are technical discussions of Android or users helping each other, but this group is intended for discussions of<i>everything</i>
-in the world of Android. We welcome announcements and discussion of products, libraries, publications, and other interesting Android-related news. We even welcome (polite!) discussion of articles and ideas critical of Android--after all, we can't improve if we don't listen. There are no restrictions on the subject matter, and we don't exclude discussions of commercial products if users are interested in talking about them.<br><br>However, we hate spam almost as passionately as we love courtesy and respect, so we reserve the right to limit discussions that amount to spam. Outright spam will result in the spammer being immediately and permanently banned from the list.
+<br>Most of our groups are for technical discussions of Android or users
+helping each other. Generally we don't put hard restrictions on the topics
+discussed in the group: as long as the topic is relevant to Android in some
+way, it's welcome on our groups. We welcome announcements and discussion of
+products, libraries, publications, and other interesting Android-related news,
+but <b>please do not cross-post</b>. Post only to the most relevant group for
+your message. We even welcome (polite!) discussion of articles and ideas
+critical of Android--after all, we can't improve if we don't listen.<br><br>
</li>
+
+<li><b>Working Lists</b>
+<br>Some of our groups are considered "working lists", by which we mean that the
+list is intended to be used in support of the completion of specific tasks. On
+these groups, we don't welcome off-topic conversations, and will generally ask
+you to take general discussions to a different list. Since these are lists
+where people are trying to get work done, we will be pretty aggressive about
+keeping the noise level low. We ask that you respect our contributors' time
+and keep general discussions to appropriate lists.<br><br>
+</li>
+
+<li><b>Spam</b>
+<br>We hate spam almost as passionately as we love courtesy and respect, so we
+reserve the right to limit discussions that amount to spam. Outright spam will
+result in the spammer being immediately and permanently banned from the list.
+<br><br></li>
</ul>
-<p>The most important rule is friendliness. Remember: disrespect and rudeness are not welcome in our community under any circumstances. We don't have a formal policy on dealing with troublemakers, and we hope we never need one.That said, we do pledge to do our best to be fair, and we will always try to warn someone before banning him or her.
+
+<p>The most important rule is friendliness. Remember: disrespect and rudeness
+are not welcome in our community under any circumstances. We don't have a
+formal policy on dealing with troublemakers, and we hope we never need
+one.That said, we do pledge to do our best to be fair, and we will always try
+to warn someone before banning him or her.
</p>
<h2>
Contacting the moderators
diff --git a/pdk/docs/community/index.jd b/pdk/docs/community/index.jd
index 6e6f59e..853db54 100644
--- a/pdk/docs/community/index.jd
+++ b/pdk/docs/community/index.jd
@@ -1,7 +1,7 @@
-page.title=Community
+page.title=Android Community
doc.type=community
+doc.hidenav=true
@jd:body
-<h1>Android Community</h1>
<p>Welcome to the Android community!</p>
<p>The key to any community is, obviously, communication. Like most projects,
Android communicates via mailing lists. Because Android is an extremely large
@@ -37,6 +37,14 @@
<h2>Open Source Project discussions</h2>
<ul>
+<li><b>android-platform</b><br/>
+This list is for general discussion about the Android open-source project or
+the platform technologies.<br/><br/>
+Subscribe using Google Groups: <a
+href="http://groups.google.com/group/android-platform">android-platform</a><br/>
+Subscribe via email: <a href="mailto:android-platform+subscribe@googlegroups.com">android-platform+subscribe@googlegroups.com</a>
+</li>
+
<li><b>android-building</b><br/>
Subscribe to this list for discussion and help on building the Android source
code, and on the build system. If you've just checked out the source code and
@@ -58,14 +66,14 @@
Subscribe via email: <a href="mailto:android-porting+subscribe@googlegroups.com">android-porting+subscribe@googlegroups.com</a>
</li>
-<li><b>android-platform</b><br/>
-This list is for developers who want to contribute code to the Android
-user-space projects, such as the core system libraries, the Android
-services, the public APIs, or the built-in applications. Note: contributors
+<li><b>android-contrib</b><br/>
+This list is for developers who want to contribute code to Android. This is a
+working list, and is not appropriate for general discussion. We ask that
+general discussion go to android-platform. Note: contributors
to the Android kernel should go to the android-kernel list, below.<br/><br/>
Subscribe using Google Groups: <a
-href="http://groups.google.com/group/android-platform">android-platform</a><br/>
-Subscribe via email: <a href="mailto:android-platform+subscribe@googlegroups.com">android-platform+subscribe@googlegroups.com</a>
+href="http://groups.google.com/group/android-contrib">android-contrib</a><br/>
+Subscribe via email: <a href="mailto:android-contrib+subscribe@googlegroups.com">android-contrib+subscribe@googlegroups.com</a>
</li>
<li><b>android-kernel</b><br/>
@@ -88,9 +96,8 @@
under "subscribe via email" in the lists above.</p>
<p>To set up how you receive mailing list postings by email:</p>
<ol>
-<li>Sign into the group via the Google Groups site. For example, for the android-framework group you would
-visit <a
-href="http://groups.google.com/group/android-framework">http://groups.google.com/group/android-framework</a>.</li>
+<li>Sign into the group via the Google Groups site. For example, for the android-platform group you would
+visit <a href="http://groups.google.com/group/android-platform">http://groups.google.com/group/android-platform</a>.</li>
<li>Click "Edit my membership" on the right side.</li>
<li>Under "How do you want to read this group?" select one of the email options.</li>
</ol>
@@ -106,9 +113,9 @@
community is using, but are not official. These aren't official or officially
moderated/managed, so you use the channels below at your own risk. The Open
Handset Alliance doesn't endorse these channels, there's no warranty express
-or implied, and so on. There may be more.</p>
+or implied, and so on. There may be more channels than just these listed.</p>
<ul>
<li><b>#android-offtopic</b> - for, well, off-topic discussions</li>
<li><b>#android-root</b> - for discussion related to off-label uses of hardware</li>
-<li><b>#androidfra</b> - pour discuter d'Android en français</li>
+<li><b>#android-fr</b> - pour discuter d'Android en français</li>
</ul>
diff --git a/pdk/docs/compatibility/android-1.6-cdd.pdf b/pdk/docs/compatibility/android-1.6-cdd.pdf
new file mode 100644
index 0000000..ba7b4ad
--- /dev/null
+++ b/pdk/docs/compatibility/android-1.6-cdd.pdf
Binary files differ
diff --git a/pdk/docs/compatibility/android-2.1-cdd.pdf b/pdk/docs/compatibility/android-2.1-cdd.pdf
new file mode 100644
index 0000000..4ef6fbd
--- /dev/null
+++ b/pdk/docs/compatibility/android-2.1-cdd.pdf
Binary files differ
diff --git a/pdk/docs/compatibility/android-cts-manual-r4.pdf b/pdk/docs/compatibility/android-cts-manual-r4.pdf
new file mode 100644
index 0000000..f16b6f9
--- /dev/null
+++ b/pdk/docs/compatibility/android-cts-manual-r4.pdf
Binary files differ
diff --git a/pdk/docs/compatibility/compatibility_toc.cs b/pdk/docs/compatibility/compatibility_toc.cs
index 5688d14..536f772 100644
--- a/pdk/docs/compatibility/compatibility_toc.cs
+++ b/pdk/docs/compatibility/compatibility_toc.cs
@@ -7,12 +7,12 @@
<ul>
<li><h2>Getting Started</h2><ul>
<li><a href="<?cs var:toroot ?>compatibility/overview.html">Compatibility Overview</a></li>
- <li><a href="">Current CDD</a></li>
+ <li><a href="<?cs var:toroot ?>compatibility/android-2.1-cdd.pdf">Current CDD</a></li>
<li><a href="<?cs var:toroot ?>compatibility/cts-intro.html">CTS Introduction</a></li>
</ul></li>
<li><h2>More Information</h2><ul>
- <li><a href="<?cs var:toroot ?>downloads/index.html">Downloads</a></li>
+ <li><a href="<?cs var:toroot ?>compatibility/downloads.html">Downloads</a></li>
<li><a href="<?cs var:toroot ?>faqs.html#compatibility">FAQs</a></li>
<li><a href="<?cs var:toroot ?>compatibility/contact-us.html">Contact Us</a></li>
</ul></li>
diff --git a/pdk/docs/compatibility/contact-us.jd b/pdk/docs/compatibility/contact-us.jd
index ba4e887..609c8ab 100644
--- a/pdk/docs/compatibility/contact-us.jd
+++ b/pdk/docs/compatibility/contact-us.jd
@@ -7,13 +7,13 @@
out of any of these options, please first read "Getting the Most from Our
Lists" on the <a href="{@docRoot}community/index.html">Community page.</a></p>
-<h3>Discussion Group</h3>
+<h3>For General Discussion</h3>
<p>The preferred way to reach us is via the <a
href="http://groups.google.com/group/android-compatibility">android-compatibility
mailing list</a>. Use this list for all your compatibility-related questions.
Please be aware that this is a public forum.</p>
-<h3>CTS Technical Questions</h3>
+<h3>For CTS Technical Questions</h3>
<p>If you have specific issues with the Compatibility Test Suite that require
you to disclose information you'd prefer not to be public, you can contact an
email address we've set up specifically this purpose: <a
@@ -23,9 +23,9 @@
list. Note also that this list is for specific technical questions; general
inquiries will also be directed back to the android-compatibility list.</p>
-<h3>Private Inquiries</h3>
+<h3>For Business Inquiries</h3>
<p>Finally, business inquiries about the compatibility program, including
requests to use branding elements and so on, can be sent to the address <a
-href="mailto:compatibility@android.com">compatibility@android.com</a>. Like
+href="mailto:android-partnerships@google.com">android-partnerships@google.com</a>. Like
the CTS address, this address is for specific, private inquiries; general
questions will be directed back to the android-compatibility list.</p>
diff --git a/pdk/docs/compatibility/downloads.jd b/pdk/docs/compatibility/downloads.jd
new file mode 100644
index 0000000..b97ce9f
--- /dev/null
+++ b/pdk/docs/compatibility/downloads.jd
@@ -0,0 +1,38 @@
+page.title=Android Compatibility Downloads
+doc.type=compatibility
+@jd:body
+<p>Thanks for your interest in Android Compatibility! The links below allow
+you to access the key documents and information.</p>
+
+<h2>Android 2.1</h2>
+<p>Android 2.1 is the release of the development milestone code-named
+Eclair. Android 2.1 is the current version of Android. Source code for Android
+2.1 is found in the 'eclair' branch in the open-source tree. Note that for
+technical reasons, there is no compatibility program for Android 2.0 or 2.0.1,
+and new devices must use Android 2.1.
+</p>
+<ul>
+ <li><a href="{@docRoot}compatibility/android-2.1-cdd.pdf">Android 2.1 Compatibility Definition Document (CDD)</a></li>
+</ul>
+
+<h2>Android 1.6</h2>
+<p>Android 1.6 was the release of the development milestone code-named Donut.
+Android 1.6 was obsoleted by Android 2.1. Source code for Android 1.6 is found
+in the 'donut' branch in the open-source tree.
+<ul>
+ <li><a href="{@docRoot}compatibility/android-1.6-cdd.pdf">Android 1.6 Compatibility Definition Document (CDD)</a></li>
+</ul>
+
+<h2>Compatibility Test Suite</h2>
+<p>The Compatibility Test Suite (CTS) is found in the open-source tree. Specific
+snapshots of the CTS are found in the same branches as for the system. (For
+instance, the Android 2.1 CTS can be found in the 'eclair' branch.) The CTS user
+manual is not specific to given versions.
+<ul>
+ <li><a href="{@docRoot}compatibility/android-cts-manual-r4.pdf">Compatibility Test Suite (CTS) User Manual</a></li>
+</ul>
+
+<h2>Older Android Versions</h2>
+<p>There is no Compatibility Program for older versions of Android, such as Android
+1.5 (known in development as Cupcake). New devices intended to be Android
+compatible must ship with Android 1.6 or later.</p>
diff --git a/pdk/docs/compatibility/index.jd b/pdk/docs/compatibility/index.jd
index 0c39a98..55be052 100644
--- a/pdk/docs/compatibility/index.jd
+++ b/pdk/docs/compatibility/index.jd
@@ -1,17 +1,21 @@
page.title=Android Compatibility
doc.type=compatibility
@jd:body
-<p>Android is an open source product, and anyone can use the source code to build
-devices. The purpose of the Android compatibility program is to help Android
-device implementations remain compatible with all apps.</p>
-<p>A device is considered compatible if existing and new third-party
-applications run correctly on it. Poor device implementations that change APIs
-or alter behaviors will break these apps and so are not compatible. The
-Android compatibility program's aim is to ensure that these APIs are
-consistently implemented across devices.</p>
-<p>The latest version of the Android source code and compatibility program is
-1.6, which roughly corresponded to the Donut branch. The compatibility
-program for Android 2.x (corresponding to Eclair) is coming soon.</p>
+<p>Android's purpose is to establish an open platform for developers to build
+innovative mobile apps. Three key components work together to realize this
+platform.</p>
+<p>The Android Compatibility Program defines the technical details of Android
+platform and provides tools used by OEMs to ensure that developers’ apps run
+on a variety of devices. The Android SDK provides built-in tools that
+Developers use to clearly state the device features their apps require. And
+Android Market shows apps only to those devices that can properly run
+them.</p>
+<p>These pages describe the Android Compatibility Program and how to get
+access to compatibility information and tools. The latest version of the
+Android source code and compatibility program is 2.1, which roughly
+corresponded to the Eclair branch.</p>
+
+
<h2>Why build compatible Android devices?</h2>
<h3>Users want a customizable device.</h3>
<p>A mobile phone is a highly personal, always-on, always-present gateway to
@@ -20,7 +24,7 @@
platform for running after-market applications.</p>
<h3>Developers outnumber us all.</h3>
-<p>No device manufacturer can hope to write all the software that anyone could
+<p>No device manufacturer can hope to write all the software that a person could
conceivably need. We need third-party developers to write the apps users want,
so the Android Open Source Project aims to make it as easy and open as
possible for developers to build apps.</p>
@@ -38,30 +42,23 @@
sure your device is compatible with Android. For more details about the
Android compatibility program in general, see <a
href="{@docRoot}compatibility/overview.html">the program overview</a>.</p>
-<p>Building a compatible device is a four-step process:</p>
+<p>Building a compatible device is a three-step process:</p>
<ol>
- <li><b>Obtain the Android software stack source code</b><p>This is the
+ <li><b>Obtain the Android software source code</b><p>This is the
<a href="{@docRoot}source/index.html">source code for the Android
platform</a>, that you port to your hardware.</p></li>
- <li><b>Comply with Android Compatibility Definition Document</b><p>
- This document enumerates the software and the hardware features of
+ <li><b>Comply with Android Compatibility Definition Document (CDD)</b><p>
+ The CDD enumerates the software and hardware requirements of
a compatible Android device.</p></li>
<li><b>Pass the Compatibility Test Suite (CTS)</b><p>You can use the CTS
(included in the Android source code) as an ongoing aid to compatibility
during the development process.</p></li>
- <li><b>Submit CTS report</b><p>[Optional] You can also submit your CTS report,
- so that it can be validated and recorded.</p><p><i>Note:
- the submission system is currently under construciton, and is not currently
- available.</i></p></li>
</ol>
-<h2>Benefits of compatibility</h2>
-<p>By submitting a validated CTS report, you receive public recognition of
-your device's compatibility. This also opens up additional options you can
-pursue such as use of the Android branding, access to Android Market, and
-more.</p>
-<p>As a consequence of some legal quirks, we aren't able to offer automatic
-licensing of either the Android Market or branding. To actually obtain access
-to these programs, you will need to <a
-href="{@docRoot}compatibility/contact-us.html">contact us</a> to obtain a
-license.</p>
+<h2>Joining the Ecosystem</h2>
+<p>Once you've built a compatible device, you may wish to include Android
+Market to provide your users access to the third-party app ecosystem.
+Unfortunately, for a variety of legal and business reasons, we aren't able to
+automatically license Android Market to all compatible devices. To inquire
+about access about Android Market, you can <a
+href="{@docRoot}compatibility/contact-us.html">contact us</a></p>
diff --git a/pdk/docs/downloads/downloads_toc.cs b/pdk/docs/downloads/downloads_toc.cs
deleted file mode 100644
index 28f43af..0000000
--- a/pdk/docs/downloads/downloads_toc.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-<script type="text/javascript" language="JavaScript">
-<!--
-function nothing() {}
--->
-</script>
-
-<ul>
- <li><h2>PDK</h2><ul>
- <li><a href="">PDK 1.6</a></li>
- </ul></li>
-
- <li><h2>Compatibility</h2><ul>
- <li><a href="">Android 1.6</a></li>
- </ul></li>
-</ul>
-
-<script type="text/javascript">
-<!--
- buildToggleLists();
-//-->
-</script>
diff --git a/pdk/docs/downloads/index.jd b/pdk/docs/downloads/index.jd
deleted file mode 100644
index b6f5a2f..0000000
--- a/pdk/docs/downloads/index.jd
+++ /dev/null
@@ -1,44 +0,0 @@
-page.title=Downloads
-doc.type=downloads
-doc.hidenav=true
-@jd:body
-<p>This page provides access to various downloads. Note that if you're looking
-for the Android SDK (for application developers), you should visit <a
-href="http://developer.android.com/sdk/index.html">developer.android.com</a>.</p>
-
-<h2>Compatibility</h2>
-<p>The Compatibility Definition Document can be downloaded below. The
-Compatibility Test Suite is available in the open-source tree.</p>
-<table class="download">
- <tr>
- <th>Item</th>
- <th>File</th>
- <th>Size</th>
- </tr>
- <tr>
- <td>Android CDD 2.1</td>
- <td><a href="">android-cdd-2.1.pdf</a></td>
- <td>23070805 bytes</td>
- </tr>
- <tr class="alt-color">
- <td>Android CTS 2.1 Manual</td>
- <td><a href="">android-cts-manual-2.1.0.pdf</a></td>
- <td>23070805 bytes</td>
- </tr>
- <tr>
- <td>Android CDD 1.6</td>
- <td><a href="">android-cdd-1.6.pdf</a></td>
- <td>23070805 bytes</td>
- </tr>
- <tr class="alt-color">
- <td>Android CTS 1.6 Manual</td>
- <td><a href="">android-cts-manual-1.6.4.pdf</a></td>
- <td>23070805 bytes</td>
- </tr>
-</table>
-<p>For more information on how to build an Android-compatible device, see the
-<a href="{@docRoot}compatibility/index.html">Compatibility</a> page. Note that
-there is no compatibility program for Android 1.5 and earlier. Note also that
-there is no compatibility program for Android 2.0, since it was superceded by
-Android 2.1 after only a few weeks.
-</p>
diff --git a/pdk/docs/faqs.jd b/pdk/docs/faqs.jd
index a55d380..2506f08 100644
--- a/pdk/docs/faqs.jd
+++ b/pdk/docs/faqs.jd
@@ -74,8 +74,8 @@
<p>Finally, Google works on the next version of the Android platform in tandem
with developing a flagship device. This branch pulls in changes from the
experimental and stable branches as appropriate.</p>
-<p>You can find more information on this topic at our Branches Releases
- page.</p>
+<p>You can find more information on this topic at our <a
+href="{@docRoot}source/code-lines.html">Branches and Releases</a> page.</p>
<h3>Why are parts of Android developed in private?</h3>
<p>It typically takes over a year to bring a device to market, but of course
@@ -143,8 +143,7 @@
"Android compatible devices" from devices that merely run derivatives of the
source code. We welcome all uses of the Android source code, but only
Android compatible devices -- as defined and tested by the Android
- Compatibility Program -- may call themselves "Android" and participate in
- the Android ecosystem.</p>
+ Compatibility Program -- may participate in the Android ecosystem.</p>
<h3>How can I contribute to Android?</h3>
<p>There are a number of ways you can contribute to Android. You can report
@@ -170,8 +169,9 @@
<p>Once submitted, changes need to be accepted by a designated Approver.
Approvers are typically Google employees, but the same approvers are
responsible for all submissions, regardless of origin.</p>
-<p>You can find more information on this topic at the Submitting Patches
- page.</p>
+<p>You can find more information on this topic at the <a
+ href="{@docRoot}source/submit-patches.html">Submitting Patches</a>
+ page.</p>
<a name="compatibility"></a><h2>Compatibility</h2>
<h3>What does "compatibility" mean?</h3>
@@ -185,7 +185,7 @@
<p>In other words, compatibility is a prerequisite to participate in the
Android apps ecosystem. Anyone is welcome to use the Android source code,
but if the device isn't compatible, it's not considered part of the Android
- ecosystem, and irrelevant to developers.</p>
+ ecosystem.</p>
<h3>What is the role of Android Market in compatibility?</h3>
<p>Devices that are Android compatible may seek to license the Android Market
@@ -200,11 +200,11 @@
Compatibility Definition Document (CDD) spells out the specific device
configurations that will be considered compatible.</p>
<p>For example, though the Android source code could be ported to run on a
- device that doesn't have a camera, the CDD requires that in order to be
- compatible, all devices must have a camera. This allows developers to rely
- on a consistent set of device capabilities when writing their apps.</p>
+ phone that doesn't have a camera, the CDD requires that in order to be
+ compatible, all phones must have a camera. This allows developers to rely
+ on a consistent set of capabilities when writing their apps.</p>
<p>The CDD will evolve over time to reflect market realities. For instance,
- the 1.6 CDD only allows cell phones, but the 2.x CDD allows devices to omit
+ the 1.6 CDD only allows cell phones, but the 2.1 CDD allows devices to omit
telephony hardware, allowing for non-phone devices such as tablet-style
music players to be compatible. As we make these changes, we will also
augment Android Market to allow developers to retain control over where
@@ -271,12 +271,15 @@
generally have much effect on third-party apps. As such, device builders are
free to customize the user interface as much as they like. The Compatibility
Definition Document does restrict the degree to which OEMs may alter the
- system user interface for the few areas that do impact third-party apps.</p>
+ system user interface for areas that do impact third-party apps.</p>
<h3>When are compatibility definitions released for new Android versions?</h3>
<p>Our goal is to release new versions of Android Compatibility Definition
Documents (CDDs) once the corresponding Android platform version has
- converged enough to permit it. Since the CDDs</p>
+ converged enough to permit it. While we can't release a final draft of a CDD
+ for an Android software version before the first flagship device ships with
+ that software, final CDDs will always be released after the first device.
+ However, wherever practical we will make draft versions of CDDs available.</p>
<h3>How are device manufacturers' compatibility claims validated?</h3>
<p>There is no validation process for Android device compatibility. However,
diff --git a/pdk/docs/index.jd b/pdk/docs/index.jd
index 217877d..76f48da 100644
--- a/pdk/docs/index.jd
+++ b/pdk/docs/index.jd
@@ -1,7 +1,19 @@
page.title=Welcome to Android
home=true
@jd:body
-<div style="float: left; width: 45%; font-size: 1.3em;">
+<div style="float: right; width: 35%;">
+<h3 style="padding-top: 0px;">News</h3>
+<p><b>Introducing the Compatibility Program</b><br/>
+We're pleased to introduce the Android Compatibility Program. We've released
+two tools -- the Compatibility Definition Document and the Compatibility Test
+Suite -- to help device manufacturers build compatible devices.</p>
+<p><b>Site redesign</b><br/>
+You're looking at the new and improved source.android.com! We've updated
+the layout and site design, and also added new information. We hope you find
+these improvements helpful.</p>
+</div>
+<img style="float: right; padding-right: 1.5em;" src="{@docRoot}images/home-bugdroid.png" alt="Android Mascot"/>
+<div style="font-size: 1.3em;">
<p>Here you can find the information and source code you need to build an
Android-compatible device.</p>
<p>Android is an open-source software stack for mobile devices, and a
@@ -12,51 +24,40 @@
created Android, and made its source code open.</p>
<p><a href="{@docRoot}about/index.html">Learn more »</a></p>
</div>
-<div style="float: right; width: 35%;">
-<h3 style="padding-top: 0px;">News</h3>
-<p><b>Site redesign</b><br/>
-You're looking at the new and improved source.android.com! We've updated
-the layout and site design, and also added new information. We hope you find
-these improvements helpful.</p>
-<p><b>Introducing the Compatibility Program</b><br/>
-We're pleased to introduce the Android Compatibility Program. We've released
-two tools -- the Compatibility Definition Document and the Compatibility Test
-Suite -- to help device manufacturers build compatible devices. Full details
-of the Compatibility Program will be available in the first quarter of 2010.</p>
-</div>
-<img style="float: right; padding-right: 1.5em;" src="{@docRoot}images/home-bugdroid.png" alt="Android Mascot"/>
<div style="clear: both;"/>
<table border="0" style="border: 0px; margin: 0px; padding: 0px;"><tr><td align="center" style="border: 0px; margin: 0px; padding: 0px;">
<div class="rebox lil" style="float: left; width: 30%; margin: 1em;">
- <h2 style="color: white; background-color: #95C0D0; border: 0px;">Get Involved</h2>
+ <h2 style="color: white; background-color: #95C0D0; border: 0px;">Source</h2>
<div class="p">
- <p><img src="images/lil-wrench.png" alt="" style="margin: 1em;"/>
+ <p><img src="images/lil-wrench.png" alt="" style="margin: 1em; margin-bottom: 5em;"/>
If you're interested in contributing to the Android source code or helping
- out with the project in some other way, click here.</p>
- <p><a href="{@docRoot}source/index.html">More »</a></p>
+ out with the open-source project, our Source pages have the information
+ you need.</p>
+ <p><a href="{@docRoot}source/index.html">Get Involved »</a></p>
</div>
</div>
<div class="rebox lil" style="float: left; width: 30%; margin: 1em;">
- <h2 style="color: white; background-color: #95C0D0; border: 0px;">Build a Device</h2>
+ <h2 style="color: white; background-color: #95C0D0; border: 0px;">Porting</h2>
<div class="p">
- <p><img src="images/lil-wrench.png" alt="" style="margin: 1em;"/>
- If you're an engineer building a device intended to run the Android
- software stack, click here to find porting information and tips.</p>
- <p><a href="{@docRoot}porting/index.html">More »</a></p>
+ <p><img src="images/lil-wrench.png" alt="" style="margin: 1em; margin-bottom: 5em;"/>
+ If you're an engineer building a device
+ intended to run the Android software stack, look at our Porting pages for
+ information and tips.</p>
+ <p><a href="{@docRoot}porting/index.html">Build a Device »</a></p>
</div>
</div>
<div class="rebox lil" style="float: left; width: 30%; margin: 1em;">
<h2 style="color: white; background-color: #95C0D0; border: 0px;">Compatibility</h2>
<div class="p">
- <p><img src="images/lil-wrench.png" alt="" style="margin: 1em;"/>
- If you're an OEM or other organization building an Android device, click
- here to find out how to ensure that your device is fully compatible, and
- how to take advantage of the benefits of compatibility.</p>
- <p><a href="{@docRoot}compatibility/index.html">More »</a></p>
+ <p><img src="images/lil-wrench.png" alt="" style="margin: 1em; margin-bottom: 5em;"/>
+ If you're an organization building an Android device, you'll want to check out our
+ Compatibility pages to find out how to take advantage of the benefits of
+ compatibility.</p>
+ <p><a href="{@docRoot}compatibility/index.html">Get Compatible »</a></p>
</div>
</div>
</td></tr></table>
diff --git a/pdk/docs/source/building-dream.jd b/pdk/docs/source/building-dream.jd
index 89392fd..d130524 100644
--- a/pdk/docs/source/building-dream.jd
+++ b/pdk/docs/source/building-dream.jd
@@ -3,10 +3,10 @@
@jd:body
<p><i>The information on this page is a bit out of date. We'll update this
page as soon as we can.</i></p>
-<div>The basic manifest for cupcake (and above) defines which projects are
+<div>The basic manifest for 1.6 defines which projects are
needed to do a generic build for the emulator or for unlocked Dream devices
(e.g. the Android Dev Phone 1). You need to have an appropriate device running
-a matching official image.<br><br>To build donut or master for dream (your
+a matching official image.<br><br>To build donut for dream (your
device needs to be an ADP1 running an official 1.6 system):<br><ol><li>Follow
the <a href="{@docRoot}source/download.html">normal steps</a>
to setup repo and check out the sources.
@@ -22,17 +22,6 @@
</li>
<li>from this point, the fastboot tool (which is put automatically in your path) can be used to flash a device: boot the device into the bootloader by holding the back key while pressing the power key, and run "fastboot -w flashall".<br></li>
</ol>
-To build cupcake for dream (your device needs to be an ADP1 running an official 1.5 system):<br><ol><li>Follow the <a href="{@docRoot}source/download.html">normal steps</a>
-to setup repo and check out the sources.
-</li>
-<li>At the root of your source tree, run ". build/envsetup.sh" like you normally would for an emulator build.
-</li>
-<li>Run "make adb" if you don't already have adb in your path.<br></li>
-<li>in vendor/htc/dream-open/ there is a script called "extract-files.sh" that must be run (from that directory) to extract some proprietary binaries from your device (*). You only need to do this once.<br></li>
-<li>run "lunch htc_dream-eng" to specifically configure the build system for dream (the default is the equivalent of "lunch generic-eng", which doesn't contain dream-specific files).<br></li>
-<li>run make from the top of the source tree.
-</li>
-<li>from this point, the fastboot tool (which is put automatically in your path) can be used to flash a device: boot the device into the bootloader by holding the back key while pressing the power key, and run "fastboot -w flashall".<br></li>
-</ol>
-* The Dream device software contains some proprietary binaries.For contractual reasons, these cannot be redistributed to be used directly with the Android Open-Source Project, but the provided script may be used to extract these binaries from your development device so that they can be correctly included in your build.These libraries include the openGL|ES library, the Qualcomm camera library, the HTC Radio Interface Library, etc.
+<p>Note: these instructions work for the sapphire (ADP2) build target, as
+well. Simply replace "dream" with "sapphire" above.</p>
</div>
diff --git a/pdk/docs/source/code-lines.jd b/pdk/docs/source/code-lines.jd
index 61b400d..09e90cd 100644
--- a/pdk/docs/source/code-lines.jd
+++ b/pdk/docs/source/code-lines.jd
@@ -62,9 +62,9 @@
Google retains responsibility for the strategic direction of Android as a
platform and a product. Our approach is based on focusing on a small number of
flagship devices to drive features, and secure protections of Android-related
-intellectual property through patents and the like.</p>
+intellectual property.</p>
<p>As a result, Google frequently has possession of confidential
-information of third parties, and we must refrain from revealing patentable
+information of third parties, and we must refrain from revealing sensitive
features until we've secured the appropriate protections. Meanwhile, there are
real risks to the platform arising from having too many platform versions
extant at once. For these reasons, we have structured the open-source project
diff --git a/pdk/docs/source/index.jd b/pdk/docs/source/index.jd
index 230a0b3..a9acbf4 100644
--- a/pdk/docs/source/index.jd
+++ b/pdk/docs/source/index.jd
@@ -3,7 +3,7 @@
@jd:body
<div>
<p>Thanks for your interest in Android! Here are some ways you can get involved
-and help Google improve Android. For background on the Android project and our
+and help us improve Android. For background on the Android project and our
goals, check out the <a href="{@docRoot}about/philosophy.html">Project
Philosophy page</a>.</p>
@@ -11,7 +11,7 @@
<p>One of the easiest and most effective ways you can help improve Android is
to file bugs. For more information, visit the <a
href="{@docRoot}source/report-bugs.html">Reporting Bugs</a> page.</p>
-<p>Please note that we can't guarantee that any particular bug can be fixed in
+<p>Please note that we can't guarantee that any particular bug will be fixed in
any particular release. To see what happens to your bug once you report it,
read <a href="{@docRoot}source/life-of-a-bug.html">Life of a Bug</a>.</p>
@@ -26,8 +26,10 @@
<h2>Contribute to the Code</h2>
<p>Code is King. We'd love to review any changes you submit, so please check
-out the source, pick a bug or feature, and get coding.</p>
-<p>You can get started with by learning about the <a
+out the source, pick a bug or feature, and get coding. Note that the smaller
+and more targetted your patch submissions, the easier it will be for us to
+review them.</p>
+<p>You can get started with Android by learning about the <a
href="{@docRoot}source/life-of-a-patch.html">Life of a Patch</a>, and by
learning about <code>git</code>, <code>repo</code>, and other tools using the
links to the left. If you need help along the way, you can join our <a
diff --git a/pdk/docs/source/licenses.jd b/pdk/docs/source/licenses.jd
index 846a92a..17cebeb 100644
--- a/pdk/docs/source/licenses.jd
+++ b/pdk/docs/source/licenses.jd
@@ -4,18 +4,15 @@
<div>
<p>The Android Open Source Project uses a few <a
href="http://www.opensource.org/">open source initiative</a> approved open
-source licenses to enable availability of source code and to accept
-contributions from individuals and corporations.</p>
+source licenses for our software.</p>
<h2>Android Open Source Project license</h2>
-<p>The preferred license for the Android Open Source Project is <a
-href="http://www.apache.org/licenses/LICENSE-2.0">Apache 2.0</a>. Apache 2.0
-is a commercial and open source friendly open source license. The majority of
-the Android platform is licensed under the <a
-href="http://www.apache.org/licenses/">Apache 2.0 license</a>. While the
-project will strive to adhere to the preferred license, there may be
-exceptions which will be handled on a case-by-case basis. For example, the
-Linux kernel patches are under the GPLv2 license with system exceptions, which
-can be found on <a
+<p>The preferred license for the Android Open Source Project is the <a
+href="http://www.apache.org/licenses/LICENSE-2.0">Apache Software License,
+2.0</a> ("Apache 2.0"), and the majority of the Android software is licensed
+with Apache 2.0. While the project will strive to adhere to the preferred
+license, there may be exceptions which will be handled on a case-by-case
+basis. For example, the Linux kernel patches are under the GPLv2 license with
+system exceptions, which can be found on <a
href="http://www.kernel.org/pub/linux/kernel/COPYING">kernel.org</a>.
</p>
<h2>Contributor License Grants</h2>
@@ -25,15 +22,15 @@
href="{@docRoot}source/cla-individual.html">Individual
Contributor License Grant</a>. The grant can be executed online through the <a
href="https://review.source.android.com/#settings,agreements">code review
-tool</a>. The agreement clearly defines the terms under which intellectual
-property has been contributed to the Android Open Source Project.This license
+tool</a>. The grant clearly defines the terms under which intellectual
+property has been contributed to the Android Open Source Project. This license
is for your protection as a contributor as well as the protection of the
project; it does not change your rights to use your own contributions for any
other purpose.</p>
<p>For a <b>corporation</b> (or other entity) that has assigned employees to
work on the Android Open Source Project, a <a
href="{@docRoot}source/cla-corporate.html">Corporate
-Contributor License Grant</a> is available. This version of the Grant allows a
+Contributor License Grant</a> is available. This version of the grant allows a
corporation to authorize contributions submitted by its designated employees
and to grant copyright and patent licenses. Note that a Corporate Contributor
License Grant does not remove the need for any developer to sign their own
diff --git a/pdk/docs/source/life-of-a-bug.jd b/pdk/docs/source/life-of-a-bug.jd
index 1d58ae1..5d77f7a 100644
--- a/pdk/docs/source/life-of-a-bug.jd
+++ b/pdk/docs/source/life-of-a-bug.jd
@@ -57,7 +57,7 @@
which is considered the "master" copy. (For instance, Google maintains one
such private issue tracker, intended primarily for bugs which contain
sensitive information which can't be revealed publicly.)</p></li>
-<li><b>Assigned</b><li>Like <code>Unassigned</code>, but the bug has been
+<li><b>Assigned</b><p>Like <code>Unassigned</code>, but the bug has been
actually assigned to a specific contributor to fix.</p></li>
</ul>
<p>Typically, a given bug will start in <code>Unassigned</code>, where it
@@ -77,8 +77,8 @@
<li><b>Spam</b><p>A kind soul sent us some delicious pork products, that we,
regrettably, do not want.</p></li>
<li><b>Question</b><p>Someone mistook the issue tracker for a help forum.
-(This is not as uncommon as one might assume: many users whose native language
-isn't English can make this mistake.)</p></li>
+(This is not as uncommon as you might think: many users whose native language
+isn't English misunderstand the site and make this mistake.)</p></li>
<li><b>Unreproducible</b><p>An AOSP contributor attempted to reproduce the
behavior described, and was unable to do so. This sometimes means that the bug
is legitimate but simply rare or difficult to reproduce, and sometimes means
diff --git a/pdk/docs/source/roles.jd b/pdk/docs/source/roles.jd
index 451c821..f4fb891 100644
--- a/pdk/docs/source/roles.jd
+++ b/pdk/docs/source/roles.jd
@@ -15,22 +15,22 @@
<h2>Contributor</h2>
<p>A "Contributor" is anyone making contributions to the AOSP source code,
-including both employees of Google or other companies, as well as
-external developers who are contributing to Android on their own behalf.
-There is no distinction between Contributors who are employed by
-Google, and those who are not: all engineers use the same git/gerrit tools,
-follow the same code review process, and are subject to the same requirements
-on code style and so on.</p>
+including both employees of Google or other companies, as well as external
+developers who are contributing to Android on their own behalf. There is no
+distinction between Contributors who are employed by Google, and those who are
+not: all engineers use the same tools (<code>git</code>, <code>repo</code>,
+and <code>gerrit</code>), follow the same code review process, and are subject
+to the same requirements on code style and so on.</p>
<p/>
<h2>Developer</h2>
<p>A "Developer" is an engineer writing applications that run on Android
devices. There is, of course, no difference in skillset between a "Developer"
-and a "Contributor"; AOSP simply uses "Developer" to help identify our audience.
-Since the key purpose of Android is to cultivate an open development platform,
-"Developers" are one of the key customers of the Android platform. As such, we
-talk about them a lot, though this isn't technically a separate role in the
-AOSP <i>per se.</i></p>
+and a "Contributor", but AOSP uses "Developer" to distinguish between
+engineers using the platform and those contributing to it. Developers are
+(along with end users) the "customers" of the platform that the Contributors
+create. As such, we talk about Developers a lot, though this isn't technically
+a separate role in the AOSP <i>per se.</i></p>
<p/>
<h2>Verifier</h2>
@@ -62,12 +62,12 @@
releases.</li>
<li>Designate Verifiers and Approvers for submitted patches.</li>
<li>Be fair and unbiased while reviewing changes. Accept or reject patches
- based on technical merit and alignment with the Android platform.</li>
+ based on technical merit and alignment with the Android strategy.</li>
<li>Review changes in a timely manner and make best efforts to communicate
when changes are not accepted.</li>
<li>Optionally maintain a web site for the project for information and
documents specific to the project.</li>
<li>Act as a facilitator in resolving technical conflicts.</li>
- <li>Be the public face for the project and the go-to person for questions
+ <li>Be a public face for the project and the go-to person for questions
related to the project.</li>
</ul>
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index 1e3f66b..84af770 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -207,6 +207,15 @@
</intent-filter>
</activity>
+ <!-- Fragment Samples -->
+
+ <activity android:name=".app.SimpleFragment" android:label="@string/simple_fragment">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+
<!-- Intent Samples -->
<activity android:name=".app.Intents" android:label="@string/activity_intents">
diff --git a/samples/ApiDemos/res/layout/gallery_1.xml b/samples/ApiDemos/res/layout/gallery_1.xml
index 9b9c4bb..8239ef0 100644
--- a/samples/ApiDemos/res/layout/gallery_1.xml
+++ b/samples/ApiDemos/res/layout/gallery_1.xml
@@ -14,8 +14,19 @@
limitations under the License.
-->
-<Gallery xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gallery"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout2"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+<Gallery android:id="@+id/gallery"
+android:layout_width="fill_parent"
+android:layout_height="wrap_content"
/>
-
+<EditText
+ android:text="@+id/EditText01"
+ android:id="@+id/EditText01"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"></EditText>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/ApiDemos/res/layout/simple_fragment.xml b/samples/ApiDemos/res/layout/simple_fragment.xml
new file mode 100644
index 0000000..d0fbb9f
--- /dev/null
+++ b/samples/ApiDemos/res/layout/simple_fragment.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Top-level content view for the simple fragment sample. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:padding="4dip"
+ android:gravity="center_horizontal"
+ android:layout_width="match_parent" android:layout_height="match_parent">
+
+ <FrameLayout
+ android:id="@+id/simple_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1" />
+
+ <Button android:id="@+id/next"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:text="@string/next">
+ <requestFocus />
+ </Button>
+
+</LinearLayout>
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index 802fc80..ce3bf74 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -83,6 +83,9 @@
<string name="redirect_getter">Enter the text that will be used by the main activity. Press back to cancel.</string>
<string name="apply">Apply</string>
+ <string name="simple_fragment">App/Fragment/Simple</string>
+ <string name="next">Next</string>
+
<string name="activity_menu">App/Activity/Menu</string>
<string name="open_menu">Open menu</string>
<string name="close_menu">Close menu</string>
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/SimpleFragment.java b/samples/ApiDemos/src/com/example/android/apis/app/SimpleFragment.java
new file mode 100644
index 0000000..d3fda7e
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/SimpleFragment.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+public class SimpleFragment extends Activity {
+ int mWhichFragment;
+ Fragment mCurFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.simple_fragment);
+ switchToFragment(0);
+
+ // Watch for button clicks.
+ Button button = (Button)findViewById(R.id.next);
+ button.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ switchToFragment(mWhichFragment == 0 ? 1 : 0);
+ }
+ });
+ }
+
+ void switchToFragment(int which) {
+ mWhichFragment = which;
+ Fragment newFragment = which == 0
+ ? new FirstFragment() : new SecondFragment();
+ FragmentTransaction ft = openFragmentTransaction();
+ if (mCurFragment != null) {
+ ft.remove(mCurFragment);
+ }
+ ft.add(newFragment, R.id.simple_fragment);
+ mCurFragment = newFragment;
+ ft.commit();
+ }
+
+ class FirstFragment extends Fragment {
+ public View onCreateView(LayoutInflater inflater, ViewGroup container) {
+ return inflater.inflate(R.layout.hello_world, container, false);
+ }
+ }
+
+ class SecondFragment extends Fragment {
+ public View onCreateView(LayoutInflater inflater, ViewGroup container) {
+ View v = inflater.inflate(R.layout.hello_world, container, false);
+ View tv = v.findViewById(R.id.text);
+ ((TextView)tv).setText("Second Hello World!");
+ return v;
+ }
+ }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/view/Gallery1.java b/samples/ApiDemos/src/com/example/android/apis/view/Gallery1.java
index a539a5b..e33bea7 100644
--- a/samples/ApiDemos/src/com/example/android/apis/view/Gallery1.java
+++ b/samples/ApiDemos/src/com/example/android/apis/view/Gallery1.java
@@ -71,8 +71,25 @@
}
public class ImageAdapter extends BaseAdapter {
- int mGalleryItemBackground;
-
+ private static final int ITEM_WIDTH = 136;
+ private static final int ITEM_HEIGHT = 88;
+
+ private final int mGalleryItemBackground;
+ private final Context mContext;
+
+ private final Integer[] mImageIds = {
+ R.drawable.gallery_photo_1,
+ R.drawable.gallery_photo_2,
+ R.drawable.gallery_photo_3,
+ R.drawable.gallery_photo_4,
+ R.drawable.gallery_photo_5,
+ R.drawable.gallery_photo_6,
+ R.drawable.gallery_photo_7,
+ R.drawable.gallery_photo_8
+ };
+
+ private final float mDensity;
+
public ImageAdapter(Context c) {
mContext = c;
// See res/values/attrs.xml for the <declare-styleable> that defines
@@ -81,6 +98,8 @@
mGalleryItemBackground = a.getResourceId(
R.styleable.Gallery1_android_galleryItemBackground, 0);
a.recycle();
+
+ mDensity = c.getResources().getDisplayMetrics().density;
}
public int getCount() {
@@ -96,30 +115,25 @@
}
public View getView(int position, View convertView, ViewGroup parent) {
- ImageView i = new ImageView(mContext);
+ ImageView imageView;
+ if (convertView == null) {
+ convertView = new ImageView(mContext);
- i.setImageResource(mImageIds[position]);
- i.setScaleType(ImageView.ScaleType.FIT_XY);
- i.setLayoutParams(new Gallery.LayoutParams(136, 88));
+ imageView = (ImageView) convertView;
+ imageView.setScaleType(ImageView.ScaleType.FIT_XY);
+ imageView.setLayoutParams(new Gallery.LayoutParams(
+ (int) (ITEM_WIDTH * mDensity + 0.5f),
+ (int) (ITEM_HEIGHT * mDensity + 0.5f)));
- // The preferred Gallery item background
- i.setBackgroundResource(mGalleryItemBackground);
-
- return i;
+ // The preferred Gallery item background
+ imageView.setBackgroundResource(mGalleryItemBackground);
+ } else {
+ imageView = (ImageView) convertView;
+ }
+
+ imageView.setImageResource(mImageIds[position]);
+
+ return imageView;
}
-
- private Context mContext;
-
- private Integer[] mImageIds = {
- R.drawable.gallery_photo_1,
- R.drawable.gallery_photo_2,
- R.drawable.gallery_photo_3,
- R.drawable.gallery_photo_4,
- R.drawable.gallery_photo_5,
- R.drawable.gallery_photo_6,
- R.drawable.gallery_photo_7,
- R.drawable.gallery_photo_8
- };
}
-
}
diff --git a/samples/Spinner/AndroidManifest.xml b/samples/Spinner/AndroidManifest.xml
new file mode 100644
index 0000000..f1accf8
--- /dev/null
+++ b/samples/Spinner/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ Declares the contents of this Android application. The "namespace"
+ attribute brings in the Android platform namespace, and the
+ "package" attribute provides a unique Android name for the application.
+ If you use this file as a template in your own application, you must change
+ the package name from "com.android.example" to one that you own or have
+ control over.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.example.spinner"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <!--
+ Sets the application's user-readable label
+ -->
+ <application android:label="@string/app_name">
+ <!--
+ Sets the activity's name and label
+ -->
+ <activity android:name=".SpinnerActivity"
+ android:label="@string/app_name">
+ <!--
+ This activity responds to MAIN and LAUNCHER intents
+ -->
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+ <!--
+ Requires a minimum platform version of Android-3 (SDK 1.5) to run
+ -->
+ <uses-sdk android:minSdkVersion="3"/>
+
+</manifest>
diff --git a/samples/Spinner/_index.html b/samples/Spinner/_index.html
new file mode 100644
index 0000000..2e50d9d
--- /dev/null
+++ b/samples/Spinner/_index.html
@@ -0,0 +1,22 @@
+<p>
+This sample is the application under test for the
+<a href="../../../resources/tutorials/testing/activity_test.html">Activity
+ Testing Tutorial</a>. It contains a single Activity that displays a
+Spinner widget backed by an array of strings containing the names of the planets
+in the Solar System. When an entry is selected from the Spinner, the entry is
+displayed in a text box.
+</p>
+<p>
+An important part of this application is state management. When the application
+is first run, the spinner widget displays a default selection of
+"Earth". Thereafter, the application saves a selection as soon as it
+is made. The application remembers the selection from invocation to invocation, even
+if the device reboots.
+</p>
+<p>
+For more information about this application, see the Activity Testing Tutorial.
+The test application for this application is in the <a
+ href="../SpinnerTest/index.html">SpinnerTest</a> sample application.
+</p>
+
+<img alt="The Spinner application" src="../images/testing/spinner_main_screen.png" />
diff --git a/samples/Spinner/res/layout/main.xml b/samples/Spinner/res/layout/main.xml
new file mode 100644
index 0000000..3765b3a
--- /dev/null
+++ b/samples/Spinner/res/layout/main.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ Creates a Linear Layout View to contain the spinner
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+
+ <!--
+ Creates a spinner widget called Spinner01, within the Linear Layout
+ The prompt text comes from the string "planet_prompt" in strings.xml
+ -->
+ <Spinner
+ android:id="@+id/Spinner01"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:drawSelectorOnTop = "true"
+ android:prompt = "@string/planet_prompt">
+ </Spinner>
+
+ <!--
+ Creates a TextView called SpinnerResult, below the spinner.
+ -->
+ <TextView
+ android:id="@+id/SpinnerResult" android:text="Result"
+ android:layout_height="fill_parent" android:textSize="10pt"
+ android:textStyle="bold" android:gravity="center"
+ android:layout_width="fill_parent">
+ </TextView>
+
+</LinearLayout>
diff --git a/samples/Spinner/res/values/strings.xml b/samples/Spinner/res/values/strings.xml
new file mode 100644
index 0000000..ae445f3
--- /dev/null
+++ b/samples/Spinner/res/values/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ The string named "app_name" defines the application's visible name.
+ The string array "Planets" defines an array of 9 strings. The application loads
+ this array into the spinner's array adapter.
+ The string "planet_prompt" defines the prompt for the result text box.
+ -->
+<resources>
+ <string name="app_name">Spinner</string>
+ <string-array name="Planets">
+ <item>Mercury</item>
+ <item>Venus</item>
+ <item>Earth</item>
+ <item>Mars</item>
+ <item>Jupiter</item>
+ <item>Saturn</item>
+ <item>Uranus</item>
+ <item>Neptune</item>
+ <item>Pluto</item>
+ </string-array>
+ <string name="planet_prompt">Select a planet</string>
+</resources>
\ No newline at end of file
diff --git a/samples/Spinner/src/com/android/example/spinner/SpinnerActivity.java b/samples/Spinner/src/com/android/example/spinner/SpinnerActivity.java
new file mode 100644
index 0000000..fa4967c
--- /dev/null
+++ b/samples/Spinner/src/com/android/example/spinner/SpinnerActivity.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.example.spinner;
+
+import com.android.example.spinner.R;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.AdapterView.OnItemSelectedListener;
+
+/**
+ * Displays an Android spinner widget backed by data in an array. The
+ * array is loaded from the strings.xml resources file.
+ */
+public class SpinnerActivity extends Activity {
+
+ /**
+ * Fields to contain the current position and display contents of the spinner
+ */
+ protected int mPos;
+ protected String mSelection;
+
+ /**
+ * ArrayAdapter connects the spinner widget to array-based data.
+ */
+ protected ArrayAdapter<CharSequence> mAdapter;
+
+ /**
+ * The initial position of the spinner when it is first installed.
+ */
+ public static final int DEFAULT_POSITION = 2;
+
+ /**
+ * The name of a properties file that stores the position and
+ * selection when the activity is not loaded.
+ */
+ public static final String PREFERENCES_FILE = "SpinnerPrefs";
+
+ /**
+ * These values are used to read and write the properties file.
+ * PROPERTY_DELIMITER delimits the key and value in a Java properties file.
+ * The "marker" strings are used to write the properties into the file
+ */
+ public static final String PROPERTY_DELIMITER = "=";
+
+ /**
+ * The key or label for "position" in the preferences file
+ */
+ public static final String POSITION_KEY = "Position";
+
+ /**
+ * The key or label for "selection" in the preferences file
+ */
+ public static final String SELECTION_KEY = "Selection";
+
+ public static final String POSITION_MARKER =
+ POSITION_KEY + PROPERTY_DELIMITER;
+
+ public static final String SELECTION_MARKER =
+ SELECTION_KEY + PROPERTY_DELIMITER;
+
+ /**
+ * Initializes the application and the activity.
+ * 1) Sets the view
+ * 2) Reads the spinner's backing data from the string resources file
+ * 3) Instantiates a callback listener for handling selection from the
+ * spinner
+ * Notice that this method includes code that can be uncommented to force
+ * tests to fail.
+ *
+ * This method overrides the default onCreate() method for an Activity.
+ *
+ * @see android.app.Activity#onCreate(android.os.Bundle)
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+
+ /**
+ * derived classes that use onCreate() overrides must always call the super constructor
+ */
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.main);
+
+ Spinner spinner = (Spinner) findViewById(R.id.Spinner01);
+
+ /*
+ * Create a backing mLocalAdapter for the Spinner from a list of the
+ * planets. The list is defined by XML in the strings.xml file.
+ */
+
+ this.mAdapter = ArrayAdapter.createFromResource(this, R.array.Planets,
+ android.R.layout.simple_spinner_dropdown_item);
+
+ /*
+ * Attach the mLocalAdapter to the spinner.
+ */
+
+ spinner.setAdapter(this.mAdapter);
+
+ /*
+ * Create a listener that is triggered when Android detects the
+ * user has selected an item in the Spinner.
+ */
+
+ OnItemSelectedListener spinnerListener = new myOnItemSelectedListener(this,this.mAdapter);
+
+ /*
+ * Attach the listener to the Spinner.
+ */
+
+ spinner.setOnItemSelectedListener(spinnerListener);
+
+
+ /*
+ * To demonstrate a failure in the preConditions test,
+ * uncomment the following line.
+ * The test will fail because the selection listener for the
+ * Spinner is not set.
+ */
+ // spinner.setOnItemSelectedListener(null);
+
+ }
+
+
+ /**
+ * A callback listener that implements the
+ * {@link android.widget.AdapterView.OnItemSelectedListener} interface
+ * For views based on adapters, this interface defines the methods available
+ * when the user selects an item from the View.
+ *
+ */
+ public class myOnItemSelectedListener implements OnItemSelectedListener {
+
+ /*
+ * provide local instances of the mLocalAdapter and the mLocalContext
+ */
+
+ ArrayAdapter<CharSequence> mLocalAdapter;
+ Activity mLocalContext;
+
+ /**
+ * Constructor
+ * @param c - The activity that displays the Spinner.
+ * @param ad - The Adapter view that
+ * controls the Spinner.
+ * Instantiate a new listener object.
+ */
+ public myOnItemSelectedListener(Activity c, ArrayAdapter<CharSequence> ad) {
+
+ this.mLocalContext = c;
+ this.mLocalAdapter = ad;
+
+ }
+
+ /**
+ * When the user selects an item in the spinner, this method is invoked by the callback
+ * chain. Android calls the item selected listener for the spinner, which invokes the
+ * onItemSelected method.
+ *
+ * @see android.widget.AdapterView.OnItemSelectedListener#onItemSelected(
+ * android.widget.AdapterView, android.view.View, int, long)
+ * @param parent - the AdapterView for this listener
+ * @param v - the View for this listener
+ * @param pos - the 0-based position of the selection in the mLocalAdapter
+ * @param row - the 0-based row number of the selection in the View
+ */
+ public void onItemSelected(AdapterView<?> parent, View v, int pos, long row) {
+
+ SpinnerActivity.this.mPos = pos;
+ SpinnerActivity.this.mSelection = parent.getItemAtPosition(pos).toString();
+ /*
+ * Set the value of the text field in the UI
+ */
+ TextView resultText = (TextView)findViewById(R.id.SpinnerResult);
+ resultText.setText(SpinnerActivity.this.mSelection);
+ }
+
+ /**
+ * The definition of OnItemSelectedListener requires an override
+ * of onNothingSelected(), even though this implementation does not use it.
+ * @param parent - The View for this Listener
+ */
+ public void onNothingSelected(AdapterView<?> parent) {
+
+ // do nothing
+
+ }
+ }
+
+ /**
+ * Restores the current state of the spinner (which item is selected, and the value
+ * of that item).
+ * Since onResume() is always called when an Activity is starting, even if it is re-displaying
+ * after being hidden, it is the best place to restore state.
+ *
+ * Attempts to read the state from a preferences file. If this read fails,
+ * assume it was just installed, so do an initialization. Regardless, change the
+ * state of the spinner to be the previous position.
+ *
+ * @see android.app.Activity#onResume()
+ */
+ @Override
+ public void onResume() {
+
+ /*
+ * an override to onResume() must call the super constructor first.
+ */
+
+ super.onResume();
+
+ /*
+ * Try to read the preferences file. If not found, set the state to the desired initial
+ * values.
+ */
+
+ if (!readInstanceState(this)) setInitialState();
+
+ /*
+ * Set the spinner to the current state.
+ */
+
+ Spinner restoreSpinner = (Spinner)findViewById(R.id.Spinner01);
+ restoreSpinner.setSelection(getSpinnerPosition());
+
+ }
+
+ /**
+ * Store the current state of the spinner (which item is selected, and the value of that item).
+ * Since onPause() is always called when an Activity is about to be hidden, even if it is about
+ * to be destroyed, it is the best place to save state.
+ *
+ * Attempt to write the state to the preferences file. If this fails, notify the user.
+ *
+ * @see android.app.Activity#onPause()
+ */
+ @Override
+ public void onPause() {
+
+ /*
+ * an override to onPause() must call the super constructor first.
+ */
+
+ super.onPause();
+
+ /*
+ * Save the state to the preferences file. If it fails, display a Toast, noting the failure.
+ */
+
+ if (!writeInstanceState(this)) {
+ Toast.makeText(this,
+ "Failed to write state!", Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Sets the initial state of the spinner when the application is first run.
+ */
+ public void setInitialState() {
+
+ this.mPos = DEFAULT_POSITION;
+
+ }
+
+ /**
+ * Read the previous state of the spinner from the preferences file
+ * @param c - The Activity's Context
+ */
+ public boolean readInstanceState(Context c) {
+
+ /*
+ * The preferences are stored in a SharedPreferences file. The abstract implementation of
+ * SharedPreferences is a "file" containing a hashmap. All instances of an application
+ * share the same instance of this file, which means that all instances of an application
+ * share the same preference settings.
+ */
+
+ /*
+ * Get the SharedPreferences object for this application
+ */
+
+ SharedPreferences p = c.getSharedPreferences(PREFERENCES_FILE, MODE_WORLD_READABLE);
+ /*
+ * Get the position and value of the spinner from the file, or a default value if the
+ * key-value pair does not exist.
+ */
+ this.mPos = p.getInt(POSITION_KEY, SpinnerActivity.DEFAULT_POSITION);
+ this.mSelection = p.getString(SELECTION_KEY, "");
+
+ /*
+ * SharedPreferences doesn't fail if the code tries to get a non-existent key. The
+ * most straightforward way to indicate success is to return the results of a test that
+ * SharedPreferences contained the position key.
+ */
+
+ return (p.contains(POSITION_KEY));
+
+ }
+
+ /**
+ * Write the application's current state to a properties repository.
+ * @param c - The Activity's Context
+ *
+ */
+ public boolean writeInstanceState(Context c) {
+
+ /*
+ * Get the SharedPreferences object for this application
+ */
+
+ SharedPreferences p =
+ c.getSharedPreferences(SpinnerActivity.PREFERENCES_FILE, MODE_WORLD_READABLE);
+
+ /*
+ * Get the editor for this object. The editor interface abstracts the implementation of
+ * updating the SharedPreferences object.
+ */
+
+ SharedPreferences.Editor e = p.edit();
+
+ /*
+ * Write the keys and values to the Editor
+ */
+
+ e.putInt(POSITION_KEY, this.mPos);
+ e.putString(SELECTION_KEY, this.mSelection);
+
+ /*
+ * Commit the changes. Return the result of the commit. The commit fails if Android
+ * failed to commit the changes to persistent storage.
+ */
+
+ return (e.commit());
+
+ }
+
+ public int getSpinnerPosition() {
+ return this.mPos;
+ }
+
+ public void setSpinnerPosition(int pos) {
+ this.mPos = pos;
+ }
+
+ public String getSpinnerSelection() {
+ return this.mSelection;
+ }
+
+ public void setSpinnerSelection(String selection) {
+ this.mSelection = selection;
+ }
+}
diff --git a/samples/SpinnerTest/AndroidManifest.xml b/samples/SpinnerTest/AndroidManifest.xml
new file mode 100644
index 0000000..e151d6e
--- /dev/null
+++ b/samples/SpinnerTest/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ Declare the contents of this Android application. The "namespace"
+ attribute brings in the Android platform namespace, and the
+ "package" attribute provides a unique Android name for the application.
+ If you use this file as a template in your own application, you must change
+ the package name from "com.android.example" to one that you own or have
+ control over.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.example.spinner.test"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:label="@string/app_name">
+ <!--
+ this package needs to link against the android.test library,
+ which is needed when building test cases.
+ -->
+ <uses-library android:name="android.test.runner"/>
+ </application>
+ <!--
+ This declares that this application uses the instrumentation test runner targeting
+ the package of com.android.example.spinner. To run the tests use the command:
+ "adb shell am instrument -w \
+ com.android.example.spinner.test/android.test.InstrumentationTestRunner"
+ -->
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.example.spinner"
+ android:label="Tests for com.android.example.spinner"/>
+</manifest>
diff --git a/samples/SpinnerTest/_index.html b/samples/SpinnerTest/_index.html
new file mode 100644
index 0000000..7a176fd
--- /dev/null
+++ b/samples/SpinnerTest/_index.html
@@ -0,0 +1,9 @@
+<p>
+This sample is the test application for the
+<a href="../../../resources/tutorials/testing/activity_test.html">Activity
+ Testing Tutorial</a>. Refer to the tutorial text for a full explanation
+of this sample code.
+</p>
+<p>
+ This application does not have an Android GUI.
+</p>
diff --git a/samples/SpinnerTest/res/values/strings.xml b/samples/SpinnerTest/res/values/strings.xml
new file mode 100644
index 0000000..9721572
--- /dev/null
+++ b/samples/SpinnerTest/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ This defines the test application's name
+ -->
+<resources>
+ <string name="app_name">SpinnerTest</string>
+</resources>
diff --git a/samples/SpinnerTest/src/com/android/example/spinner/test/SpinnerActivityTest.java b/samples/SpinnerTest/src/com/android/example/spinner/test/SpinnerActivityTest.java
new file mode 100644
index 0000000..4c23317
--- /dev/null
+++ b/samples/SpinnerTest/src/com/android/example/spinner/test/SpinnerActivityTest.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.example.spinner.test;
+
+import com.android.example.spinner.SpinnerActivity;
+
+import android.app.Instrumentation;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.view.KeyEvent;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+
+/**
+ * Tests the example application Spinner. Uses the instrumentation test class
+ * {@link ActivityInstrumentationTestCase2} as its base class. The tests include
+ * <ol>
+ * <li>test initial conditions</li>
+ * <li>test the UI</li>
+ * <li>state management - preserving state after the app is shut down and restarted, preserving
+ * state after the app is hidden (paused) and re-displayed (resumed)</li>
+ * </ol>
+ *
+ * Demonstrates the use of JUnit setUp() and assert() methods.
+ */
+public class SpinnerActivityTest extends ActivityInstrumentationTestCase2<SpinnerActivity> {
+
+ // Number of items in the spinner's backing mLocalAdapter
+
+ public static final int ADAPTER_COUNT = 9;
+
+ // The location of Saturn in the backing mLocalAdapter array (0-based)
+
+ public static final int TEST_POSITION = 5;
+
+ // Set the initial position of the spinner to zero
+
+ public static final int INITIAL_POSITION = 0;
+
+ // The initial position corresponds to Mercury
+
+ public static final String INITIAL_SELECTION = "Mercury";
+
+ // Test values of position and selection for the testStateDestroy test
+
+ public static final int TEST_STATE_DESTROY_POSITION = 2;
+ public static final String TEST_STATE_DESTROY_SELECTION = "Earth";
+
+ // Test values of position and selection for the testStatePause test
+
+ public static final int TEST_STATE_PAUSE_POSITION = 4;
+ public static final String TEST_STATE_PAUSE_SELECTION = "Jupiter";
+
+ // The Application object for the application under test
+
+ private SpinnerActivity mActivity;
+
+ // String displayed in the spinner in the app under test
+
+ private String mSelection;
+
+ // The currently selected position in the spinner in the app under test
+
+ private int mPos;
+
+ /*
+ * The Spinner object in the app under test. Used with instrumentation to control the
+ * app under test.
+ */
+
+ private Spinner mSpinner;
+
+ /*
+ * The data backing the Spinner in the app under test.
+ */
+
+ private SpinnerAdapter mPlanetData;
+
+ /**
+ * Constructor for the test class. Required by Android test classes. The constructor
+ * must call the super constructor, providing the Android package name of the app under test
+ * and the Java class name of the activity in that application that handles the MAIN intent.
+ */
+ public SpinnerActivityTest() {
+
+ super("com.android.example.spinner", SpinnerActivity.class);
+ }
+
+ /**
+ * Sets up the test environment before each test.
+ * @see android.test.ActivityInstrumentationTestCase2#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+
+ /*
+ * Call the super constructor (required by JUnit)
+ */
+
+ super.setUp();
+
+ /*
+ * prepare to send key events to the app under test by turning off touch mode.
+ * Must be done before the first call to getActivity()
+ */
+
+ setActivityInitialTouchMode(false);
+
+ /*
+ * Start the app under test by starting its main activity. The test runner already knows
+ * which activity this is from the call to the super constructor, as mentioned
+ * previously. The tests can now use instrumentation to directly access the main
+ * activity through mActivity.
+ */
+ mActivity = getActivity();
+
+ /*
+ * Get references to objects in the application under test. These are
+ * tested to ensure that the app under test has initialized correctly.
+ */
+
+ mSpinner = (Spinner)mActivity.findViewById(com.android.example.spinner.R.id.Spinner01);
+
+ mPlanetData = mSpinner.getAdapter();
+
+ }
+
+ /**
+ * Tests the initial values of key objects in the app under test, to ensure the initial
+ * conditions make sense. If one of these is not initialized correctly, then subsequent
+ * tests are suspect and should be ignored.
+ */
+
+ public void testPreconditions() {
+
+ /*
+ * An example of an initialization test. Assert that the item select listener in
+ * the main Activity is not null (has been set to a valid callback)
+ */
+ assertTrue(mSpinner.getOnItemSelectedListener() != null);
+
+ /*
+ * Test that the spinner's backing mLocalAdapter was initialized correctly.
+ */
+
+ assertTrue(mPlanetData != null);
+
+ /*
+ * Also ensure that the backing mLocalAdapter has the correct number of entries.
+ */
+
+ assertEquals(mPlanetData.getCount(), ADAPTER_COUNT);
+ }
+
+ /*
+ * Tests the UI of the main activity. Sends key events (keystrokes) to the UI, then checks
+ * if the resulting spinner state is consistent with the attempted selection.
+ */
+ public void testSpinnerUI() {
+
+ /*
+ * Request focus for the spinner widget in the application under test,
+ * and set its initial position. This code interacts with the app's View
+ * so it has to run on the app's thread not the test's thread.
+ *
+ * To do this, pass the necessary code to the application with
+ * runOnUiThread(). The parameter is an anonymous Runnable object that
+ * contains the Java statements put in it by its run() method.
+ */
+ mActivity.runOnUiThread(
+ new Runnable() {
+ public void run() {
+ mSpinner.requestFocus();
+ mSpinner.setSelection(INITIAL_POSITION);
+ }
+ }
+ );
+
+ // Activate the spinner by clicking the center keypad key
+
+ this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+
+ // send 5 down arrow keys to the spinner
+
+ for (int i = 1; i <= TEST_POSITION; i++) {
+
+ this.sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+
+ // select the item at the current spinner position
+
+ this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+
+ // get the position of the selected item
+
+ mPos = mSpinner.getSelectedItemPosition();
+
+ /*
+ * from the spinner's data mLocalAdapter, get the object at the selected position
+ * (this is a String value)
+ */
+
+ mSelection = (String)mSpinner.getItemAtPosition(mPos);
+
+ /*
+ * Get the TextView widget that displays the result of selecting an item from the spinner
+ */
+
+ TextView resultView =
+ (TextView) mActivity.findViewById(com.android.example.spinner.R.id.SpinnerResult);
+
+ // Get the String value in the EditText object
+
+ String resultText = (String) resultView.getText();
+
+ /*
+ * Confirm that the EditText contains the same value as the data in the mLocalAdapter
+ */
+
+ assertEquals(resultText,mSelection);
+ }
+
+ /*
+ * Tests that the activity under test maintains the spinner state when the activity halts
+ * and then restarts (for example, if the device reboots). Sets the spinner to a
+ * certain state, calls finish() on the activity, restarts the activity, and then
+ * checks that the spinner has the same state.
+ *
+ */
+
+ public void testStateDestroy() {
+
+ /*
+ * Set the position and value of the spinner in the Activity. The test runner's
+ * instrumentation enables this by running the test app and the main app in the same
+ * process.
+ */
+
+
+ mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION);
+
+ mActivity.setSpinnerSelection(TEST_STATE_DESTROY_SELECTION);
+
+ // Halt the Activity by calling Activity.finish() on it
+
+ mActivity.finish();
+
+ // Restart the activity by calling ActivityInstrumentationTestCase2.getActivity()
+
+ mActivity = this.getActivity();
+
+ /*
+ * Get the current position and selection from the activity.
+ */
+
+ int currentPosition = mActivity.getSpinnerPosition();
+ String currentSelection = mActivity.getSpinnerSelection();
+
+ // test that they are the same.
+
+ assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
+
+ assertEquals(TEST_STATE_DESTROY_SELECTION, currentSelection);
+ }
+
+ /*
+ * Tests that the activity under test maintains the spinner's state when the activity is
+ * paused and then resumed.
+ *
+ * Calls the activity's onResume() method. Changes the spinner's state by
+ * altering the activity's View. This means the test must run
+ * on the UI Thread. All the statements in the test method may be run on
+ * that thread, so instead of using the runOnUiThread() method, the
+ * @UiThreadTest is used.
+ */
+ @UiThreadTest
+
+ public void testStatePause() {
+
+ /*
+ * Get the instrumentation object for this application. This object
+ * does all the instrumentation work for the test runner
+ */
+
+ Instrumentation instr = this.getInstrumentation();
+
+ /*
+ * Set the activity's fields for the position and value of the spinner
+ */
+
+ mActivity.setSpinnerPosition(TEST_STATE_PAUSE_POSITION);
+
+ mActivity.setSpinnerSelection(TEST_STATE_PAUSE_SELECTION);
+
+ /*
+ * Use the instrumentation to onPause() on the currently running Activity.
+ * This analogous to calling finish() in the testStateDestroy() method.
+ * This way demonstrates using the test class' instrumentation.
+ */
+
+ instr.callActivityOnPause(mActivity);
+
+ /*
+ * Set the spinner to a test position
+ */
+
+ mActivity.setSpinnerPosition(0);
+
+ mActivity.setSpinnerSelection("");
+
+ /*
+ * Call the activity's onResume() method. This forces the activity
+ * to restore its state.
+ */
+
+ instr.callActivityOnResume(mActivity);
+
+ /*
+ * Get the current state of the spinner
+ */
+
+ int currentPosition = mActivity.getSpinnerPosition();
+
+ String currentSelection = mActivity.getSpinnerSelection();
+
+ assertEquals(TEST_STATE_PAUSE_POSITION,currentPosition);
+ assertEquals(TEST_STATE_PAUSE_SELECTION,currentSelection);
+ }
+
+}
diff --git a/samples/XmlAdapters/Android.mk b/samples/XmlAdapters/Android.mk
new file mode 100644
index 0000000..24a3327
--- /dev/null
+++ b/samples/XmlAdapters/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := XmlAdaptersSample
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/XmlAdapters/AndroidManifest.xml b/samples/XmlAdapters/AndroidManifest.xml
new file mode 100644
index 0000000..af040ad
--- /dev/null
+++ b/samples/XmlAdapters/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.xmladapters">
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+ <application android:label="@string/app_name">
+ <activity android:name="ContactsListActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/samples/XmlAdapters/res/drawable-hdpi/ic_contact_picture.png b/samples/XmlAdapters/res/drawable-hdpi/ic_contact_picture.png
new file mode 100644
index 0000000..a60565a
--- /dev/null
+++ b/samples/XmlAdapters/res/drawable-hdpi/ic_contact_picture.png
Binary files differ
diff --git a/samples/XmlAdapters/res/layout/contact_item.xml b/samples/XmlAdapters/res/layout/contact_item.xml
new file mode 100644
index 0000000..6fcb109
--- /dev/null
+++ b/samples/XmlAdapters/res/layout/contact_item.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeight">
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="0px"
+ android:layout_weight="1.0"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:gravity="center_vertical"
+ android:drawablePadding="6dip"
+ android:paddingLeft="6dip"
+ android:paddingRight="6dip" />
+
+ <ImageView
+ android:id="@+id/star"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/samples/XmlAdapters/res/layout/contacts_list.xml b/samples/XmlAdapters/res/layout/contacts_list.xml
new file mode 100644
index 0000000..973fe4b
--- /dev/null
+++ b/samples/XmlAdapters/res/layout/contacts_list.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <ListView
+ android:id="@android:id/list"
+ android:adapter="@xml/contacts"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <TextView android:id="@android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/no_contacts"
+ android:visibility="gone" />
+
+</merge>
diff --git a/samples/XmlAdapters/res/values/strings.xml b/samples/XmlAdapters/res/values/strings.xml
new file mode 100644
index 0000000..3a0b5fe
--- /dev/null
+++ b/samples/XmlAdapters/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASI
+ 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">Xml Contacts Adapter</string>
+ <string name="no_contacts">No contacts available</string>
+</resources>
diff --git a/samples/XmlAdapters/res/xml/contacts.xml b/samples/XmlAdapters/res/xml/contacts.xml
new file mode 100644
index 0000000..b33d948
--- /dev/null
+++ b/samples/XmlAdapters/res/xml/contacts.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+ android:uri="content://com.android.contacts/contacts"
+ android:selection="has_phone_number=1"
+ android:layout="@layout/contact_item">
+
+ <bind android:from="display_name" android:to="@id/name" android:as="string" />
+ <bind android:from="starred" android:to="@id/star" android:as="drawable">
+ <map android:fromValue="0" android:toValue="@android:drawable/star_big_off" />
+ <map android:fromValue="1" android:toValue="@android:drawable/star_big_on" />
+ </bind>
+ <bind android:from="_id" android:to="@id/name"
+ android:as="com.example.android.xmladapters.ContactPhotoBinder" />
+
+</cursor-adapter>
diff --git a/samples/XmlAdapters/src/com/example/android/xmladapters/ContactPhotoBinder.java b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactPhotoBinder.java
new file mode 100644
index 0000000..947eb2a
--- /dev/null
+++ b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactPhotoBinder.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.xmladapters;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.view.View;
+import android.widget.Adapters;
+import android.widget.TextView;
+
+import java.io.InputStream;
+import java.util.HashMap;
+
+/**
+ * This custom cursor binder is used by the adapter defined in res/xml to
+ * bin contacts photos to their respective list item. This binder simply
+ * queries a contact's photo based on the contact's id and sets the
+ * photo as a compound drawable on the TextView used to display the contact's
+ * name.
+ */
+public class ContactPhotoBinder extends Adapters.CursorBinder {
+ private static final int PHOTO_SIZE_DIP = 54;
+
+ private final Drawable mDefault;
+ private final HashMap<Long, Drawable> mCache;
+ private final Resources mResources;
+ private final int mPhotoSize;
+
+ public ContactPhotoBinder(Context context, Adapters.CursorTransformation transformation) {
+ super(context, transformation);
+
+ mResources = mContext.getResources();
+ // Default picture used when a contact does not provide one
+ mDefault = mResources.getDrawable(R.drawable.ic_contact_picture);
+ // Cache used to avoid requerying contacts photos every time
+ mCache = new HashMap<Long, Drawable>();
+ // Compute the size of the photo based on the display's density
+ mPhotoSize = (int) (PHOTO_SIZE_DIP * mResources.getDisplayMetrics().density + 0.5f);
+ }
+
+ @Override
+ public boolean bind(View view, Cursor cursor, int columnIndex) {
+ final long id = cursor.getLong(columnIndex);
+
+ // First check whether we have already cached the contact's photo
+ Drawable d = mCache.get(id);
+
+ if (d == null) {
+ // If the photo wasn't in the cache, ask the contacts provider for
+ // an input stream we can use to load the photo
+ Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
+ InputStream stream = ContactsContract.Contacts.openContactPhotoInputStream(
+ mContext.getContentResolver(), uri);
+
+ // Creates the drawable for the contact's photo or use our fallback drawable
+ if (stream != null) {
+ Bitmap bitmap = BitmapFactory.decodeStream(stream);
+ d = new BitmapDrawable(mResources, bitmap);
+ } else {
+ d = mDefault;
+ }
+
+ d.setBounds(0, 0, mPhotoSize, mPhotoSize);
+ ((TextView) view).setCompoundDrawables(d, null, null, null);
+
+ // Remember the photo associated with this contact
+ mCache.put(id, d);
+ } else {
+ ((TextView) view).setCompoundDrawables(d, null, null, null);
+ }
+
+ return true;
+ }
+}
diff --git a/samples/XmlAdapters/src/com/example/android/xmladapters/ContactsListActivity.java b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactsListActivity.java
new file mode 100644
index 0000000..bf5ab58
--- /dev/null
+++ b/samples/XmlAdapters/src/com/example/android/xmladapters/ContactsListActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.xmladapters;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+
+/**
+ * This activity demonstrates how to create a complex UI using a ListView
+ * and an adapter defined in XML.
+ *
+ * The following activity shows a list of contacts, their starred status
+ * and their photos, using the adapter defined in res/xml.
+ */
+public class ContactsListActivity extends ListActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.contacts_list);
+ }
+}
diff --git a/simulator/app/MessageStream.cpp b/simulator/app/MessageStream.cpp
index 2397c63..c52e7c4 100644
--- a/simulator/app/MessageStream.cpp
+++ b/simulator/app/MessageStream.cpp
@@ -8,6 +8,7 @@
#include "utils/Log.h"
+#include <stdint.h>
#include <string.h>
#include <assert.h>
@@ -338,7 +339,7 @@
* and capability flags.
*/
if (initiateHello) {
- long data = kHelloMsg;
+ int32_t data = kHelloMsg;
Message msg;
/* send hello */
@@ -357,14 +358,15 @@
return false;
}
- const long* pAck;
- pAck = (const long*) msg.getData();
+ const int32_t* pAck;
+ pAck = (const int32_t*) msg.getData();
if (pAck == NULL || *pAck != kHelloAckMsg) {
- LOG(LOG_WARN, "", "hello ack was bad\n");
+ LOG(LOG_WARN, "", "hello ack was bad (%08x vs %08x)\n",
+ *pAck, kHelloAckMsg);
return false;
}
} else {
- long data = kHelloAckMsg;
+ int32_t data = kHelloAckMsg;
Message msg;
LOG(LOG_DEBUG, "", "waiting for hello from peer\n");
@@ -375,8 +377,8 @@
return false;
}
- const long* pAck;
- pAck = (const long*) msg.getData();
+ const int32_t* pAck;
+ pAck = (const int32_t*) msg.getData();
if (pAck == NULL || *pAck != kHelloMsg) {
LOG(LOG_WARN, "", "hello was bad\n");
return false;
diff --git a/testrunner/adb_interface.py b/testrunner/adb_interface.py
index 698ea8b..ea9188c 100755
--- a/testrunner/adb_interface.py
+++ b/testrunner/adb_interface.py
@@ -355,13 +355,44 @@
"in test_defs.xml?" % instrumentation_path)
raise errors.WaitForResponseTimedOutError()
- def Sync(self, retry_count=3):
+ def WaitForBootComplete(self, wait_time=120):
+ """Waits for targeted device's bootcomplete flag to be set.
+
+ Args:
+ wait_time: time in seconds to wait
+
+ Raises:
+ WaitForResponseTimedOutError if wait_time elapses and pm still does not
+ respond.
+ """
+ logger.Log("Waiting for boot complete...")
+ self.SendCommand("wait-for-device")
+ # Now the device is there, but may not be running.
+ # Query the package manager with a basic command
+ boot_complete = False
+ attempts = 0
+ wait_period = 5
+ while not boot_complete and (attempts*wait_period) < wait_time:
+ output = self.SendShellCommand("getprop dev.bootcomplete", retry_count=1)
+ output = output.strip()
+ if output == "1":
+ boot_complete = True
+ else:
+ time.sleep(wait_period)
+ attempts += 1
+ if not boot_complete:
+ raise errors.WaitForResponseTimedOutError(
+ "dev.bootcomplete flag was not set after %s seconds" % wait_time)
+
+ def Sync(self, retry_count=3, runtime_restart=False):
"""Perform a adb sync.
Blocks until device package manager is responding.
Args:
retry_count: number of times to retry sync before failing
+ runtime_restart: stop runtime during sync and restart afterwards, useful
+ for syncing system libraries (core, framework etc)
Raises:
WaitForResponseTimedOutError if package manager does not respond
@@ -369,6 +400,13 @@
"""
output = ""
error = None
+ if runtime_restart:
+ self.SendShellCommand("setprop ro.monkey 1", retry_count=retry_count)
+ # manual rest bootcomplete flag
+ self.SendShellCommand("setprop dev.bootcomplete 0",
+ retry_count=retry_count)
+ self.SendShellCommand("stop", retry_count=retry_count)
+
try:
output = self.SendCommand("sync", retry_count=retry_count)
except errors.AbortError, e:
@@ -389,10 +427,17 @@
# exception occurred that cannot be recovered from
raise error
logger.SilentLog(output)
- self.WaitForDevicePm()
+ if runtime_restart:
+ # start runtime and wait till boot complete flag is set
+ self.SendShellCommand("start", retry_count=retry_count)
+ self.WaitForBootComplete()
+ # press the MENU key, this will disable key guard if runtime is started
+ # with ro.monkey set to 1
+ self.SendShellCommand("input keyevent 82", retry_count=retry_count)
+ else:
+ self.WaitForDevicePm()
return output
def GetSerialNumber(self):
"""Returns the serial number of the targeted device."""
return self.SendCommand("get-serialno").strip()
-
diff --git a/testrunner/coverage.py b/testrunner/coverage.py
index 52e8a8c..4322e26 100755
--- a/testrunner/coverage.py
+++ b/testrunner/coverage.py
@@ -62,28 +62,6 @@
self._adb = adb_interface
self._targets_manifest = self._ReadTargets()
- 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.
- """
- try:
- output = self._adb.SendShellCommand("cat init.rc | grep BOOTCLASSPATH | "
- "grep emma.jar")
- if len(output) > 0:
- return True
- except errors.AbortError:
- pass
- 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,
device_coverage_path,
output_path=None,
@@ -311,6 +289,25 @@
os.environ["EMMA_INSTRUMENT"] = "true"
+def TestDeviceCoverageSupport(adb):
+ """Check if device has support for generating code coverage metrics.
+
+ This tries to dump emma help information on device, a response containing
+ help information will indicate that emma is already on system class path.
+
+ Returns:
+ True if device can support code coverage. False otherwise.
+ """
+ try:
+ output = adb.SendShellCommand("exec app_process / emma -h")
+
+ if output.find('emma usage:') == 0:
+ return True
+ except errors.AbortError:
+ pass
+ return False
+
+
def Run():
"""Does coverage operations based on command line args."""
# TODO: do we want to support combining coverage for a single target
diff --git a/testrunner/runtest.py b/testrunner/runtest.py
index d53312f..6b03c79 100755
--- a/testrunner/runtest.py
+++ b/testrunner/runtest.py
@@ -157,7 +157,6 @@
group.add_option("-s", "--serial", dest="serial",
help="use specific serial")
parser.add_option_group(group)
-
self._options, self._test_args = parser.parse_args()
if (not self._options.only_list_tests
@@ -222,31 +221,35 @@
def _DoBuild(self):
logger.SilentLog("Building tests...")
+
+ tests = self._GetTestsToRun()
+ self._DoFullBuild(tests)
+
target_set = Set()
extra_args_set = Set()
- tests = self._GetTestsToRun()
for test_suite in tests:
self._AddBuildTarget(test_suite, target_set, extra_args_set)
+ rebuild_libcore = False
if target_set:
if self._options.coverage:
coverage.EnableCoverageBuild()
-
- # hack to build cts dependencies
- # TODO: remove this when build dependency support added to runtest or
- # cts dependencies are removed
- if self._IsCtsTests(tests):
- # need to use make since these fail building with ONE_SHOT_MAKEFILE
- cmd = ('make -j%s CtsTestStubs android.core.tests.runner' %
- self._options.make_jobs)
- logger.Log(cmd)
- if not self._options.preview:
- old_dir = os.getcwd()
- os.chdir(self._root_path)
+ # hack to remove core library intermediates
+ # hack is needed because:
+ # 1. EMMA_INSTRUMENT changes what source files to include in libcore
+ # but it does not trigger a rebuild
+ # 2. there's no target (like "clear-intermediates") to remove the files
+ # decently
+ rebuild_libcore = not coverage.TestDeviceCoverageSupport(self._adb)
+ if rebuild_libcore:
+ cmd = "rm -rf %s" % os.path.join(
+ self._root_path,
+ "out/target/common/obj/JAVA_LIBRARIES/core_intermediates/")
+ logger.Log(cmd)
run_command.RunCommand(cmd, return_output=False)
- os.chdir(old_dir)
- target_build_string = " ".join(list(target_set))
- extra_args_string = " ".join(list(extra_args_set))
+
+ target_build_string = ' '.join(list(target_set))
+ extra_args_string = ' '.join(list(extra_args_set))
# mmm cannot be used from python, so perform a similar operation using
# ONE_SHOT_MAKEFILE
cmd = 'ONE_SHOT_MAKEFILE="%s" make -j%s -C "%s" files %s' % (
@@ -259,16 +262,49 @@
# run
logger.Log("adb sync")
else:
- run_command.RunCommand(cmd, return_output=False)
+ # set timeout for build to 10 minutes, since libcore may need to
+ # be rebuilt
+ run_command.RunCommand(cmd, return_output=False, timeout_time=600)
logger.Log("Syncing to device...")
- self._adb.Sync()
+ self._adb.Sync(runtime_restart=rebuild_libcore)
+
+ def _DoFullBuild(self, tests):
+ """If necessary, run a full 'make' command for the tests that need it."""
+ extra_args_set = Set()
+
+ # hack to build cts dependencies
+ # TODO: remove this when cts dependencies are removed
+ if self._IsCtsTests(tests):
+ # need to use make since these fail building with ONE_SHOT_MAKEFILE
+ extra_args_set.add('CtsTestStubs')
+ extra_args_set.add('android.core.tests.runner')
+ for test in tests:
+ if test.IsFullMake():
+ if test.GetExtraBuildArgs():
+ # extra args contains the args to pass to 'make'
+ extra_args_set.add(test.GetExtraBuildArgs())
+ else:
+ logger.Log("Warning: test %s needs a full build but does not specify"
+ " extra_build_args" % test.GetName())
+
+ # check if there is actually any tests that required a full build
+ if extra_args_set:
+ cmd = ('make -j%s %s' % (self._options.make_jobs,
+ ' '.join(list(extra_args_set))))
+ logger.Log(cmd)
+ if not self._options.preview:
+ old_dir = os.getcwd()
+ os.chdir(self._root_path)
+ run_command.RunCommand(cmd, return_output=False)
+ os.chdir(old_dir)
def _AddBuildTarget(self, test_suite, target_set, extra_args_set):
- build_dir = test_suite.GetBuildPath()
- if self._AddBuildTargetPath(build_dir, target_set):
- extra_args_set.add(test_suite.GetExtraBuildArgs())
- for path in test_suite.GetBuildDependencies(self._options):
- self._AddBuildTargetPath(path, target_set)
+ if not test_suite.IsFullMake():
+ build_dir = test_suite.GetBuildPath()
+ if self._AddBuildTargetPath(build_dir, target_set):
+ extra_args_set.add(test_suite.GetExtraBuildArgs())
+ for path in test_suite.GetBuildDependencies(self._options):
+ self._AddBuildTargetPath(path, target_set)
def _AddBuildTargetPath(self, build_dir, target_set):
if build_dir is not None:
diff --git a/testrunner/test_defs.xml b/testrunner/test_defs.xml
index d6ce4c5..9263b63 100644
--- a/testrunner/test_defs.xml
+++ b/testrunner/test_defs.xml
@@ -380,7 +380,8 @@
<test name="browser"
build_path="packages/apps/Browser"
package="com.android.browser.tests"
- coverage_target="Browser" />
+ coverage_target="Browser"
+ continuous="true" />
<test name="calendar"
build_path="packages/apps/Calendar"
@@ -479,6 +480,12 @@
description="Google test."
extra_build_args="GTEST_TESTS=1" />
+<!-- Libjingle -->
+<test-native name="libjingle"
+ build_path="vendor/google/libraries/libjingle"
+ description="Libjingle."
+ full_make="true"
+ extra_build_args="LIBJINGLE_TESTS=1" />
<!-- host java tests -->
<test-host name="cts-appsecurity"
diff --git a/testrunner/test_defs.xsd b/testrunner/test_defs.xsd
index 7134459..6781941 100644
--- a/testrunner/test_defs.xsd
+++ b/testrunner/test_defs.xsd
@@ -41,7 +41,7 @@
<xs:attribute name="name" type="xs:string" use="required" />
<!-- File system path, relative to Android build root, to this
- package's Android.mk file. -->
+ package's Android.mk file.-->
<xs:attribute name="build_path" type="xs:string" use="required" />
<!-- Include test in continuous test system. -->
@@ -55,10 +55,17 @@
test. -->
<xs:attribute name="description" type="xs:string" use="optional" />
+ <!-- Specifies that a full 'make', as opposed to 'mmm' command, is
+ needed to build this test. The build command used will be
+ 'make extra_build_args' -->
+ <xs:attribute name="full_make" type="xs:boolean"
+ use="optional" />
+
<!-- Extra arguments to append to build command when building this
test. -->
<xs:attribute name="extra_build_args" type="xs:string"
use="optional" />
+
</xs:complexType>
<!-- Java on device instrumentation test.
diff --git a/testrunner/test_defs/instrumentation_test.py b/testrunner/test_defs/instrumentation_test.py
index f0a8656..9d2f779 100644
--- a/testrunner/test_defs/instrumentation_test.py
+++ b/testrunner/test_defs/instrumentation_test.py
@@ -32,8 +32,8 @@
DEFAULT_RUNNER = "android.test.InstrumentationTestRunner"
- # build path to Emma target Makefile
- _EMMA_BUILD_PATH = os.path.join("external", "emma")
+ # dependency on libcore (used for Emma)
+ _LIBCORE_BUILD_PATH = os.path.join("dalvik", "libcore")
def __init__(self):
test_suite.AbstractTestSuite.__init__(self)
@@ -87,7 +87,7 @@
def GetBuildDependencies(self, options):
if options.coverage:
- return [self._EMMA_BUILD_PATH]
+ return [self._LIBCORE_BUILD_PATH]
return []
def Run(self, options, adb):
@@ -144,8 +144,6 @@
logger.Log(adb_cmd)
elif options.coverage:
coverage_gen = coverage.CoverageGenerator(adb)
- if not coverage_gen.TestDeviceCoverageSupport():
- raise errors.AbortError
adb.WaitForInstrumentation(self.GetPackageName(),
self.GetRunnerName())
# need to parse test output to determine path to coverage file
diff --git a/testrunner/test_defs/test_suite.py b/testrunner/test_defs/test_suite.py
index 90d5792..102a738 100644
--- a/testrunner/test_defs/test_suite.py
+++ b/testrunner/test_defs/test_suite.py
@@ -32,6 +32,7 @@
self._suite = None
self._description = ''
self._extra_build_args = ''
+ self._is_full_make = False
def GetName(self):
return self._name
@@ -88,6 +89,13 @@
self._extra_build_args = build_args
return self
+ def IsFullMake(self):
+ return self._is_full_make
+
+ def SetIsFullMake(self, full_make):
+ self._is_full_make = full_make
+ return self
+
def Run(self, options, adb):
"""Runs the test.
diff --git a/testrunner/test_defs/test_walker.py b/testrunner/test_defs/test_walker.py
index 06c4e6d..948d53e 100755
--- a/testrunner/test_defs/test_walker.py
+++ b/testrunner/test_defs/test_walker.py
@@ -65,20 +65,20 @@
if not os.path.exists(path):
logger.Log('%s does not exist' % path)
return []
- abspath = os.path.abspath(path)
+ realpath = os.path.realpath(path)
# ensure path is in ANDROID_BUILD_ROOT
- self._build_top = android_build.GetTop()
- if not self._IsPathInBuildTree(abspath):
+ self._build_top = os.path.realpath(android_build.GetTop())
+ if not self._IsPathInBuildTree(realpath):
logger.Log('%s is not a sub-directory of build root %s' %
(path, self._build_top))
return []
# first, assume path is a parent directory, which specifies to run all
# tests within this directory
- tests = self._FindSubTests(abspath, [])
+ tests = self._FindSubTests(realpath, [])
if not tests:
logger.SilentLog('No tests found within %s, searching upwards' % path)
- tests = self._FindUpstreamTests(abspath)
+ tests = self._FindUpstreamTests(realpath)
return tests
def _IsPathInBuildTree(self, path):
diff --git a/testrunner/test_defs/xml_suite_helper.py b/testrunner/test_defs/xml_suite_helper.py
index b7ed83b..6cf2e6c 100644
--- a/testrunner/test_defs/xml_suite_helper.py
+++ b/testrunner/test_defs/xml_suite_helper.py
@@ -39,6 +39,7 @@
_SUITE_ATTR = 'suite'
_DESCRIPTION_ATTR = 'description'
_EXTRA_BUILD_ARGS_ATTR = 'extra_build_args'
+ _FULL_MAKE_ATTR = 'full_make'
def Parse(self, element):
"""Populates common suite attributes from given suite xml element.
@@ -79,6 +80,9 @@
default_value=''))
test_suite.SetExtraBuildArgs(self._ParseAttribute(
suite_element, self._EXTRA_BUILD_ARGS_ATTR, False, default_value=''))
+ test_suite.SetIsFullMake(self._ParseAttribute(
+ suite_element, self._FULL_MAKE_ATTR, False, default_value=False))
+
def _ParseAttribute(self, suite_element, attribute_name, mandatory,
default_value=None):
diff --git a/tools/monkeyrunner/Android.mk b/tools/monkeyrunner/Android.mk
index d15c67e..21cf67a 100644
--- a/tools/monkeyrunner/Android.mk
+++ b/tools/monkeyrunner/Android.mk
@@ -16,3 +16,4 @@
MONKEYRUNNER_LOCAL_DIR := $(call my-dir)
include $(MONKEYRUNNER_LOCAL_DIR)/etc/Android.mk
include $(MONKEYRUNNER_LOCAL_DIR)/src/Android.mk
+include $(MONKEYRUNNER_LOCAL_DIR)/test/Android.mk
diff --git a/tools/monkeyrunner/etc/manifest.txt b/tools/monkeyrunner/etc/manifest.txt
index 288be5f..706842e 100644
--- a/tools/monkeyrunner/etc/manifest.txt
+++ b/tools/monkeyrunner/etc/manifest.txt
@@ -1 +1 @@
-Main-Class: com.android.monkeyrunner.MonkeyRunner
+Main-Class: com.android.monkeyrunner.MonkeyRunnerStarter
diff --git a/tools/monkeyrunner/src/Android.mk b/tools/monkeyrunner/src/Android.mk
index fb6b9c1..614acca 100644
--- a/tools/monkeyrunner/src/Android.mk
+++ b/tools/monkeyrunner/src/Android.mk
@@ -22,7 +22,8 @@
LOCAL_JAVA_LIBRARIES := \
ddmlib \
jython \
- xmlwriter
+ xmlwriter \
+ guavalib
LOCAL_MODULE := monkeyrunner
@@ -48,4 +49,3 @@
LOCAL_MODULE := xmlwriter
include $(BUILD_HOST_JAVA_LIBRARY)
-
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/CaptureRawAndConvertedImage.java b/tools/monkeyrunner/src/com/android/monkeyrunner/CaptureRawAndConvertedImage.java
new file mode 100644
index 0000000..31d8981
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/CaptureRawAndConvertedImage.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.RawImage;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Utility program to capture raw and converted images from a device and write them to a file.
+ * This is used to generate the test data for ImageUtilsTest.
+ */
+public class CaptureRawAndConvertedImage {
+ public static class MonkeyRunnerRawImage implements Serializable {
+ public int version;
+ public int bpp;
+ public int size;
+ public int width;
+ public int height;
+ public int red_offset;
+ public int red_length;
+ public int blue_offset;
+ public int blue_length;
+ public int green_offset;
+ public int green_length;
+ public int alpha_offset;
+ public int alpha_length;
+
+ public byte[] data;
+
+ public MonkeyRunnerRawImage(RawImage rawImage) {
+ version = rawImage.version;
+ bpp = rawImage.bpp;
+ size = rawImage.size;
+ width = rawImage.width;
+ height = rawImage.height;
+ red_offset = rawImage.red_offset;
+ red_length = rawImage.red_length;
+ blue_offset = rawImage.blue_offset;
+ blue_length = rawImage.blue_length;
+ green_offset = rawImage.green_offset;
+ green_length = rawImage.green_length;
+ alpha_offset = rawImage.alpha_offset;
+ alpha_length = rawImage.alpha_length;
+
+ data = rawImage.data;
+ }
+
+ public RawImage toRawImage() {
+ RawImage rawImage = new RawImage();
+
+ rawImage.version = version;
+ rawImage.bpp = bpp;
+ rawImage.size = size;
+ rawImage.width = width;
+ rawImage.height = height;
+ rawImage.red_offset = red_offset;
+ rawImage.red_length = red_length;
+ rawImage.blue_offset = blue_offset;
+ rawImage.blue_length = blue_length;
+ rawImage.green_offset = green_offset;
+ rawImage.green_length = green_length;
+ rawImage.alpha_offset = alpha_offset;
+ rawImage.alpha_length = alpha_length;
+
+ rawImage.data = data;
+ return rawImage;
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ DebugBridge adb = DebugBridge.createDebugBridge();
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ IDevice device = adb.getPreferredDevice();
+ RawImage screenshot = device.getScreenshot();
+ BufferedImage convertImage = ImageUtils.convertImage(screenshot);
+
+ // write out to a file
+ ImageIO.write(convertImage, "png", new File("output.png"));
+ writeOutImage(screenshot, "output.raw");
+ }
+
+ private static void writeOutImage(RawImage screenshot, String name) throws IOException {
+ ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(name));
+ out.writeObject(new MonkeyRunnerRawImage(screenshot));
+ out.close();
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/DebugBridge.java b/tools/monkeyrunner/src/com/android/monkeyrunner/DebugBridge.java
new file mode 100644
index 0000000..6ba1239
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/DebugBridge.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Logger;
+
+public class DebugBridge {
+ private final Logger log = Logger.getLogger(DebugBridge.class.getName());
+
+ private final List<IDevice> devices = Lists.newArrayList();
+
+ private final AndroidDebugBridge bridge;
+
+ public DebugBridge() {
+ this.bridge = AndroidDebugBridge.createBridge();
+ }
+
+ public DebugBridge(AndroidDebugBridge bridge) {
+ this.bridge = bridge;
+ }
+
+ /* package */ void addDevice(IDevice device) {
+ devices.add(device);
+ }
+
+ /* package */ void removeDevice(IDevice device) {
+ devices.remove(device);
+ }
+
+ public Collection<IDevice> getConnectedDevices() {
+ if (devices.size() > 0) {
+ return ImmutableList.copyOf(devices);
+ }
+ return Collections.emptyList();
+ }
+
+ public IDevice getPreferredDevice() {
+ if (devices.size() > 0) {
+ return devices.get(0);
+ }
+ return null;
+ }
+
+ public static DebugBridge createDebugBridge() {
+ AndroidDebugBridge.init(false);
+
+ final DebugBridge bridge = new DebugBridge(AndroidDebugBridge.createBridge());
+ AndroidDebugBridge.addDeviceChangeListener(new IDeviceChangeListener() {
+ public void deviceDisconnected(IDevice device) {
+ bridge.removeDevice(device);
+ }
+
+ public void deviceConnected(IDevice device) {
+ bridge.addDevice(device);
+ }
+
+ public void deviceChanged(IDevice device, int arg1) {
+ // TODO Auto-generated method stub
+
+ }
+ });
+
+ return bridge;
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/ImageUtils.java b/tools/monkeyrunner/src/com/android/monkeyrunner/ImageUtils.java
new file mode 100644
index 0000000..e4d022c
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/ImageUtils.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import com.android.ddmlib.RawImage;
+
+import java.awt.Point;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.PixelInterleavedSampleModel;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.util.Hashtable;
+/**
+ * Useful image related functions.
+ */
+public class ImageUtils {
+ // Utility class
+ private ImageUtils() { }
+
+ private static Hashtable<?,?> EMPTY_HASH = new Hashtable();
+ private static int[] BAND_OFFSETS_32 = { 0, 1, 2, 3 };
+ private static int[] BAND_OFFSETS_16 = { 0, 1 };
+
+ /**
+ * Convert a raw image into a buffered image.
+ *
+ * @param rawImage the raw image to convert
+ * @param image the old image to (possibly) recycle
+ * @return the converted image
+ */
+ public static BufferedImage convertImage(RawImage rawImage, BufferedImage image) {
+ switch (rawImage.bpp) {
+ case 16:
+ return rawImage16toARGB(image, rawImage);
+ case 32:
+ return rawImage32toARGB(rawImage);
+ }
+ return null;
+ }
+
+ /**
+ * Convert a raw image into a buffered image.
+ *
+ * @param rawImage the image to convert.
+ * @return the converted image.
+ */
+ public static BufferedImage convertImage(RawImage rawImage) {
+ return convertImage(rawImage, null);
+ }
+
+ static int getMask(int length) {
+ int res = 0;
+ for (int i = 0 ; i < length ; i++) {
+ res = (res << 1) + 1;
+ }
+
+ return res;
+ }
+
+ private static BufferedImage rawImage32toARGB(RawImage rawImage) {
+ // Do as much as we can to not make an extra copy of the data. This is just a bunch of
+ // classes that wrap's the raw byte array of the image data.
+ DataBufferByte dataBuffer = new DataBufferByte(rawImage.data, rawImage.size);
+
+ PixelInterleavedSampleModel sampleModel =
+ new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, rawImage.width, rawImage.height,
+ 4, rawImage.width * 4, BAND_OFFSETS_32);
+ WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer,
+ new Point(0, 0));
+ return new BufferedImage(new ThirtyTwoBitColorModel(rawImage), raster, false, EMPTY_HASH);
+ }
+
+ private static BufferedImage rawImage16toARGB(BufferedImage image, RawImage rawImage) {
+ // Do as much as we can to not make an extra copy of the data. This is just a bunch of
+ // classes that wrap's the raw byte array of the image data.
+ DataBufferByte dataBuffer = new DataBufferByte(rawImage.data, rawImage.size);
+
+ PixelInterleavedSampleModel sampleModel =
+ new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, rawImage.width, rawImage.height,
+ 2, rawImage.width * 2, BAND_OFFSETS_16);
+ WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer,
+ new Point(0, 0));
+ return new BufferedImage(new SixteenBitColorModel(rawImage), raster, false, EMPTY_HASH);
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/LoggingOutputReceiver.java b/tools/monkeyrunner/src/com/android/monkeyrunner/LoggingOutputReceiver.java
new file mode 100644
index 0000000..5b8eb45
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/LoggingOutputReceiver.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import com.android.ddmlib.IShellOutputReceiver;
+
+import java.util.logging.Logger;
+
+public class LoggingOutputReceiver implements IShellOutputReceiver {
+ private final Logger log;
+
+ public LoggingOutputReceiver(Logger log) {
+ this.log = log;
+ }
+
+ public void addOutput(byte[] data, int offset, int length) {
+ log.info(new String(data, offset, length));
+ }
+
+ public void flush() { }
+
+ public boolean isCancelled() {
+ return false;
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyController.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyController.java
new file mode 100644
index 0000000..7df4409
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyController.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import java.util.logging.Logger;
+
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+
+/**
+ * Application that can control an attached device using the network monkey. It has a window
+ * that shows what the current screen looks like and allows the user to click in it. Clicking in
+ * the window sends touch events to the attached device. It also supports keyboard input for
+ * typing and has buttons to press to simulate physical buttons on the device.
+ */
+public class MonkeyController extends JFrame {
+ private static final Logger LOG = Logger.getLogger(MonkeyController.class.getName());
+
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ LOG.severe("Unable to sleep");
+ throw new RuntimeException(e);
+ }
+
+ DebugBridge adb = DebugBridge.createDebugBridge();
+ MonkeyControllerFrame mf = new MonkeyControllerFrame(adb);
+ mf.setVisible(true);
+ }
+ });
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyControllerFrame.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyControllerFrame.java
new file mode 100644
index 0000000..96e6e38
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyControllerFrame.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.RawImage;
+
+import java.awt.KeyEventDispatcher;
+import java.awt.KeyboardFocusManager;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.AbstractAction;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JToolBar;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+
+/**
+ * Main window for MonkeyController.
+ */
+public class MonkeyControllerFrame extends JFrame {
+ private static final Logger LOG = Logger.getLogger(MonkeyControllerFrame.class.getName());
+
+ private final DebugBridge adb;
+
+ private final JButton refreshButton = new JButton("Refresh");
+ private final JButton variablesButton = new JButton("Variable");
+ private final JLabel imageLabel = new JLabel();
+ private final VariableFrame variableFrame;
+
+ private IDevice preferredDevice;
+ private MonkeyManager monkeyManager;
+ private BufferedImage currentImage;
+
+ private final Timer timer = new Timer(1000, new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ updateScreen();
+ }
+ });
+
+ private class PressAction extends AbstractAction {
+ private final PhysicalButton button;
+
+ public PressAction(PhysicalButton button) {
+ this.button = button;
+ }
+ public void actionPerformed(ActionEvent event) {
+ try {
+ monkeyManager.press(button);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ updateScreen();
+ }
+ }
+
+ private JButton createToolbarButton(PhysicalButton hardButton) {
+ JButton button = new JButton(new PressAction(hardButton));
+ button.setText(hardButton.getKeyName());
+ return button;
+ }
+
+ public MonkeyControllerFrame(DebugBridge adb) {
+ super("MonkeyController");
+
+ this.adb = adb;
+
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
+ JToolBar toolbar = new JToolBar();
+
+ toolbar.add(createToolbarButton(PhysicalButton.HOME));
+ toolbar.add(createToolbarButton(PhysicalButton.BACK));
+ toolbar.add(createToolbarButton(PhysicalButton.SEARCH));
+ toolbar.add(createToolbarButton(PhysicalButton.MENU));
+
+ add(toolbar);
+ add(refreshButton);
+ add(variablesButton);
+ add(imageLabel);
+
+ refreshButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ updateScreen();
+ }
+ });
+
+ variableFrame = new VariableFrame();
+ variablesButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ variableFrame.setVisible(true);
+ }
+ });
+
+ imageLabel.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent event) {
+ try {
+ monkeyManager.touch(event.getX(), event.getY());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ updateScreen();
+ }
+
+ });
+
+ KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
+ focusManager.addKeyEventDispatcher(new KeyEventDispatcher() {
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (KeyEvent.KEY_TYPED == event.getID()) {
+ try {
+ monkeyManager.type(event.getKeyChar());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return false;
+ }
+ });
+
+ SwingUtilities.invokeLater(new Runnable(){
+ public void run() {
+ init();
+ variableFrame.init(monkeyManager);
+ }
+ });
+
+ pack();
+ }
+
+ private void updateScreen() {
+ RawImage screenshot;
+ try {
+ screenshot = preferredDevice.getScreenshot();
+ } catch (IOException e) {
+ LOG.log(Level.SEVERE, "Error getting screenshot", e);
+ throw new RuntimeException(e);
+ }
+
+ currentImage = ImageUtils.convertImage(screenshot, currentImage);
+ imageLabel.setIcon(new ImageIcon(currentImage));
+
+ pack();
+ }
+
+ private void init() {
+ preferredDevice = adb.getPreferredDevice();
+ monkeyManager = new MonkeyManager(preferredDevice);
+ updateScreen();
+ timer.start();
+ }
+
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java
new file mode 100644
index 0000000..b62d4c9
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import com.android.ddmlib.RawImage;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.ImageOutputStream;
+
+/**
+ * Jython object to encapsulate images that have been taken.
+ */
+public class MonkeyImage {
+ private final RawImage screenshot;
+
+ /**
+ * Create a new monkey image.
+ *
+ * @param screenshot the screenshot to wrap.
+ */
+ MonkeyImage(RawImage screenshot) {
+ this.screenshot = screenshot;
+ }
+
+ /**
+ * Write out the file to the specified location. This function tries to guess at the output
+ * format depending on the file extension given. If unable to determine, it uses PNG.
+ *
+ * @param path where to write the file
+ * @return success.
+ */
+ public boolean writeToFile(String path) {
+ int offset = path.lastIndexOf('.');
+ if (offset < 0) {
+ return writeToFile(path, "png");
+ }
+ String ext = path.substring(offset + 1);
+ Iterator<ImageWriter> writers = ImageIO.getImageWritersBySuffix(ext);
+ if (!writers.hasNext()) {
+ return writeToFile(path, "png");
+ }
+ ImageWriter writer = writers.next();
+ BufferedImage image = ImageUtils.convertImage(screenshot);
+ try {
+ File f = new File(path);
+ f.delete();
+
+ ImageOutputStream outputStream = ImageIO.createImageOutputStream(f);
+ writer.setOutput(outputStream);
+
+ try {
+ writer.write(image);
+ } finally {
+ writer.dispose();
+ outputStream.flush();
+ }
+ } catch (IOException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Write out the file to the specified location.
+ *
+ * @param path where to write the file
+ * @param format the ImageIO format to use.
+ * @return success.
+ */
+ public boolean writeToFile(String path, String format) {
+ BufferedImage image = ImageUtils.convertImage(screenshot);
+ try {
+ ImageIO.write(image, format, new File(path));
+ } catch (IOException e) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyManager.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyManager.java
new file mode 100644
index 0000000..d9fa7be
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyManager.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import com.google.common.collect.Lists;
+
+import com.android.ddmlib.IDevice;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Provides a nicer interface to interacting with the low-level network access protocol for talking
+ * to the monkey.
+ *
+ * This class is thread-safe and can handle being called from multiple threads.
+ */
+public class MonkeyManager {
+ private static String DEFAULT_MONKEY_SERVER_ADDRESS = "127.0.0.1";
+ private static int DEFAULT_MONKEY_PORT = 12345;
+
+ private static Logger LOG = Logger.getLogger(MonkeyManager.class.getName());
+
+ private Socket monkeySocket;
+ private BufferedWriter monkeyWriter;
+ private BufferedReader monkeyReader;
+ private final IDevice device;
+
+ /**
+ * Create a new MonkeyMananger to talk to the specified device.
+ *
+ * @param device the device to talk to
+ * @param address the address on which to talk to the device
+ * @param port the port on which to talk to the device
+ */
+ public MonkeyManager(IDevice device, String address, int port) {
+ this.device = device;
+ device.createForward(port, port);
+ String command = "monkey --port " + port + "&";
+ try {
+ device.executeShellCommand(command, new LoggingOutputReceiver(LOG));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ try {
+ InetAddress addr = InetAddress.getByName(address);
+ monkeySocket = new Socket(addr, port);
+ monkeyWriter = new BufferedWriter(new OutputStreamWriter(monkeySocket.getOutputStream()));
+ monkeyReader = new BufferedReader(new InputStreamReader(monkeySocket.getInputStream()));
+ } catch (UnknownHostException e) {
+ throw new RuntimeException(e);
+ } catch(IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Create a new MonkeyMananger to talk to the specified device.
+ *
+ * @param device the device to talk to
+ */
+ public MonkeyManager(IDevice device) {
+ this(device, DEFAULT_MONKEY_SERVER_ADDRESS, DEFAULT_MONKEY_PORT);
+ }
+
+ /**
+ * Send a touch down event at the specified location.
+ *
+ * @param x the x coordinate of where to click
+ * @param y the y coordinate of where to click
+ * @return success or not
+ * @throws IOException on error communicating with the device
+ */
+ public boolean touchDown(int x, int y) throws IOException {
+ return sendMonkeyEvent("touch down " + x + " " + y);
+ }
+
+ /**
+ * Send a touch down event at the specified location.
+ *
+ * @param x the x coordinate of where to click
+ * @param y the y coordinate of where to click
+ * @return success or not
+ * @throws IOException on error communicating with the device
+ */
+ public boolean touchUp(int x, int y) throws IOException {
+ return sendMonkeyEvent("touch up " + x + " " + y);
+ }
+
+ /**
+ * Send a touch (down and then up) event at the specified location.
+ *
+ * @param x the x coordinate of where to click
+ * @param y the y coordinate of where to click
+ * @return success or not
+ * @throws IOException on error communicating with the device
+ */
+ public boolean touch(int x, int y) throws IOException {
+ return sendMonkeyEvent("tap " + x + " " + y);
+ }
+
+ /**
+ * Press a physical button on the device.
+ *
+ * @param name the name of the button (As specified in the protocol)
+ * @return success or not
+ * @throws IOException on error communicating with the device
+ */
+ public boolean press(String name) throws IOException {
+ return sendMonkeyEvent("press " + name);
+ }
+
+ /**
+ * Press a physical button on the device.
+ *
+ * @param button the button to press
+ * @return success or not
+ * @throws IOException on error communicating with the device
+ */
+ public boolean press(PhysicalButton button) throws IOException {
+ return press(button.getKeyName());
+ }
+
+ /**
+ * This function allows the communication bridge between the host and the device
+ * to be invisible to the script for internal needs.
+ * It splits a command into monkey events and waits for responses for each over an adb tcp socket.
+ * Returns on an error, else continues and sets up last response.
+ *
+ * @param command the monkey command to send to the device
+ * @return the (unparsed) response returned from the monkey.
+ */
+ private String sendMonkeyEventAndGetResponse(String command) throws IOException {
+ command = command.trim();
+ LOG.info("Monkey Command: " + command + ".");
+
+ // send a single command and get the response
+ monkeyWriter.write(command + "\n");
+ monkeyWriter.flush();
+ return monkeyReader.readLine();
+ }
+
+ /**
+ * Parse a monkey response string to see if the command succeeded or not.
+ *
+ * @param monkeyResponse the response
+ * @return true if response code indicated success.
+ */
+ private boolean parseResponseForSuccess(String monkeyResponse) {
+ if (monkeyResponse == null) {
+ return false;
+ }
+ // return on ok
+ if(monkeyResponse.startsWith("OK")) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Parse a monkey response string to get the extra data returned.
+ *
+ * @param monkeyResponse the response
+ * @return any extra data that was returned, or empty string if there was nothing.
+ */
+ private String parseResponseForExtra(String monkeyResponse) {
+ int offset = monkeyResponse.indexOf(':');
+ if (offset < 0) {
+ return "";
+ }
+ return monkeyResponse.substring(offset + 1);
+ }
+
+ /**
+ * This function allows the communication bridge between the host and the device
+ * to be invisible to the script for internal needs.
+ * It splits a command into monkey events and waits for responses for each over an
+ * adb tcp socket.
+ *
+ * @param command the monkey command to send to the device
+ * @return true on success.
+ */
+ private boolean sendMonkeyEvent(String command) throws IOException {
+ synchronized (this) {
+ String monkeyResponse = sendMonkeyEventAndGetResponse(command);
+ return parseResponseForSuccess(monkeyResponse);
+ }
+ }
+
+ /**
+ * Close all open resources related to this device.
+ */
+ public void close() {
+ try {
+ monkeySocket.close();
+ } catch (IOException e) {
+ LOG.log(Level.SEVERE, "Unable to close monkeySocket", e);
+ }
+ try {
+ monkeyReader.close();
+ } catch (IOException e) {
+ LOG.log(Level.SEVERE, "Unable to close monkeyReader", e);
+ }
+ try {
+ monkeyWriter.close();
+ } catch (IOException e) {
+ LOG.log(Level.SEVERE, "Unable to close monkeyWriter", e);
+ }
+ }
+
+ /**
+ * Function to get a static variable from the device
+ *
+ * @param name name of static variable to get
+ * @return the value of the variable, or empty string if there was an error
+ */
+ public String getVariable(String name) throws IOException {
+ synchronized (this) {
+ String response = sendMonkeyEventAndGetResponse("getvar " + name);
+ if (!parseResponseForSuccess(response)) {
+ return "";
+ }
+ return parseResponseForExtra(response);
+ }
+ }
+
+ /**
+ * Function to get the list of static variables from the device
+ */
+ public Collection<String> listVariable() throws IOException {
+ synchronized (this) {
+ String response = sendMonkeyEventAndGetResponse("listvar");
+ if (!parseResponseForSuccess(response)) {
+ Collections.emptyList();
+ }
+ String extras = parseResponseForExtra(response);
+ return Lists.newArrayList(extras.split(" "));
+ }
+ }
+
+ /**
+ * Tells the monkey that we are done for this session.
+ * @throws IOException
+ */
+ public void done() throws IOException {
+ // this command just drops the connection, so handle it here
+ synchronized (this) {
+ sendMonkeyEventAndGetResponse("done");
+ }
+ }
+
+ /**
+ * Send a tap event at the specified location.
+ *
+ * @param x the x coordinate of where to click
+ * @param y the y coordinate of where to click
+ * @return success or not
+ * @throws IOException
+ * @throws IOException on error communicating with the device
+ */
+ public boolean tap(int x, int y) throws IOException {
+ return sendMonkeyEvent("tap " + x + " " + y);
+ }
+
+ /**
+ * Type the following string to the monkey.
+ *
+ * @param text the string to type
+ * @return success
+ * @throws IOException
+ */
+ public boolean type(String text) throws IOException {
+ // The network protocol can't handle embedded line breaks, so we have to handle it
+ // here instead
+ StringTokenizer tok = new StringTokenizer(text, "\n", true);
+ while (tok.hasMoreTokens()) {
+ String line = tok.nextToken();
+ if ("\n".equals(line)) {
+ boolean success = press(PhysicalButton.ENTER);
+ if (!success) {
+ return false;
+ }
+ } else {
+ boolean success = sendMonkeyEvent("type " + line);
+ if (!success) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Type the character to the monkey.
+ *
+ * @param keyChar the character to type.
+ * @return success
+ * @throws IOException
+ */
+ public boolean type(char keyChar) throws IOException {
+ return type(Character.toString(keyChar));
+ }
+
+ /**
+ * Gets the underlying device so low-level commands can be executed.
+ *
+ * NOTE: using this method doesn't provide any thread safety. If needed, the MonkeyMananger
+ * itself should be used as the lock for synchronization. For Example:
+ *
+ * <code>
+ * MonkeyMananger mgr;
+ * IDevice device = mgr.getDevice();
+ * synchronized (mgr) {
+ * /// Do stuff with the device
+ * }
+ * </code>
+ *
+ * @return the device.
+ */
+ public IDevice getDevice() {
+ return device;
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRecorder.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRecorder.java
deleted file mode 100644
index f06eafd..0000000
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRecorder.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.monkeyrunner;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-import org.jheer.XMLWriter;
-
-/**
- * MonkeyRecorder is a host side class that records the output of scripts that are run.
- * It creates a unique directory, puts in an xml file that records each cmd and result.
- * It stores every screenshot in this directory.
- * When finished, it zips this all up.
- *
- * Calling Sequence:
- * mr = new MonkeyRecorder(scriptName);
- * mr.startCommand();
- * [mr.addAttribute(name, value);]
- * ...
- * [mr.addInput(cmd);]
- * [mr.addResults(result, filename);] // filename = "" if no screenshot
- * mr.endCommand();
- * mr.addComment(comment);
- * mr.startCommand();
- * ...
- * mr.endCommand();
- * ...
- * mr.close();
- *
- * With MonkeyRunner this should output an xml file, <script_name>-yyyyMMdd-HH:mm:ss.xml, into the
- * directory out/<script_name>-yyyyMMdd-HH:mm:ss with the contents like:
- *
- * <?xml version="1.0" encoding='UTF-8'?>
- * <!-- Monkey Script Results -->
- * <script_run script_name="filename" monkeyRunnerVersion="0.2">
- * <!-- Device specific variables -->
- * <device_var var_name="name" var_value="value" />
- * <device_var name="build.display" value="opal-userdebug 1.6 DRC79 14207 test-keys"/>
- * ...
- * <!-- Script commands -->
- * <command>
- * dateTime="20090921-17:08:43"
- * <input cmd="Pressing: menu"/>
- * <response result="OK" dateTime="20090921-17:08:43"/>
- * </command>
- * ...
- * <command>
- * dateTime="20090921-17:09:44"
- * <input cmd="grabscreen"/>
- * <response result="OK" dateTime="20090921-17:09:45" screenshot="home_screen-20090921-17:09:45.png"/>
- * </command>
- * ...
- * </script_run>
- *
- * And then zip it up with all the screenshots in the file: <script_name>-yyyyMMdd-HH:mm:ss.zip.
- */
-
-public class MonkeyRecorder {
-
- // xml file to store output results in
- private static String mXmlFilename;
- private static FileWriter mXmlFile;
- private static XMLWriter mXmlWriter;
-
- // unique subdirectory to put results in (screenshots and xml file)
- private static String mDirname;
- private static List<String> mScreenShotNames = new ArrayList<String>();
-
- // where we store all the results for all the script runs
- private static final String ROOT_DIR = "out";
-
- // for getting the date and time in now()
- private static final SimpleDateFormat SIMPLE_DATE_TIME_FORMAT =
- new SimpleDateFormat("yyyyMMdd-HH:mm:ss");
-
- /**
- * Create a new MonkeyRecorder that records commands and zips up screenshots for submittal
- *
- * @param scriptName filepath of the monkey script we are running
- */
- public MonkeyRecorder(String scriptName, String version) throws IOException {
- // Create directory structure to store xml file, images and zips
- File scriptFile = new File(scriptName);
- scriptName = scriptFile.getName(); // Get rid of path
- mDirname = ROOT_DIR + "/" + stripType(scriptName) + "-" + now();
- new File(mDirname).mkdirs();
-
- // Initialize xml file
- mXmlFilename = stampFilename(stripType(scriptName) + ".xml");
- initXmlFile(scriptName, version);
- }
-
- // Get the current date and time in a simple string format (used for timestamping filenames)
- private static String now() {
- return SIMPLE_DATE_TIME_FORMAT.format(Calendar.getInstance().getTime());
- }
-
- /**
- * Initialize the xml file writer
- *
- * @param scriptName filename (not path) of the monkey script, stored as attribute in the xml file
- * @param version of the monkey runner test system
- */
- private static void initXmlFile(String scriptName, String version) throws IOException {
- String[] names = new String[] { "script_name", "monkeyRunnerVersion" };
- String[] values = new String[] { scriptName, version };
- mXmlFile = new FileWriter(mDirname + "/" + mXmlFilename);
- mXmlWriter = new XMLWriter(mXmlFile);
- mXmlWriter.begin();
- mXmlWriter.comment("Monkey Script Results");
- mXmlWriter.start("script_run", names, values, names.length);
- }
-
- /**
- * Add a comment to the xml file.
- *
- * @param comment comment to add to the xml file
- */
- public static void addComment(String comment) throws IOException {
- mXmlWriter.comment(comment);
- }
-
- /**
- * Begin writing a command xml element
- */
- public static void startCommand() throws IOException {
- mXmlWriter.start("command", "dateTime", now());
- }
-
- /**
- * Write a command name attribute in a command xml element.
- * It's add as a sinlge script command could be multiple monkey commands.
- *
- * @param cmd command sent to the monkey
- */
- public static void addInput(String cmd) throws IOException {
- String name = "cmd";
- String value = cmd;
- mXmlWriter.tag("input", name, value);
- }
-
- /**
- * Write a response xml element in a command.
- * Attributes include the monkey result, datetime, and possibly screenshot filename
- *
- * @param result response of the monkey to the command
- * @param filename filename of the screen shot (or other file to be included)
- */
- public static void addResult(String result, String filename) throws IOException {
- int num_args = 2;
- String[] names = new String[3];
- String[] values = new String[3];
- names[0] = "result";
- values[0] = result;
- names[1] = "dateTime";
- values[1] = now();
- if (filename.length() != 0) {
- names[2] = "screenshot";
- values[2] = stampFilename(filename);
- addScreenShot(filename);
- num_args = 3;
- }
- mXmlWriter.tag("response", names, values, num_args);
- }
-
- /**
- * Add an attribut to an open xml element. name="escaped_value"
- *
- * @param name name of the attribute
- * @param value value of the attribute
- */
- public static void addAttribute(String name, String value) throws IOException {
- mXmlWriter.addAttribute(name, value);
- }
-
- /**
- * Add an xml device variable element. name="escaped_value"
- *
- * @param name name of the variable
- * @param value value of the variable
- */
- public static void addDeviceVar(String name, String value) throws IOException {
- String[] names = {"name", "value"};
- String[] values = {name, value};
- mXmlWriter.tag("device_var", names, values, names.length);
- }
-
- /**
- * Move the screenshot to storage and remember you did it so it can be zipped up later.
- *
- * @param filename file name of the screenshot to be stored (Not path name)
- */
- private static void addScreenShot(String filename) {
- File file = new File(filename);
- String screenShotName = stampFilename(filename);
- file.renameTo(new File(mDirname, screenShotName));
- mScreenShotNames.add(screenShotName);
- }
-
- /**
- * Finish writing a command xml element
- */
- public static void endCommand() throws IOException {
- mXmlWriter.end();
- }
-
- /**
- * Add datetime in front of filetype (the stuff after and including the last infamous '.')
- *
- * @param filename path of file to be stamped
- */
- private static String stampFilename(String filename) {
- //
- int typeIndex = filename.lastIndexOf('.');
- if (typeIndex == -1) {
- return filename + "-" + now();
- }
- return filename.substring(0, typeIndex) + "-" + now() + filename.substring(typeIndex);
- }
-
- /**
- * Strip out the file type (the stuff after and including the last infamous '.')
- *
- * @param filename path of file to be stripped of type information
- */
- private static String stripType(String filename) {
- //
- int typeIndex = filename.lastIndexOf('.');
- if (typeIndex == -1)
- return filename;
- return filename.substring(0, typeIndex);
- }
-
- /**
- * Close the monkeyRecorder by closing the xml file and zipping it up with the screenshots.
- *
- * @param filename path of file to be stripped of type information
- */
- public static void close() throws IOException {
- // zip up xml file and screenshots into ROOT_DIR.
- byte[] buf = new byte[1024];
- String zipFileName = mXmlFilename + ".zip";
- endCommand();
- mXmlFile.close();
- FileOutputStream zipFile = new FileOutputStream(ROOT_DIR + "/" + zipFileName);
- ZipOutputStream out = new ZipOutputStream(zipFile);
-
- // add the xml file
- addFileToZip(out, mDirname + "/" + mXmlFilename, buf);
-
- // Add the screenshots
- for (String filename : mScreenShotNames) {
- addFileToZip(out, mDirname + "/" + filename, buf);
- }
- out.close();
- }
-
- /**
- * Helper function to zip up a file into an open zip archive.
- *
- * @param zip the stream of the zip archive
- * @param filepath the filepath of the file to be added to the zip archive
- * @param buf storage place to stage reads of file before zipping
- */
- private static void addFileToZip(ZipOutputStream zip, String filepath, byte[] buf) throws IOException {
- FileInputStream in = new FileInputStream(filepath);
- zip.putNextEntry(new ZipEntry(filepath));
- int len;
- while ((len = in.read(buf)) > 0) {
- zip.write(buf, 0, len);
- }
- zip.closeEntry();
- in.close();
- }
-}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
index 4734ba1..4e613b6 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,644 +13,114 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.monkeyrunner;
-import com.android.ddmlib.AndroidDebugBridge;
-import com.android.ddmlib.IDevice;
-import com.android.ddmlib.Log;
import com.android.ddmlib.NullOutputReceiver;
-import com.android.ddmlib.RawImage;
-import com.android.ddmlib.Log.ILogOutput;
-import com.android.ddmlib.Log.LogLevel;
-import java.awt.image.BufferedImage;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.imageio.ImageIO;
/**
- * MonkeyRunner is a host side application to control a monkey instance on a
- * device. MonkeyRunner provides some useful helper functions to control the
- * device as well as various other methods to help script tests.
+ * This is the main interface class into the jython bindings.
*/
public class MonkeyRunner {
+ /* package */ static MonkeyManager MONKEY_MANANGER;
- static String monkeyServer = "127.0.0.1";
- static int monkeyPort = 1080;
- static Socket monkeySocket = null;
+ /**
+ * This is where we would put any standard init stuff, if we had any.
+ */
+ public static void start_script() throws IOException {
+ }
- static IDevice monkeyDevice;
+ /**
+ * This is a house cleaning routine to run after finishing a script.
+ * Puts the monkey server in a known state and closes the recording.
+ */
+ public static void end_script() throws IOException {
+ MONKEY_MANANGER.done();
+ }
- static BufferedReader monkeyReader;
- static BufferedWriter monkeyWriter;
- static String monkeyResponse;
+ /** This is a method for scripts to launch an activity on the device
+ *
+ * @param name The name of the activity to launch
+ */
+ public static void launch_activity(String name) throws IOException {
+ System.out.println("Launching: " + name);
+ // We're single threaded, so don't worry about the thead-safety.
+ MONKEY_MANANGER.getDevice().executeShellCommand("am start -a android.intent.action.MAIN -n "
+ + name, new NullOutputReceiver());
+ }
- static MonkeyRecorder monkeyRecorder;
+ /**
+ * Grabs the current state of the screen stores it as a png
+ *
+ * @param tag filename or tag descriptor of the screenshot
+ */
+ public static MonkeyImage grabscreen() throws IOException {
+ return new MonkeyImage(MONKEY_MANANGER.getDevice().getScreenshot());
+ }
- static String scriptName = null;
-
- // Obtain a suitable logger.
- private static Logger logger = Logger.getLogger("com.android.monkeyrunner");
-
- // delay between key events
- final static int KEY_INPUT_DELAY = 1000;
-
- // version of monkey runner
- final static String monkeyRunnerVersion = "0.4";
-
- // TODO: interface cmd; class xml tags; fix logger; test class/script
-
- public static void main(String[] args) throws IOException {
-
- // haven't figure out how to get below INFO...bad parent. Pass -v INFO to turn on logging
- logger.setLevel(Level.parse("WARNING"));
- processOptions(args);
-
- logger.info("initAdb");
- initAdbConnection();
- logger.info("openMonkeyConnection");
- openMonkeyConnection();
-
- logger.info("start_script");
- start_script();
-
- logger.info("ScriptRunner.run");
- ScriptRunner.run(scriptName);
-
- logger.info("end_script");
- end_script();
- logger.info("closeMonkeyConnection");
- closeMonkeyConnection();
- }
-
- /**
- * Initialize an adb session with a device connected to the host
- *
- */
- public static void initAdbConnection() {
- String adbLocation = "adb";
- boolean device = false;
- boolean emulator = false;
- String serial = null;
-
- AndroidDebugBridge.init(false /* debugger support */);
-
- try {
- AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(
- adbLocation, true /* forceNewBridge */);
-
- // we can't just ask for the device list right away, as the internal thread getting
- // them from ADB may not be done getting the first list.
- // Since we don't really want getDevices() to be blocking, we wait here manually.
- int count = 0;
- while (bridge.hasInitialDeviceList() == false) {
+ /**
+ * Sleeper method for script to call
+ *
+ * @param msec msecs to sleep for
+ */
+ public static void sleep(int msec) throws IOException {
try {
- Thread.sleep(100);
- count++;
+ Thread.sleep(msec);
} catch (InterruptedException e) {
- // pass
+ e.printStackTrace();
}
-
- // let's not wait > 10 sec.
- if (count > 100) {
- System.err.println("Timeout getting device list!");
- return;
- }
- }
-
- // now get the devices
- IDevice[] devices = bridge.getDevices();
-
- if (devices.length == 0) {
- printAndExit("No devices found!", true /* terminate */);
- }
-
- monkeyDevice = null;
-
- if (emulator || device) {
- for (IDevice d : devices) {
- // this test works because emulator and device can't both be true at the same
- // time.
- if (d.isEmulator() == emulator) {
- // if we already found a valid target, we print an error and return.
- if (monkeyDevice != null) {
- if (emulator) {
- printAndExit("Error: more than one emulator launched!",
- true /* terminate */);
- } else {
- printAndExit("Error: more than one device connected!",true /* terminate */);
- }
- }
- monkeyDevice = d;
- }
- }
- } else if (serial != null) {
- for (IDevice d : devices) {
- if (serial.equals(d.getSerialNumber())) {
- monkeyDevice = d;
- break;
- }
- }
- } else {
- if (devices.length > 1) {
- printAndExit("Error: more than one emulator or device available!",
- true /* terminate */);
- }
- monkeyDevice = devices[0];
- }
-
- monkeyDevice.createForward(monkeyPort, monkeyPort);
- String command = "monkey --port " + monkeyPort;
- monkeyDevice.executeShellCommand(command, new NullOutputReceiver());
-
- } catch(IOException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Open a tcp session over adb with the device to communicate monkey commands
- */
- public static void openMonkeyConnection() {
- try {
- InetAddress addr = InetAddress.getByName(monkeyServer);
- monkeySocket = new Socket(addr, monkeyPort);
- monkeyWriter = new BufferedWriter(new OutputStreamWriter(monkeySocket.getOutputStream()));
- monkeyReader = new BufferedReader(new InputStreamReader(monkeySocket.getInputStream()));
- } catch (UnknownHostException e) {
- e.printStackTrace();
- } catch(IOException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Close tcp session with the monkey on the device
- *
- */
- public static void closeMonkeyConnection() {
- try {
- monkeyReader.close();
- monkeyWriter.close();
- monkeySocket.close();
- AndroidDebugBridge.terminate();
- } catch(IOException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * This is a house cleaning routine to run before starting a script. Puts
- * the device in a known state and starts recording interesting info.
- */
- public static void start_script() throws IOException {
- press("menu", false);
- press("menu", false);
- press("home", false);
-
- // Start recording the script output, might want md5 signature of file for completeness
- monkeyRecorder = new MonkeyRecorder(scriptName, monkeyRunnerVersion);
-
- // Record what device we are running on
- addDeviceVars();
- monkeyRecorder.addComment("Script commands");
- }
-
- /**
- * This is a house cleaning routine to run after finishing a script.
- * Puts the monkey server in a known state and closes the recording.
- */
- public static void end_script() throws IOException {
- String command = "done";
- sendMonkeyEvent(command, false, false);
-
- // Stop the recording and zip up the results
- monkeyRecorder.close();
- }
-
- /** This is a method for scripts to launch an activity on the device
- *
- * @param name The name of the activity to launch
- */
- public static void launch_activity(String name) throws IOException {
- System.out.println("Launching: " + name);
- recordCommand("Launching: " + name);
- monkeyDevice.executeShellCommand("am start -a android.intent.action.MAIN -n "
- + name, new NullOutputReceiver());
- // void return, so no response given, just close the command element in the xml file.
- monkeyRecorder.endCommand();
- }
-
- /**
- * Grabs the current state of the screen stores it as a png
- *
- * @param tag filename or tag descriptor of the screenshot
- */
- public static void grabscreen(String tag) throws IOException {
- tag += ".png";
-
- try {
- Thread.sleep(1000);
- getDeviceImage(monkeyDevice, tag, false);
- } catch (InterruptedException e) {
- }
- }
-
- /**
- * Sleeper method for script to call
- *
- * @param msec msecs to sleep for
- */
- public static void sleep(int msec) throws IOException {
- try {
- recordCommand("sleep: " + msec);
- Thread.sleep(msec);
- recordResponse("OK");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Tap function for scripts to call at a particular x and y location
- *
- * @param x x-coordinate
- * @param y y-coordinate
- */
- public static boolean tap(int x, int y) throws IOException {
- String command = "tap " + x + " " + y;
- boolean result = sendMonkeyEvent(command);
- return result;
- }
-
- /**
- * Press function for scripts to call on a particular button or key
- *
- * @param key key to press
- */
- public static boolean press(String key) throws IOException {
- return press(key, true);
- }
-
- /**
- * Press function for scripts to call on a particular button or key
- *
- * @param key key to press
- * @param print whether to send output to user
- */
- private static boolean press(String key, boolean print) throws IOException {
- String command = "press " + key;
- boolean result = sendMonkeyEvent(command, print, true);
- return result;
- }
-
- /**
- * dpad down function
- */
- public static boolean down() throws IOException {
- return press("dpad_down");
- }
-
- /**
- * dpad up function
- */
- public static boolean up() throws IOException {
- return press("dpad_up");
- }
-
- /**
- * Function to type text on the device
- *
- * @param text text to type
- */
- public static boolean type(String text) throws IOException {
- boolean result = false;
- // text might have line ends, which signal new monkey command, so we have to eat and reissue
- String[] lines = text.split("[\\r\\n]+");
- for (String line: lines) {
- result = sendMonkeyEvent("type " + line + "\n");
- }
- // return last result. Should never fail..?
- return result;
- }
-
- /**
- * Function to get a static variable from the device
- *
- * @param name name of static variable to get
- */
- public static boolean getvar(String name) throws IOException {
- return sendMonkeyEvent("getvar " + name + "\n");
- }
-
- /**
- * Function to get the list of static variables from the device
- */
- public static boolean listvar() throws IOException {
- return sendMonkeyEvent("listvar \n");
- }
-
- /**
- * This function is the communication bridge between the host and the device.
- * It sends monkey events and waits for responses over the adb tcp socket.
- * This version if for all scripted events so that they get recorded and reported to user.
- *
- * @param command the monkey command to send to the device
- */
- private static boolean sendMonkeyEvent(String command) throws IOException {
- return sendMonkeyEvent(command, true, true);
- }
-
- /**
- * This function allows the communication bridge between the host and the device
- * to be invisible to the script for internal needs.
- * It splits a command into monkey events and waits for responses for each over an adb tcp socket.
- * Returns on an error, else continues and sets up last response.
- *
- * @param command the monkey command to send to the device
- * @param print whether to print out the responses to the user
- * @param record whether to put the command in the xml file that stores test outputs
- */
- private static boolean sendMonkeyEvent(String command, Boolean print, Boolean record) throws IOException {
- command = command.trim();
- if (print)
- System.out.println("MonkeyCommand: " + command);
- if (record)
- recordCommand(command);
- logger.info("Monkey Command: " + command + ".");
-
- // send a single command and get the response
- monkeyWriter.write(command + "\n");
- monkeyWriter.flush();
- monkeyResponse = monkeyReader.readLine();
-
- if(monkeyResponse != null) {
- // if a command returns with a response
- if (print)
- System.out.println("MonkeyServer: " + monkeyResponse);
- if (record)
- recordResponse(monkeyResponse);
- logger.info("Monkey Response: " + monkeyResponse + ".");
-
- // return on error
- if (monkeyResponse.startsWith("ERROR"))
- return false;
-
- // return on ok
- if(monkeyResponse.startsWith("OK"))
- return true;
-
- // return on something else?
- return false;
- }
- // didn't get a response...
- if (print)
- System.out.println("MonkeyServer: ??no response");
- if (record)
- recordResponse("??no response");
- logger.info("Monkey Response: ??no response.");
-
- //return on no response
- return false;
- }
-
- /**
- * Record the command in the xml file
- *
- * @param command the command sent to the monkey server
- */
- private static void recordCommand(String command) throws IOException {
- if (monkeyRecorder != null) { // don't record setup junk
- monkeyRecorder.startCommand();
- monkeyRecorder.addInput(command);
- }
- }
-
- /**
- * Record the response in the xml file
- *
- * @param response the response sent by the monkey server
- */
- private static void recordResponse(String response) throws IOException {
- recordResponse(response, "");
- }
-
- /**
- * Record the response and the filename in the xml file, store the file (to be zipped up later)
- *
- * @param response the response sent by the monkey server
- * @param filename the filename of a file to be time stamped, recorded in the xml file and stored
- */
- private static void recordResponse(String response, String filename) throws IOException {
- if (monkeyRecorder != null) { // don't record setup junk
- monkeyRecorder.addResult(response, filename); // ignores file if filename empty
- monkeyRecorder.endCommand();
- }
- }
-
- /**
- * Add the device variables to the xml file in monkeyRecorder.
- * The results get added as device_var tags in the script_run tag
- */
- private static void addDeviceVars() throws IOException {
- monkeyRecorder.addComment("Device specific variables");
- sendMonkeyEvent("listvar \n", false, false);
- if (monkeyResponse.startsWith("OK:")) {
- // peel off "OK:" string and get the individual var names
- String[] varNames = monkeyResponse.substring(3).split("\\s+");
- // grab all the individual var values
- for (String name: varNames) {
- sendMonkeyEvent("getvar " + name, false, false);
- if(monkeyResponse != null) {
- if (monkeyResponse.startsWith("OK") ) {
- if (monkeyResponse.length() > 2) {
- monkeyRecorder.addDeviceVar(name, monkeyResponse.substring(3));
- } else {
- // only got OK - good variable but no value
- monkeyRecorder.addDeviceVar(name, "null");
- }
- } else {
- // error returned - couldn't get var value for name... include error return
- monkeyRecorder.addDeviceVar(name, monkeyResponse);
- }
- } else {
- // no monkeyResponse - bad variable with no value
- monkeyRecorder.addDeviceVar(name, "null");
- }
- }
- } else {
- // it's an error, can't find variable names...
- monkeyRecorder.addAttribute("listvar", monkeyResponse);
- }
- }
-
- /**
- * Process the command-line options
- *
- * @return Returns true if options were parsed with no apparent errors.
- */
- private static void processOptions(String[] args) {
- // parse command line parameters.
- int index = 0;
-
- do {
- String argument = args[index++];
-
- if ("-s".equals(argument)) {
- if(index == args.length) {
- printUsageAndQuit("Missing Server after -s");
- }
-
- monkeyServer = args[index++];
-
- } else if ("-p".equals(argument)) {
- // quick check on the next argument.
- if (index == args.length) {
- printUsageAndQuit("Missing Server port after -p");
- }
-
- monkeyPort = Integer.parseInt(args[index++]);
-
- } else if ("-v".equals(argument)) {
- // quick check on the next argument.
- if (index == args.length) {
- printUsageAndQuit("Missing Log Level after -v");
- }
-
- Level level = Level.parse(args[index++]);
- logger.setLevel(level);
- level = logger.getLevel();
- System.out.println("Log level set to: " + level + "(" + level.intValue() + ").");
- System.out.println("Warning: Log levels below INFO(800) not working currently... parent issues");
-
- } else if (argument.startsWith("-")) {
- // we have an unrecognized argument.
- printUsageAndQuit("Unrecognized argument: " + argument + ".");
-
- monkeyPort = Integer.parseInt(args[index++]);
-
- } else {
- // get the filepath of the script to run. This will be the last undashed argument.
- scriptName = argument;
- }
- } while (index < args.length);
- }
-
- /*
- * Grab an image from an ADB-connected device.
- */
- private static void getDeviceImage(IDevice device, String filepath, boolean landscape)
- throws IOException {
- RawImage rawImage;
- recordCommand("grabscreen");
- System.out.println("Grabbing Screeshot: " + filepath + ".");
-
- try {
- rawImage = device.getScreenshot();
- }
- catch (IOException ioe) {
- recordResponse("No frame buffer", "");
- printAndExit("Unable to get frame buffer: " + ioe.getMessage(), true /* terminate */);
- return;
}
- // device/adb not available?
- if (rawImage == null) {
- recordResponse("No image", "");
- return;
- }
-
- assert rawImage.bpp == 16;
-
- BufferedImage image;
-
- logger.info("Raw Image - height: " + rawImage.height + ", width: " + rawImage.width);
-
- if (landscape) {
- // convert raw data to an Image
- image = new BufferedImage(rawImage.height, rawImage.width,
- BufferedImage.TYPE_INT_ARGB);
-
- byte[] buffer = rawImage.data;
- int index = 0;
- for (int y = 0 ; y < rawImage.height ; y++) {
- for (int x = 0 ; x < rawImage.width ; x++) {
-
- int value = buffer[index++] & 0x00FF;
- value |= (buffer[index++] << 8) & 0x0FF00;
-
- int r = ((value >> 11) & 0x01F) << 3;
- int g = ((value >> 5) & 0x03F) << 2;
- int b = ((value >> 0) & 0x01F) << 3;
-
- value = 0xFF << 24 | r << 16 | g << 8 | b;
-
- image.setRGB(y, rawImage.width - x - 1, value);
- }
- }
- } else {
- // convert raw data to an Image
- image = new BufferedImage(rawImage.width, rawImage.height,
- BufferedImage.TYPE_INT_ARGB);
-
- byte[] buffer = rawImage.data;
- int index = 0;
- for (int y = 0 ; y < rawImage.height ; y++) {
- for (int x = 0 ; x < rawImage.width ; x++) {
-
- int value = buffer[index++] & 0x00FF;
- value |= (buffer[index++] << 8) & 0x0FF00;
-
- int r = ((value >> 11) & 0x01F) << 3;
- int g = ((value >> 5) & 0x03F) << 2;
- int b = ((value >> 0) & 0x01F) << 3;
-
- value = 0xFF << 24 | r << 16 | g << 8 | b;
-
- image.setRGB(x, y, value);
- }
- }
+ /**
+ * Tap function for scripts to call at a particular x and y location
+ *
+ * @param x x-coordinate
+ * @param y y-coordinate
+ */
+ public static boolean tap(int x, int y) throws IOException {
+ return MONKEY_MANANGER.tap(x, y);
}
- if (!ImageIO.write(image, "png", new File(filepath))) {
- recordResponse("No png writer", "");
- throw new IOException("Failed to find png writer");
+ /**
+ * Press function for scripts to call on a particular button or key
+ *
+ * @param key key to press
+ */
+ public static boolean press(String key) throws IOException {
+ return press(key, true);
}
- recordResponse("OK", filepath);
- }
- private static void printUsageAndQuit(String message) {
- // 80 cols marker: 01234567890123456789012345678901234567890123456789012345678901234567890123456789
- System.out.println(message);
- System.out.println("Usage: monkeyrunner [options] SCRIPT_FILE");
- System.out.println("");
- System.out.println(" -s MonkeyServer IP Address.");
- System.out.println(" -p MonkeyServer TCP Port.");
- System.out.println(" -v MonkeyServer Logging level (ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, OFF)");
- System.out.println("");
- System.out.println("");
-
- System.exit(1);
- }
-
- private static void printAndExit(String message, boolean terminate) {
- System.out.println(message);
- if (terminate) {
- AndroidDebugBridge.terminate();
+ /**
+ * Press function for scripts to call on a particular button or key
+ *
+ * @param key key to press
+ * @param print whether to send output to user
+ */
+ private static boolean press(String key, boolean print) throws IOException {
+ return MONKEY_MANANGER.press(key);
}
- System.exit(1);
- }
+
+ /**
+ * dpad down function
+ */
+ public static boolean down() throws IOException {
+ return press("dpad_down");
+ }
+
+ /**
+ * dpad up function
+ */
+ public static boolean up() throws IOException {
+ return press("dpad_up");
+ }
+
+ /**
+ * Function to type text on the device
+ *
+ * @param text text to type
+ */
+ public static boolean type(String text) throws IOException {
+ return MONKEY_MANANGER.type(text);
+ }
}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
new file mode 100644
index 0000000..cc813a1
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.monkeyrunner;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.IDevice;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * MonkeyRunner is a host side application to control a monkey instance on a
+ * device. MonkeyRunner provides some useful helper functions to control the
+ * device as well as various other methods to help script tests. This class bootstraps
+ * MonkeyRunner.
+ */
+public class MonkeyRunnerStarter {
+ private static final Logger LOG = Logger.getLogger(MonkeyRunnerStarter.class.getName());
+ // delay between key events
+ private static final int KEY_INPUT_DELAY = 1000;
+
+ private final MonkeyManager monkeyManager;
+ private final File scriptFile;
+
+ public MonkeyRunnerStarter(MonkeyManager monkeyManager,
+ File scriptFile) {
+ this.monkeyManager = monkeyManager;
+ this.scriptFile = scriptFile;
+ }
+
+ private void run() throws IOException {
+ MonkeyRunner.MONKEY_MANANGER = monkeyManager;
+ MonkeyRunner.start_script();
+ ScriptRunner.run(scriptFile.getAbsolutePath());
+ MonkeyRunner.end_script();
+ monkeyManager.close();
+ }
+
+ public static void main(String[] args) throws IOException {
+ // haven't figure out how to get below INFO...bad parent. Pass -v INFO to turn on logging
+ LOG.setLevel(Level.parse("WARNING"));
+ MonkeyRunningOptions options = MonkeyRunningOptions.processOptions(args);
+
+ if (options == null) {
+ return;
+ }
+
+ MonkeyRunnerStarter runner = new MonkeyRunnerStarter(initAdbConnection(),
+ options.getScriptFile());
+ runner.run();
+ System.exit(0);
+ }
+
+ /**
+ * Initialize an adb session with a device connected to the host
+ *
+ * @return a monkey manager.
+ */
+ private static MonkeyManager initAdbConnection() {
+ String adbLocation = "adb";
+ boolean device = false;
+ boolean emulator = false;
+ String serial = null;
+
+ AndroidDebugBridge.init(false /* debugger support */);
+
+ AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(
+ adbLocation, true /* forceNewBridge */);
+
+ // we can't just ask for the device list right away, as the internal thread getting
+ // them from ADB may not be done getting the first list.
+ // Since we don't really want getDevices() to be blocking, we wait here manually.
+ int count = 0;
+ while (bridge.hasInitialDeviceList() == false) {
+ try {
+ Thread.sleep(100);
+ count++;
+ } catch (InterruptedException e) {
+ // pass
+ }
+
+ // let's not wait > 10 sec.
+ if (count > 100) {
+ System.err.println("Timeout getting device list!");
+ return null;
+ }
+ }
+
+ // now get the devices
+ IDevice[] devices = bridge.getDevices();
+
+ if (devices.length == 0) {
+ printAndExit("No devices found!", true /* terminate */);
+ }
+
+ IDevice monkeyDevice = null;
+
+ if (emulator || device) {
+ for (IDevice d : devices) {
+ // this test works because emulator and device can't both be true at the same
+ // time.
+ if (d.isEmulator() == emulator) {
+ // if we already found a valid target, we print an error and return.
+ if (monkeyDevice != null) {
+ if (emulator) {
+ printAndExit("Error: more than one emulator launched!",
+ true /* terminate */);
+ } else {
+ printAndExit("Error: more than one device connected!",
+ true /* terminate */);
+ }
+ }
+ monkeyDevice = d;
+ }
+ }
+ } else if (serial != null) {
+ for (IDevice d : devices) {
+ if (serial.equals(d.getSerialNumber())) {
+ monkeyDevice = d;
+ break;
+ }
+ }
+ } else {
+ if (devices.length > 1) {
+ printAndExit("Error: more than one emulator or device available!",
+ true /* terminate */);
+ }
+ monkeyDevice = devices[0];
+ }
+
+ return new MonkeyManager(monkeyDevice);
+ }
+
+ private static void printAndExit(String message, boolean terminate) {
+ System.out.println(message);
+ if (terminate) {
+ AndroidDebugBridge.terminate();
+ }
+ System.exit(1);
+ }
+}
\ No newline at end of file
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunningOptions.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunningOptions.java
new file mode 100644
index 0000000..42ba3e4
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunningOptions.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import java.io.File;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class MonkeyRunningOptions {
+ private static final Logger LOG = Logger.getLogger(MonkeyRunningOptions.class.getName());
+ private static String DEFAULT_MONKEY_SERVER_ADDRESS = "127.0.0.1";
+ private static int DEFAULT_MONKEY_PORT = 12345;
+
+ private final int port;
+ private final String hostname;
+ private final File scriptFile;
+
+ private MonkeyRunningOptions(String hostname, int port, File scriptFile) {
+ this.hostname = hostname;
+ this.port = port;
+ this.scriptFile = scriptFile;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public File getScriptFile() {
+ return scriptFile;
+ }
+
+ private static void printUsage(String message) {
+ System.out.println(message);
+ System.out.println("Usage: monkeyrunner [options] SCRIPT_FILE");
+ System.out.println("");
+ System.out.println(" -s MonkeyServer IP Address.");
+ System.out.println(" -p MonkeyServer TCP Port.");
+ System.out.println(" -v MonkeyServer Logging level (ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, OFF)");
+ System.out.println("");
+ System.out.println("");
+ }
+
+ /**
+ * Process the command-line options
+ *
+ * @return the parsed options, or null if there was an error.
+ */
+ public static MonkeyRunningOptions processOptions(String[] args) {
+ // parse command line parameters.
+ int index = 0;
+
+ String hostname = DEFAULT_MONKEY_SERVER_ADDRESS;
+ File scriptFile = null;
+ int port = DEFAULT_MONKEY_PORT;
+
+ do {
+ String argument = args[index++];
+
+ if ("-s".equals(argument)) {
+ if (index == args.length) {
+ printUsage("Missing Server after -s");
+ return null;
+ }
+ hostname = args[index++];
+
+ } else if ("-p".equals(argument)) {
+ // quick check on the next argument.
+ if (index == args.length) {
+ printUsage("Missing Server port after -p");
+ return null;
+ }
+ port = Integer.parseInt(args[index++]);
+
+ } else if ("-v".equals(argument)) {
+ // quick check on the next argument.
+ if (index == args.length) {
+ printUsage("Missing Log Level after -v");
+ return null;
+ }
+
+ Level level = Level.parse(args[index++]);
+ LOG.setLevel(level);
+ level = LOG.getLevel();
+ System.out.println("Log level set to: " + level + "(" + level.intValue() + ").");
+ System.out.println("Warning: Log levels below INFO(800) not working currently... parent issues");
+
+ } else if (argument.startsWith("-")) {
+ // we have an unrecognized argument.
+ printUsage("Unrecognized argument: " + argument + ".");
+ return null;
+ } else {
+ // get the filepath of the script to run. This will be the last undashed argument.
+ scriptFile = new File(argument);
+ if (!scriptFile.exists()) {
+ printUsage("Can't open specified script file");
+ return null;
+ }
+ }
+ } while (index < args.length);
+
+ if (scriptFile == null) {
+ printUsage("Missing required parameter");
+ return null;
+ }
+
+ return new MonkeyRunningOptions(hostname, port, scriptFile);
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/PhysicalButton.java b/tools/monkeyrunner/src/com/android/monkeyrunner/PhysicalButton.java
new file mode 100644
index 0000000..f0525a0
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/PhysicalButton.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+public enum PhysicalButton {
+ HOME("home"),
+ SEARCH("search"),
+ MENU("menu"),
+ BACK("back"),
+ DPAD_UP("DPAD_UP"),
+ DPAD_DOWN("DPAD_DOWN"),
+ DPAD_LEFT("DPAD_LEFT"),
+ DPAD_RIGHT("DPAD_RIGHT"),
+ DPAD_CENTER("DPAD_CENTER"),
+ ENTER("enter");
+
+ private String keyName;
+
+ private PhysicalButton(String keyName) {
+ this.keyName = keyName;
+ }
+
+ public String getKeyName() {
+ return keyName;
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
index 6a4405b..bbed437 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
@@ -1,14 +1,25 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.monkeyrunner;
-import org.python.core.Py;
import org.python.core.PyObject;
-import org.python.util.PythonInterpreter;
import org.python.util.InteractiveConsole;
+import org.python.util.PythonInterpreter;
-import java.io.File;
import java.io.IOException;
-import java.io.FileInputStream;
-import java.lang.RuntimeException;
import java.util.Properties;
@@ -17,80 +28,80 @@
*/
public class ScriptRunner {
- /** The "this" scope object for scripts. */
- private final Object scope;
- private final String variable;
-
- /** Private constructor. */
- private ScriptRunner(Object scope, String variable) {
- this.scope = scope;
- this.variable = variable;
- }
-
- /** Creates a new instance for the given scope object. */
- public static ScriptRunner newInstance(Object scope, String variable) {
- return new ScriptRunner(scope, variable);
- }
+ /** The "this" scope object for scripts. */
+ private final Object scope;
+ private final String variable;
- /**
- * Runs the specified Jython script. First runs the initialization script to
- * preload the appropriate client library version.
- */
- public static void run(String scriptfilename) {
- try {
- initPython();
- PythonInterpreter python = new PythonInterpreter();
-
- python.execfile(scriptfilename);
- } catch(Exception e) {
- e.printStackTrace();
+ /** Private constructor. */
+ private ScriptRunner(Object scope, String variable) {
+ this.scope = scope;
+ this.variable = variable;
}
- }
-
- /** Initialize the python interpreter. */
- private static void initPython() {
- Properties props = new Properties();
- // Default is 'message' which displays sys-package-mgr bloat
- // Choose one of error,warning,message,comment,debug
- props.setProperty("python.verbose", "error");
- props.setProperty("python.path", System.getProperty("java.class.path"));
- PythonInterpreter.initialize(System.getProperties(), props, new String[] {""});
- }
+ /** Creates a new instance for the given scope object. */
+ public static ScriptRunner newInstance(Object scope, String variable) {
+ return new ScriptRunner(scope, variable);
+ }
- /**
- * Create and run a console using a new python interpreter for the test
- * associated with this instance.
- */
- public void console() throws IOException {
- initPython();
- InteractiveConsole python = new InteractiveConsole();
- initInterpreter(python, scope, variable);
- python.interact();
- }
+ /**
+ * Runs the specified Jython script. First runs the initialization script to
+ * preload the appropriate client library version.
+ */
+ public static void run(String scriptfilename) {
+ try {
+ initPython();
+ PythonInterpreter python = new PythonInterpreter();
- /**
- * Start an interactive python interpreter using the specified set of local
- * variables. Use this to interrupt a running test script with a prompt:
- *
- * @param locals
- */
- public static void console(PyObject locals) {
- initPython();
- InteractiveConsole python = new InteractiveConsole(locals);
- python.interact();
- }
+ python.execfile(scriptfilename);
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ }
- /**
- * Initialize a python interpreter.
- *
- * @param python
- * @param scope
- * @throws IOException
- */
- public static void initInterpreter(PythonInterpreter python, Object scope, String variable)
- throws IOException {
- // Store the current test case as the this variable
- python.set(variable, scope);
- }
+
+ /** Initialize the python interpreter. */
+ private static void initPython() {
+ Properties props = new Properties();
+ // Default is 'message' which displays sys-package-mgr bloat
+ // Choose one of error,warning,message,comment,debug
+ props.setProperty("python.verbose", "error");
+ props.setProperty("python.path", System.getProperty("java.class.path"));
+ PythonInterpreter.initialize(System.getProperties(), props, new String[] {""});
+ }
+
+ /**
+ * Create and run a console using a new python interpreter for the test
+ * associated with this instance.
+ */
+ public void console() throws IOException {
+ initPython();
+ InteractiveConsole python = new InteractiveConsole();
+ initInterpreter(python, scope, variable);
+ python.interact();
+ }
+
+ /**
+ * Start an interactive python interpreter using the specified set of local
+ * variables. Use this to interrupt a running test script with a prompt:
+ *
+ * @param locals
+ */
+ public static void console(PyObject locals) {
+ initPython();
+ InteractiveConsole python = new InteractiveConsole(locals);
+ python.interact();
+ }
+
+ /**
+ * Initialize a python interpreter.
+ *
+ * @param python
+ * @param scope
+ * @throws IOException
+ */
+ public static void initInterpreter(PythonInterpreter python, Object scope, String variable)
+ throws IOException {
+ // Store the current test case as the this variable
+ python.set(variable, scope);
+ }
}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/SixteenBitColorModel.java b/tools/monkeyrunner/src/com/android/monkeyrunner/SixteenBitColorModel.java
new file mode 100644
index 0000000..8653d22
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/SixteenBitColorModel.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import com.android.ddmlib.RawImage;
+
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+
+/**
+ * Internal color model used to do conversion of 16bpp RawImages.
+ */
+class SixteenBitColorModel extends ColorModel {
+ private static final int[] BITS = {
+ 8, 8, 8, 8
+ };
+ public SixteenBitColorModel(RawImage rawImage) {
+ super(32
+ , BITS, ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ true, false, Transparency.TRANSLUCENT,
+ DataBuffer.TYPE_BYTE);
+ }
+
+ @Override
+ public boolean isCompatibleRaster(Raster raster) {
+ return true;
+ }
+
+ private int getPixel(Object inData) {
+ byte[] data = (byte[]) inData;
+ int value = data[0] & 0x00FF;
+ value |= (data[1] << 8) & 0x0FF00;
+
+ return value;
+ }
+
+ @Override
+ public int getAlpha(Object inData) {
+ return 0xff;
+ }
+
+ @Override
+ public int getBlue(Object inData) {
+ int pixel = getPixel(inData);
+ return ((pixel >> 0) & 0x01F) << 3;
+ }
+
+ @Override
+ public int getGreen(Object inData) {
+ int pixel = getPixel(inData);
+ return ((pixel >> 5) & 0x03F) << 2;
+ }
+
+ @Override
+ public int getRed(Object inData) {
+ int pixel = getPixel(inData);
+ return ((pixel >> 11) & 0x01F) << 3;
+ }
+
+ @Override
+ public int getAlpha(int pixel) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getBlue(int pixel) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getGreen(int pixel) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getRed(int pixel) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/ThirtyTwoBitColorModel.java b/tools/monkeyrunner/src/com/android/monkeyrunner/ThirtyTwoBitColorModel.java
new file mode 100644
index 0000000..3099a8b
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/ThirtyTwoBitColorModel.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import com.android.ddmlib.RawImage;
+
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+
+/**
+ * Internal color model used to do conversion of 32bpp RawImages.
+ */
+class ThirtyTwoBitColorModel extends ColorModel {
+ private static final int[] BITS = {
+ 8, 8, 8, 8,
+ };
+ private final int alphaLength;
+ private final int alphaMask;
+ private final int alphaOffset;
+ private final int blueMask;
+ private final int blueLength;
+ private final int blueOffset;
+ private final int greenMask;
+ private final int greenLength;
+ private final int greenOffset;
+ private final int redMask;
+ private final int redLength;
+ private final int redOffset;
+
+ public ThirtyTwoBitColorModel(RawImage rawImage) {
+ super(32, BITS, ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ true, false, Transparency.TRANSLUCENT,
+ DataBuffer.TYPE_BYTE);
+
+ redOffset = rawImage.red_offset;
+ redLength = rawImage.red_length;
+ redMask = ImageUtils.getMask(redLength);
+ greenOffset = rawImage.green_offset;
+ greenLength = rawImage.green_length;
+ greenMask = ImageUtils.getMask(greenLength);
+ blueOffset = rawImage.blue_offset;
+ blueLength = rawImage.blue_length;
+ blueMask = ImageUtils.getMask(blueLength);
+ alphaLength = rawImage.alpha_length;
+ alphaOffset = rawImage.alpha_offset;
+ alphaMask = ImageUtils.getMask(alphaLength);
+ }
+
+ @Override
+ public boolean isCompatibleRaster(Raster raster) {
+ return true;
+ }
+
+ private int getPixel(Object inData) {
+ byte[] data = (byte[]) inData;
+ int value = data[0] & 0x00FF;
+ value |= (data[1] & 0x00FF) << 8;
+ value |= (data[2] & 0x00FF) << 16;
+ value |= (data[3] & 0x00FF) << 24;
+
+ return value;
+ }
+
+ @Override
+ public int getAlpha(Object inData) {
+ int pixel = getPixel(inData);
+ if(alphaLength == 0) {
+ return 0xff;
+ }
+ return ((pixel >>> alphaOffset) & alphaMask) << (8 - alphaLength);
+ }
+
+ @Override
+ public int getBlue(Object inData) {
+ int pixel = getPixel(inData);
+ return ((pixel >>> blueOffset) & blueMask) << (8 - blueLength);
+ }
+
+ @Override
+ public int getGreen(Object inData) {
+ int pixel = getPixel(inData);
+ return ((pixel >>> greenOffset) & greenMask) << (8 - greenLength);
+ }
+
+ @Override
+ public int getRed(Object inData) {
+ int pixel = getPixel(inData);
+ return ((pixel >>> redOffset) & redMask) << (8 - redLength);
+ }
+
+ @Override
+ public int getAlpha(int pixel) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getBlue(int pixel) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getGreen(int pixel) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getRed(int pixel) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/VariableFrame.java b/tools/monkeyrunner/src/com/android/monkeyrunner/VariableFrame.java
new file mode 100644
index 0000000..acc29c1
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/VariableFrame.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import com.google.common.collect.Sets;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JTable;
+import javax.swing.SwingUtilities;
+import javax.swing.WindowConstants;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Swing Frame that displays all the variables that the monkey exposes on the device.
+ */
+public class VariableFrame extends JFrame {
+ private static final Logger LOG = Logger.getLogger(VariableFrame.class.getName());
+ private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool();
+ private MonkeyManager monkeyManager;
+
+ private static class VariableHolder implements Comparable<VariableHolder> {
+ private final String key;
+ private final String value;
+
+ public VariableHolder(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public int compareTo(VariableHolder o) {
+ return key.compareTo(o.key);
+ }
+ }
+
+ private static <E> E getNthElement(Set<E> set, int offset) {
+ int current = 0;
+ for (E elem : set) {
+ if (current == offset) {
+ return elem;
+ }
+ current++;
+ }
+ return null;
+ }
+
+ private class VariableTableModel extends AbstractTableModel {
+ private final TreeSet<VariableHolder> set = Sets.newTreeSet();
+
+ public void refresh() {
+ Collection<String> variables;
+ try {
+ variables = monkeyManager.listVariable();
+ } catch (IOException e) {
+ LOG.log(Level.SEVERE, "Error getting list of variables", e);
+ return;
+ }
+ for (final String variable : variables) {
+ EXECUTOR.execute(new Runnable() {
+ public void run() {
+ String value;
+ try {
+ value = monkeyManager.getVariable(variable);
+ } catch (IOException e) {
+ LOG.log(Level.SEVERE,
+ "Error getting variable value for " + variable, e);
+ return;
+ }
+ synchronized (set) {
+ set.add(new VariableHolder(variable, value));
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ VariableTableModel.this.fireTableDataChanged();
+ }
+ });
+
+ }
+ }
+ });
+ }
+ }
+
+ public int getColumnCount() {
+ return 2;
+ }
+
+ public int getRowCount() {
+ synchronized (set) {
+ return set.size();
+ }
+ }
+
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ VariableHolder nthElement;
+ synchronized (set) {
+ nthElement = getNthElement(set, rowIndex);
+ }
+ if (columnIndex == 0) {
+ return nthElement.getKey();
+ }
+ return nthElement.getValue();
+ }
+ }
+
+ public VariableFrame() {
+ super("Variables");
+ setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
+ setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
+
+ final VariableTableModel tableModel = new VariableTableModel();
+
+ JButton refreshButton = new JButton("Refresh");
+ add(refreshButton);
+ refreshButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ tableModel.refresh();
+ }
+ });
+
+
+ JTable table = new JTable(tableModel);
+ add(table);
+
+ tableModel.addTableModelListener(new TableModelListener() {
+ public void tableChanged(TableModelEvent e) {
+ pack();
+ }
+ });
+
+ this.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowOpened(WindowEvent e) {
+ tableModel.refresh();
+ }
+ });
+
+ pack();
+ }
+
+ public void init(MonkeyManager monkeyManager) {
+ this.monkeyManager = monkeyManager;
+ }
+}
diff --git a/tools/monkeyrunner/test/Android.mk b/tools/monkeyrunner/test/Android.mk
new file mode 100644
index 0000000..19a64ed
--- /dev/null
+++ b/tools/monkeyrunner/test/Android.mk
@@ -0,0 +1,23 @@
+#
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_MODULE := MonkeyRunnerTest
+LOCAL_JAVA_LIBRARIES := junit monkeyrunner ddmlib
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/ImageUtilsTest.java b/tools/monkeyrunner/test/com/android/monkeyrunner/ImageUtilsTest.java
new file mode 100644
index 0000000..5b1795d
--- /dev/null
+++ b/tools/monkeyrunner/test/com/android/monkeyrunner/ImageUtilsTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.monkeyrunner;
+
+import com.android.ddmlib.RawImage;
+import com.android.monkeyrunner.CaptureRawAndConvertedImage.MonkeyRunnerRawImage;
+
+import junit.framework.TestCase;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.WritableRaster;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+
+import javax.imageio.ImageIO;
+
+public class ImageUtilsTest extends TestCase {
+ private static BufferedImage createBufferedImage(String name) throws IOException {
+ InputStream is = ImageUtilsTest.class.getResourceAsStream(name);
+ BufferedImage img = ImageIO.read(is);
+ is.close();
+ return img;
+ }
+
+ private static RawImage createRawImage(String name) throws IOException, ClassNotFoundException {
+ ObjectInputStream is =
+ new ObjectInputStream(ImageUtilsTest.class.getResourceAsStream(name));
+ CaptureRawAndConvertedImage.MonkeyRunnerRawImage wrapper = (MonkeyRunnerRawImage) is.readObject();
+ is.close();
+ return wrapper.toRawImage();
+ }
+
+ /**
+ * Check that the two images will draw the same (ie. have the same pixels). This is different
+ * that BufferedImage.equals(), which also wants to check that they have the same ColorModel
+ * and other parameters.
+ *
+ * @param i1 the first image
+ * @param i2 the second image
+ * @return true if both images will draw the same (ie. have same pixels).
+ */
+ private static boolean checkImagesHaveSamePixels(BufferedImage i1, BufferedImage i2) {
+ if (i1.getWidth() != i2.getWidth()) {
+ return false;
+ }
+ if (i1.getHeight() != i2.getHeight()) {
+ return false;
+ }
+
+ for (int y = 0; y < i1.getHeight(); y++) {
+ for (int x = 0; x < i1.getWidth(); x++) {
+ int p1 = i1.getRGB(x, y);
+ int p2 = i2.getRGB(x, y);
+ if (p1 != p2) {
+ WritableRaster r1 = i1.getRaster();
+ WritableRaster r2 = i2.getRaster();
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public void testImageConversionOld() throws IOException, ClassNotFoundException {
+ RawImage rawImage = createRawImage("image1.raw");
+ BufferedImage convertedImage = ImageUtils.convertImage(rawImage);
+ BufferedImage correctConvertedImage = createBufferedImage("image1.png");
+
+ assertTrue(checkImagesHaveSamePixels(convertedImage, correctConvertedImage));
+ }
+
+ public void testImageConversionNew() throws IOException, ClassNotFoundException {
+ RawImage rawImage = createRawImage("image2.raw");
+ BufferedImage convertedImage = ImageUtils.convertImage(rawImage);
+ BufferedImage correctConvertedImage = createBufferedImage("image2.png");
+
+ assertTrue(checkImagesHaveSamePixels(convertedImage, correctConvertedImage));
+ }
+}
diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.png b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.png
new file mode 100644
index 0000000..cac80f4
--- /dev/null
+++ b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.png
Binary files differ
diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.raw b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.raw
new file mode 100644
index 0000000..a228793
--- /dev/null
+++ b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.raw
Binary files differ
diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.png b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.png
new file mode 100644
index 0000000..e6922f1
--- /dev/null
+++ b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.png
Binary files differ
diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.raw b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.raw
new file mode 100644
index 0000000..77333cb
--- /dev/null
+++ b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.raw
Binary files differ