Merge "Apply all Canvas transformations to ColorDrawable."
diff --git a/docs/html/guide/practices/design/responsiveness.jd b/docs/html/guide/practices/design/responsiveness.jd
index 8a4e7cf..9858e36 100644
--- a/docs/html/guide/practices/design/responsiveness.jd
+++ b/docs/html/guide/practices/design/responsiveness.jd
@@ -1,13 +1,31 @@
page.title=Designing for Responsiveness
@jd:body
-<p>It's possible to write code that wins every performance test in the world, but still sends users in a fiery rage when they try to use the application. These are the applications that aren't <em>responsive</em> enough — the ones that feel
-sluggish, hang or freeze for significant periods, or take too long to process
-input. </p>
+<div class="figure">
+<img src="{@docRoot}images/anr.png" alt="Screenshot of ANR dialog box" width="240" height="320"/>
+<p><strong>Figure 1.</strong> An ANR dialog displayed to the user.</p>
+</div>
-<p>In Android, the system guards against applications that are insufficiently responsive for a period of time by displaying a dialog to the user, called the Application Not Responding (ANR) dialog. The user can choose to let the application continue, but the user won't appreciate having to act on this dialog every time he or she uses your application. So it's important to design responsiveness into your application, so that the system never has cause to display an ANR to the user. </p>
+<p>It's possible to write code that wins every performance test in the world,
+but still sends users in a fiery rage when they try to use the application.
+These are the applications that aren't <em>responsive</em> enough — the
+ones that feel sluggish, hang or freeze for significant periods, or take too
+long to process input. </p>
-<p>Generally, the system displays an ANR if an application cannot respond to user input. For example, if an application blocks on some I/O operation (frequently a network access), then the main application thread won't be able to process incoming user input events. After a time, the system concludes that the application has hung, and displays the ANR to give the user the option to kill it.
+<p>In Android, the system guards against applications that are insufficiently
+responsive for a period of time by displaying a dialog to the user, called the
+Application Not Responding (ANR) dialog, shown at right in Figure 1. The user
+can choose to let the application continue, but the user won't appreciate having
+to act on this dialog every time he or she uses your application. It's critical
+to design responsiveness into your application, so that the system never has
+cause to display an ANR dialog to the user. </p>
+
+<p>Generally, the system displays an ANR if an application cannot respond to
+user input. For example, if an application blocks on some I/O operation
+(frequently a network access), then the main application thread won't be able to
+process incoming user input events. After a time, the system concludes that the
+application is frozen, and displays the ANR to give the user the option to kill
+it. </p>
<p>Similarly, if your application spends too much time building an elaborate in-memory
structure, or perhaps computing the next move in a game, the system will
@@ -15,31 +33,17 @@
sure these computations are efficient using the techniques above, but even the
most efficient code still takes time to run.</p>
-<p>In both of these cases, the fix is usually to create a child thread, and do
+<p>In both of these cases, the recommended approach is to create a child thread and do
most of your work there. This keeps the main thread (which drives the user
-interface event loop) running, and prevents the system from concluding your code
+interface event loop) running and prevents the system from concluding that your code
has frozen. Since such threading usually is accomplished at the class
level, you can think of responsiveness as a <em>class</em> problem. (Compare
this with basic performance, which was described above as a <em>method</em>-level
concern.)</p>
-<div class="sidebox-wrapper">
-<div class="sidebox">
-<img src="{@docRoot}images/anr.png" width="240" height="320" alt="Screenshot of ANR dialog box">
-<p style="margin-top:.5em;padding:.5em;">An ANR dialog displayed to the user.</p>
-</div>
-</div>
-
-<p>This document discusses how the Android system determines whether an application is
-not responding and provides guidelines for
-ensuring that your application is responsive. </p>
-
-<p>This document covers these topics: </p>
-<ul>
- <li><a href="#anr">What Triggers ANR?</a></li>
- <li><a href="#avoiding">How to Avoid ANR</a></li>
- <li><a href="#reinforcing">Reinforcing Responsiveness</a></li>
-</ul>
+<p>This document describes how the Android system determines whether an
+application is not responding and provides guidelines for ensuring that your
+application stays responsive. </p>
<h2 id="anr">What Triggers ANR?</h2>
@@ -48,8 +52,10 @@
for a particular application when it detects one of the following
conditions:</p>
<ul>
- <li>No response to an input event (e.g. key press, screen touch) within 5 seconds</li>
- <li>A {@link android.content.BroadcastReceiver BroadcastReceiver} hasn't finished executing within 10 seconds</li>
+ <li>No response to an input event (e.g. key press, screen touch)
+ within 5 seconds</li>
+ <li>A {@link android.content.BroadcastReceiver BroadcastReceiver}
+ hasn't finished executing within 10 seconds</li>
</ul>
<h2 id="avoiding">How to Avoid ANR</h2>
diff --git a/docs/html/guide/practices/screens_support.jd b/docs/html/guide/practices/screens_support.jd
index 2863fb2..11ad1b8 100644
--- a/docs/html/guide/practices/screens_support.jd
+++ b/docs/html/guide/practices/screens_support.jd
@@ -131,57 +131,79 @@
</dl>
-<h3 id="range">Range of Screens Supported</h3>
+<h3 id="range">Range of screens supported</h3>
-<p>Android 1.5 and earlier versions of the platform were designed to support a
-single screen configuration — HVGA (320x480) resolution on a 3.2" screen.
-Because the platform targeted just one screen, application developers could
-write their applications specifically for that screen, without needing to worry
-about how their applications would be displayed on other screens. </p>
-
-<p>Starting from Android 1.6, the platform adds support for multiple screen
+<p>Starting from Android 1.6, the platform provides support for multiple screen
sizes and resolutions, reflecting the many new types and sizes of devices on
-which the platform will run. This means that developers must design their
-applications for proper display on a range of devices and screens.</p>
+which the platform runs. If you are developing an application that will run
+on Android 1.6 or later, you can use the compatibility features of the Android
+platform to ensure that your application UI renders properly across the range of
+supported screen sizes and resolutions.</p>
-<p>To simplify the way application developers design their user interfaces for
-multiple devices, and to allow more devices to participate without impacting
+<p>To simplify the way that developers design their user interfaces for
+multiple devices and to allow more devices to participate without affecting
applications, the platform divides the range of actual supported screen sizes
and resolutions into:</p>
<ul>
<li>A set of three generalized sizes: <em>large</em>, <em>normal</em>, and <em>small</em>, and </li>
-<li>A set of three generalized densities: high (<em>hdpi</em>), medium (<em>mdpi</em>), and low (<em>ldpi</em>)
+<li>A set of three generalized densities: <em>hdpi</em> (high), <em>mdpi</em> (medium), and <em>ldpi</em> (low)
</ul>
-<!--<p>Applications use to these generalized sizesThe to let you apply custom UI
-and enable/disable functionality according to the generalized class of screen,
-rather than by the specific screen. When you are developing your application,
-you use these generalized sizes and densities and Applications can use these
-generalized sizes and densities to tell the platform I will do it or you do it.
-Or a combination of both. -->
-
<p>Applications can provide custom resources (primarily layouts) for any of the
-three generalized sizes, if needed, and they can also provide resources
-(primarily drawables such as images) for any of the three generalized densities.
-Applications do not need to work with the actual physical size or density of the
-device screen. At run time, the platform handles the loading of the correct size
-or density resources, based on the generalized size or density of the current
-device screen, and adapts them to the actual pixel map of the screen.</p>
+three generalized sizes and can provide resources (primarily drawables such as
+images) for any of the three generalized densities. Applications do not need to
+work with the actual physical size or density of the device screen. At run time,
+the platform handles the loading of the correct size or density resources, based
+on the generalized size or density of the current device screen, and adapts them
+to the actual pixel map of the screen.</p>
-<p>The table below lists some of the more common screens supported
-by Android and illustrates how the platform maps them to generalized screen
-configurations. Some devices use screens that are not specifically listed
-in the table — the platform maps those to the same set generalized
-screen configurations. </p>
+<p>The generalized size/density configurations are arranged around a
+baseline configuration that is assigned a size of <em>normal</em> and a density of
+<em>mdpi</em> (medium). All applications written for Android 1.5 or earlier are (by
+definition) designed for the baseline HVGA screen used on the T-Mobile G1 and
+similar devices, which is size <em>normal</em> and density
+<em>mdpi</em>.</p>
-<p class="table-caption" id="screens-table"><strong>Table 1.</strong> Examples of
-device screens supported by Android.</p>
+<p>Each generalized screen configuration spans a range of actual screen
+densities and physical sizes. For example, that means that multiple devices that
+report a screen size of <em>normal</em> might offer screens that differ slightly
+in actual size or aspect ratio. Similarly, devices that report a screen density
+of <em>hdpi</em> might offer screens with slightly different pixel densities.
+The platform makes these differences abstract, however — applications can
+offer UI designed for the generalized sizes and densities and let the system
+handle the actual rendering of the UI on the current device screen according to
+its characteristics. </p>
- <table id="screens-table" width="80%" style="margin-top:2em;">
+
+<img src="{@docRoot}images/screens_support/screens-ranges.png" />
+<p class="img-caption"><strong>Figure 1.</strong>
+Illustration of how the Android platform maps actual screen densities and sizes
+to generalized density and size configurations. </p>
+
+<p>Although the platform lets your application provide layouts and resources for
+generalized size-density configurations, you do not necessarily need to do write
+custom code or provide custom resources for each of the nine supported
+configurations. The platform provides robust compatibility features, described
+in the sections below, that can handle most of the work of rendering your
+application on any device screen, provided that you've implemented your
+application UI properly. For more information about how to implement a UI that
+renders properly across device screens and platform versions, see
+<a href="#screen-independence">Best Practices for Screen Independence</a>.</p>
+
+<p>To help you test your applications, the Android SDK includes emulator skins
+that replicate the sizes and densities of actual device screens on which your
+application is likely to run. You can also modify the default size and density
+of the emulator skins to replicate the characteristics of any specific
+screen.</p>
+
+<p class="table-caption" id="screens-table"><strong>Table 1.</strong> Screen
+sizes and densities of emulator skins included in the Android SDK.</p>
+
+ <table id="screens-table">
<tbody>
<tr>
- <td></td>
+ <td style="border:none"></td>
<td style="background-color:#f3f3f3">
<nobr>Low density (120), <em>ldpi</em></nobr>
</td>
@@ -196,10 +218,7 @@
<td style="background-color:#f3f3f3">
<em>Small</em> screen
</td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">QVGA (240x320), <nobr>2.6"-3.0" diagonal</nobr></li>
- </ul>
+ <td style="font-size:.9em;">QVGA (240x320)</td>
</td>
<td></td>
<td></td>
@@ -208,74 +227,29 @@
<td style="background-color:#f3f3f3">
<em>Normal</em> screen
</td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">WQVGA (240x400), <nobr>3.2"-3.5" diagonal</nobr></li>
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">FWQVGA (240x432), <nobr>3.5"-3.8" diagonal</nobr></li>
- </ul>
- </td>
- <td style="font-size:.9em;background-color:#FFE;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">HVGA (320x480), <nobr>3.0"-3.5" diagonal</nobr></li>
- </ul>
- </td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">WVGA (480x800), <nobr>3.3"-4.0" diagonal</nobr></li>
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">FWVGA (480x854), <nobr>3.5"-4.0" diagonal</nobr></li>
- </ul>
- </td>
+ <td style="font-size:.9em;">WQVGA400 (240x400)<br>WQVGA432 (240x432)</td>
+ <td style="font-size:.9em;">HVGA (320x480)</td>
+ <td style="font-size:.9em;">WVGA800 (480x800)<br>WVGA854 (480x854)</td>
</tr>
<tr>
<td style="background-color:#f3f3f3">
<em>Large</em> screen
</td>
<td></td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">WVGA (480x800), <nobr>4.8"-5.5" diagonal</nobr></li>
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">FWVGA (480x854), <nobr>5.0"-5.8" diagonal</nobr></li>
- </ul>
- </td>
+ <td style="font-size:.9em;">WVGA800* (480x800)<br>WVGA854* (480x854)</td>
<td></td>
</tr>
- </tbody>
- </table>
+ <tr>
+ <td colspan="4" style="border:none;font-size:90%;">* To emulate this
+ configuration, specify a custom density of 160 when
+ creating an AVD that uses a WVGA800 or WVGA854 skin.
+ </td>
+</table>
-<p class="caption" style="margin-top:1em;margin-bottom:1.5em;"> </p>
-
-<p>As shown above, the various screen configurations are arranged around a
-<em>baseline screen</em> that is assigned a size of "normal" and a density of
-"medium". The HVGA screen is used as the baseline because all applications
-written against Android 1.5 or earlier are (by definition) written for the HVGA
-screen used on the T-Mobile G1 and similar devices.</p>
-
-<!-- <p>Note that each screen configuration spans a range of actual resolutions
-and physical screen sizes. For example, the The baseline configuration spans a
-range of actual screen sizes — from 3.0" to 3.5" diagonal — all with
-the same HVGA resolution. That means that the actual pixel density of devices in
-a single screen configuration can vary. </p>
-
-Because differences in density can affect the displayed size of UI elements
-declared in pixels, the framework provides a density-independent pixel (dip)
-unit that applications can use to declare UI dimensions, letting the platform
-automatically handle the scaling to the actual pixel density of the screen. When
-UI dimensions are declared in dip, the result is that they are displayed at the
-same physical size on all screens in a given configuration. </p> -->
-
-<p>Although the platform currently supports the nine possible size-density
-configurations listed in the table, you do not necessarily need to create custom
-resources for each one of them. The platform provides robust compatibility
-features, described in the sections below, that can handle most of the work of
-rendering your application on the current device screen, provided that the UI is
-properly implemented. For more information, see <a
-href="#screen-independence">Best Practices for Screen Independence</a>.</p>
-
-<!--
<p>For an overview of the relative numbers of high (hdpi), medium (mdpi), and
-low (ldpi) density screens, see the <a
-href="{@docRoot}guide/resources/dashboard/screen-densities.html">Screen Densities dashboard</a>.</p>
--->
+low (ldpi) density screens in Android-powered devices available now, see the <a
+href="{@docRoot}resources/dashboard/screens.html">Screen Sizes and Densities</a> dashboard.</p>
+
<h3 id="support">How Android supports multiple screens</h3>
@@ -307,8 +281,8 @@
size-specific resources are <code>large</code>, <code>normal</code>, and
<code>small</code>, and those for density-specific resources are
<code>hdpi</code> (high), <code>mdpi</code> (medium), and <code>ldpi</code>
-(low). The qualifiers correspond to the generalized densities given in
-<a href="#range">Table 1</a>, above.</li>
+(low). The qualifiers correspond to the generalized densities described in
+<a href="#range">Range of screens supported</a>, above.</li>
<li>The platform also provides a
<a href="{@docRoot}guide/topics/manifest/supports-screens-element.html">
<code><supports-screens></code></a>
@@ -457,7 +431,7 @@
<div id=vi09 style=TEXT-ALIGN:left>
<img src="{@docRoot}images/screens_support/dip.png" style="padding-bottom:0;margin-bottom:0;" />
<p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0
-1em;"><strong>Figure 1.</strong> Examples of density independence on WVGA high
+1em;"><strong>Figure 2.</strong> Examples of density independence on WVGA high
density (left), HVGA medium density (center), and QVGA low density (right). </p>
</div>
@@ -479,7 +453,8 @@
display of your application on different classes of device screens, as listed
below. The <code>smallScreens</code>, <code>normalScreens</code>, and
<code>largeScreens</code> attributes correspond to the generalized screen sizes
-shown in <a href="#range">Table 1</a>, earlier in this document.</p>
+described in <a href="#range">Range of screens supported</a>, earlier in this
+document.</p>
<table id="vrr8">
<tr>
@@ -489,6 +464,12 @@
<th >
Description
</th>
+ <th>
+ Default value,<br><nobr>Android 1.5 and Lower</nobr>
+ </th>
+ <th>
+ Default value,<br><nobr>Android 1.6 and Higher</nobr>
+ </th>
</tr>
<tr>
<td>
@@ -497,10 +478,10 @@
<td>
Whether or not the application UI is designed for use on
<em>small</em> screens — "<code>true</code>" if it is, and
-"<code>false</code>" if not. See <a href="#defaults">Default values for
-attributes</a> for information about the assumed value of this attribute, if not
-declared.
+"<code>false</code>" if not. </p>
</td>
+<td>"<code>false</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
<tr>
<td>
@@ -509,8 +490,10 @@
<td>
Whether or not the application UI is designed for use on
<em>normal</em> screens — "<code>true</code>" if it is, and
-"<code>false</code>" if not. The default value is "<code>true</code>".
+"<code>false</code>" if not. The default value is always "<code>true</code>".
</td>
+<td>"<code>true</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
<tr>
<td>
@@ -519,10 +502,10 @@
<td>
Whether or not the application UI is designed for use on
<em>large</em> screens — "<code>true</code>" if it is, and
-"<code>false</code>" if not. See <a href="#defaults">Default values for
-attributes</a> for information about the assumed value of this attribute, if not
-declared.
+"<code>false</code>" if not.
</td>
+<td>"<code>false</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
<tr>
<td>
@@ -535,9 +518,13 @@
<ul>
<li>If set to "<code>true</code>", the platform disables its
density-compatibility features for all screen densities — specifically,
-the auto-scaling of absolute pixel units and math — and relies on the
-application to use density-independent pixel units and/or to manage the
-adaptation of pixel values according to density of the current screen. </li>
+the auto-scaling of absolute pixel units (<code>px</code>) and math — and
+relies on the application to use density-independent pixel units
+(<code>dp</code>) and/or math to manage the adaptation of pixel values according
+to density of the current screen. That is, as long as your application uses
+density-independent units (dp) for screen layout sizes, then it will perform
+properly on different densities when this attribute is set to
+"<code>true</code>".</li>
<li>If set to "<code>false</code>", the platform enables its
density-compatibility features for all screen densities. In this case, the
@@ -546,45 +533,73 @@
(160). The platform then transparently auto-scales the application's pixel units
and math as needed to match the actual device screen density. </li>
</ul>
- <p>See <a href="#defaults">Default values for attributes</a> for
-information about the assumed value of this attribute, if not declared.</p>
+<p>Note that the setting of this attribute affects density-compatibility only.
+It does not affect size-compatibility features such as display on a virtual
+baseline screen.</p>
</td>
+<td>"<code>false</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
</table>
<p>In general, when you declare a screen-size attribute
(<code>smallScreens</code>, <code>normalScreens</code>, or
-<code>largeScreens</code>) as "true", you are signaling to the platform that
-your application wants to manage its UI by itself, for all screen sizes, without
-the platform applying any size-compatibility behaviors (such as a virtual HVGA
-display area). If you declare a screen-size attribute as "false", you are
-signaling that your application is not designed for that screen size. The
-effects are conditioned by the screen size that your application does not
-support:</p>
-
+<code>largeScreens</code>) as "<code>true</code>", you are signaling to the
+platform that your application is designed to render properly on that screen
+size. As a result, the platform does not apply any size-compatibility features
+(such as a virtual HVGA display area). If you declare a screen-size attribute as
+"<code>false</code>", you are signaling that your application is <em>not</em>
+designed for that screen size. In this case, the platform <em>does</em> apply
+size-compatibility features, rendering the application in an HVGA baseline
+display area. If the current screen is larger than <em>normal</em> size, the
+platform renders the application in a virtual HVGA screen on the larger screen.
+See <a href="#compatibility-examples">Screen-Compatibility Examples</a> for an
+illustration of what an application looks like when displayed in a virtual HVGA
+screen.</p>
+
+<p>In other words, setting a <code><supports-screens></code> attribute to
+"<code>false</code>" tells the platform to enable it's compatibility features
+when displaying the application on a screen of that size <em>or any larger
+size</em>, if also disallowed. Otherwise, the platform gives the application a
+normal display area that can use the full device screen area, if
+appropriate.</p>
+
+<p>Android Market also makes use of the <code><supports-screens></code>
+attributes. It uses them to filter the application from devices whose screens
+are not supported by the application. Specifically, Android Market considers an
+application compatible with a device if the application supports a screen that
+is the same or smaller than the current device screen. Android Market filters
+the application if it disallows the device's screen size and does not support a
+smaller size. In general, Android does not provide downward size-compatibility
+features for applications.</p>
+
+<p>Here are some examples:</p>
+
<ul>
- <li>If you declare <code>largeScreens="false"</code>, your application can
-still be installed by users of devices with large screens. When run on a device
-with a large screen, this attribute value causes the platform to run the
-application in compatibility mode, rendering it in a baseline screen area
-(normal size, medium density) reserved on the larger screen. See
-<a href="#compatibility-examples">Screen-Compatibility Examples</a> for an
-illustration of what an application looks like when displayed in compatibility
-mode.</li>
- <li>If you declare <code>smallScreens="false"</code>, your application can
-still be installed by users of devices with small screens. However, this
-attribute value causes Android Market to filter your application from the list
-of applications available to such users. In effect, this prevents users from
-installing the application on small-screen devices. </li>
+ <li>Assume that you declare <code>smallScreens="false" normalScreens="true"
+largeScreens="false" </code> in your application's manifest. <p>Although the
+application is not designed for display on large screens, the platform can still
+run it successfully in <a href="#compatibility-examples">size-compatibility
+mode</a>. Android Market does not filter the application from devices
+<em>normal</em> and <em>large</em> size screens, but does filter it from
+<em>small</em> size screens, since the application provides no screen support at
+<em>small</em> size (and there is no smaller size).</p></li>
+
+ <li>Assume that you declare <code>smallScreens="false" normalScreens="false"
+largeScreens="true"</code> in your application's manifest. <p>Android Market
+filters the application from users of devices with <em>small</em> and
+<em>normal</em> size screens. In effect, this prevents such users from
+installing the application.</p></li>
</ul>
-<p>If you declare the <code>android:anyDensity</code> attribute as "true", you
-are signaling to the platform that your application wants to manage its UI by
-itself, for all screen densities, using the actual screen dimensions and pixels.
-In this case, the application must ensure that it declares its UI dimensions
-using density-independent pixels and scales any actual pixel values or math by
-the scaling factor available from
-{@link android.util.DisplayMetrics#density android.util.DisplayMetrics.density}.</p>
+<p>If you declare the <code>android:anyDensity</code> attribute as
+"<code>true</code>", you are signaling to the platform that your application is
+designed to display properly on any screen density. In this case, the
+application must ensure that it declares its UI dimensions using
+density-independent pixels (<code>dp</code>) and scales any absolute pixel
+values (<code>px</code>) or math by the scaling factor available from {@link
+android.util.DisplayMetrics#density android.util.DisplayMetrics.density}. See <a
+href="#dips-pels">Converting from dips to pixels</a> for an example.</p>
<p>Note that the setting of the <code>android:anyDensity</code> attribute does
not affect the platform's pre-scaling of drawable resources, such as bitmaps and
@@ -594,22 +609,22 @@
normal, and small screens in any densities.</p>
<pre><manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
+ ...
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
- android:resizable="true"
android:anyDensity="true" />
- </manifest>
+ ...
+</manifest>
</pre>
-
+<!-- android:resizeable="true" -->
<h4 id="defaults">
Default values for attributes
</h4>
<p>The default values for the <code><supports-screens></code> attributes
-differs, depending on the the value of the
+differ, depending on the the value of the
<a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><code>android:minSdkVersion</code></a>
attribute in the application's manifest, as well as on
the value of <code>android:targetSdkVersion</code>, if declared:</p>
@@ -618,19 +633,20 @@
<ul>
<li>
If <code>android:minSdkVersion</code> or
-<code>android:targetSdkVersion</code> is "3" (Android 1.5) or lower, the default
-value for everything except android:normalScreens is <code>false</code>. If you
-are primarily targeting pre-Android 1.6 platforms but also want to support other
-densities/screen sizes, you need to set the appropriate attributes to
-<code>true</code>.
+<code>android:targetSdkVersion</code> is "4" (Android 1.6) or higher, the
+default value for everything is "<code>true</code>". If your application uses
+APIs introduced in Android 1.6 or higher, but does not support specific screen
+densities and/or screen sizes, you need to explicitly set the appropriate
+attributes to "<code>false</code>".
</li>
<li>
- If <code>android:minSdkVersion</code> or
-<code>android:targetSdkVersion</code> is "4" (Android 1.6) or higher, the
-default value for everything is <code>true</code>. If your application
-requires <span style=BACKGROUND-COLOR:#ffffff>Android 1.6 </span>features,
-but does not support these densities and/or screen sizes, you need to set the
-appropriate attributes to <code>false</code>.
+ If <code>android:minSdkVersion</code> is declared with a value of "3"
+(Android 1.5) or lower <em>and</em> a <code>android:targetSdkVersion</code>
+attribute is <em>not</em> declared with a value of "4" or higher, the default
+value for all attributes except <code>android:normalScreens</code> is
+"<code>false</code>". If you are primarily targeting pre-Android 1.6 platforms
+but also want to support other densities/screen sizes, you need to explicitly
+set the appropriate attributes to "<code>true</code>".
</li>
<li>
Note that <code>android:normalScreens</code> always defaults to
@@ -646,8 +662,8 @@
of resources based on the characteristics of the screen on which your application
is running. You can use these qualifiers to provide size- and density-specific
resources in your application. For more information about the generalized sizes
-and densities that correspond to the qualifiers, see <a href="#range">Table
-1</a>, earlier in this document.</p>
+and densities that correspond to the qualifiers, see <a href="#range">Range
+of Screens Supported</a>, earlier in this document.</p>
<table>
<tr>
@@ -659,30 +675,29 @@
<tr>
<td rowspan="3">Size</td>
<td><code>small</code></td>
- <td>Resources for small screens, such as QVGA low density.</td>
+ <td>Resources designed for <em>small</em> size screens.</td>
</tr>
<tr>
<td><code>normal</code></td>
- <td>Resources for normal (baseline configuration) screens, such as T-Mobile
-G1/HTC Magic screen size, or equivalent.</td>
+ <td>Resources designed for <em>normal</em> size screens.</td>
</tr>
<tr>
<td><code>large</code></td>
-<td>Resources for large screens. Typical example is a tablet like device.</td>
+<td>Resources for <em>large</em> size screens.</td>
</tr>
<tr>
<td rowspan="4">Density</td>
<td><code>ldpi</code></td>
-<td>Low-density resources, for 100 to 140 dpi screens.</td>
+<td>Resources designed for low-density (<em>ldpi</em>) screens.</td>
</tr>
<tr>
<td><code>mdpi</code></td>
-<td>Medium-density resources for 140 to 180 dpi screens.</td>
+<td>Resources designed for medium-density (<em>mdpi</em>) screens.</td>
</tr>
<tr>
<td><code>hdpi</code></td>
-<td>High-density resources for 190 to 250 dpi screens.</td>
+<td>Resources designed for high-density (<em>hdpi</em>) screens.</td>
</tr>
<tr>
<td><code>nodpi</code></td>
@@ -747,8 +762,8 @@
<h2 id="screen-independence">Best practices for Screen Independence</h2>
<p>The objective of supporting multiple screens is to create an application that
-can run properly on any display and function properly on any of the screen
-configurations listed in <a href="#range">Table 1</a> earlier in this document.
+can run properly on any display and function properly on any of the generalized
+screen configurations supported by the platform.
</p>
<p>You can easily ensure that your application will display properly on
@@ -855,7 +870,7 @@
<div style="float: right;background-color:#fff;margin: 0;padding: 20px 0 20px 20px;">
<img src="{@docRoot}images/screens_support/scale-test.png" style="padding:0;margin:0;">
-<p class="caption" style="margin:0;padding:0;"><strong>Figure 2.</strong> Comparison of pre-scaled and auto-scaled bitmaps.</p>
+<p class="caption" style="margin:0;padding:0;"><strong>Figure 3.</strong> Comparison of pre-scaled and auto-scaled bitmaps.</p>
</div>
<p>Even with the size- and density-compatibility features that the platform
@@ -947,7 +962,7 @@
{@link android.graphics.Canvas Canvas} for more
information on auto-scaling.</p>
-<p>Figure 2, at right, demonstrates the results of the pre-scale and auto-scale
+<p>Figure 3, at right, demonstrates the results of the pre-scale and auto-scale
mechanisms when loading low (120), medium (160) and high (240) density bitmaps
on a baseline screen. The differences are subtle, because all of the bitmaps are
being scaled to match the current screen density, however the scaled bitmaps
@@ -1078,7 +1093,7 @@
<div id="f9.5" style="float:right;margin:0;padding:0;">
<img src="{@docRoot}images/screens_support/avds-config.png" style="padding:0;margin:0;">
- <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em;"><strong>Figure 3.</strong>
+ <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em;"><strong>Figure 4.</strong>
A typical set of AVDs for testing screens support.</p>
</div>
@@ -1143,52 +1158,29 @@
<p>Note that starting the emulator with the <code>-scale</code> option will
scale the entire emulator display, based on both the dpi of the skin and of your
-monitor. Using the default densities, the emulator skins included in the Android
-1.6 SDK will emulate the following screen sizes:</p>
-
-<ul>
- <li>
- QVGA, low density: 3.3"
- </li>
- <li>
- WQVGA, low density: 3.9"
- </li>
- <li>
- WQVGA432, low density: 4.1"
- </li>
- <li>
- HVGA, medium density: 3.6"
- </li>
- <li>
- WVGA800, high density: 3.9"
- </li>
- <li>
- WVGA854, high density: 4.1"
- </li>
-</ul>
+monitor. The default emulator skins included in the Android SDK are listed
+in <a href="#screens-table">Table 1</a>, earlier in this document.</p>
<div style="float: right;background-color:#fff;margin: 0;padding: 20px 0 20px 20px;width:520px;">
<img src="{@docRoot}images/screens_support/avd-density.png" style="padding:0;margin:0;">
- <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em; width:280px;"><strong>Figure 4.</strong>
+ <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em; width:280px;"><strong>Figure 5.</strong>
Resolution and density options that you can use, when creating an AVD using the AVD Manager.</p>
</div>
<p>You should also make sure to test your application on different physical
-screen sizes within a single size-density configuration. For example, according
-to <a href="#range">Table 1</a>, the minimum supported diagonal of QVGA is 2.8".
-To display this is on a 30" monitor you will need to adjust the value passed to
-<code>-scale</code> to 96*2.8/3.3 = 81dpi. You can also pass a float value to
-<code>-scale</code> to specify your own scaling factor:</p>
+screen sizes within a single size-density configuration. For example, to
+display this screen configuration on a 30" monitor you will need to adjust
+the value passed to <code>-scale</code> to 96*2.8/3.3 = 81dpi. You can also
+pass a float value to <code>-scale</code> to specify your own scaling factor:</p>
<pre>emulator -avd <name> -scale 0.6</pre>
<p>If you would like to test your application on a screen that uses a resolution
or density not supported by the built-in skins, you can either adjust an
-existing skin, or create an AVD
-that uses a custom resolution or density.</p>
+existing skin, or create an AVD that uses a custom resolution or density.</p>
<p>In the AVD Manager, you can specify a custom skin resolution or density in
-the Create New AVD dialog, as shown in Figure 4, at right.</p>
+the Create New AVD dialog, as shown in Figure 5, at right.</p>
<p>In the <code>android</code> tool, follow these steps to create an AVD with a
custom resolution or density:</p>
@@ -1203,9 +1195,9 @@
<li>To specify a custom density for the skin, answer "yes" when asked whether
you want to create a custom hardware profile for the new AVD.</li>
<li>Continue through the various profile settings until the tool asks you to
-specify "Abstracted LCD density" (<em>hw.lcd.density</em>). Consult <a
-href="#range">Table 1</a>, earlier in this document, and enter the appropriate
-value. For example, enter "160" to use medium density for the WVGA800 screen.</li>
+specify "Abstracted LCD density" (<em>hw.lcd.density</em>). Enter an appropriate
+value, such as "120" for a low-density screen, "160" for a medium density screen,
+or "240" for a high-density screen.</li>
<li>Set any other hardware options and complete the AVD creation.</li>
</ol>
diff --git a/docs/html/images/screens_support/screens-ranges.png b/docs/html/images/screens_support/screens-ranges.png
new file mode 100644
index 0000000..034ac34
--- /dev/null
+++ b/docs/html/images/screens_support/screens-ranges.png
Binary files differ
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 8e50d39..31c03ad 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -320,11 +320,17 @@
setAudioSource(extractor->getTrack(i));
haveAudio = true;
- sp<MetaData> fileMeta = extractor->getMetaData();
- int32_t loop;
- if (fileMeta != NULL
- && fileMeta->findInt32(kKeyAutoLoop, &loop) && loop != 0) {
- mFlags |= AUTO_LOOPING;
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
+ // Only do this for vorbis audio, none of the other audio
+ // formats even support this ringtone specific hack and
+ // retrieving the metadata on some extractors may turn out
+ // to be very expensive.
+ sp<MetaData> fileMeta = extractor->getMetaData();
+ int32_t loop;
+ if (fileMeta != NULL
+ && fileMeta->findInt32(kKeyAutoLoop, &loop) && loop != 0) {
+ mFlags |= AUTO_LOOPING;
+ }
}
}
@@ -481,6 +487,10 @@
if (eos) {
notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ if (mFlags & PREPARING) {
+ LOGV("cache has reached EOS, prepare is done.");
+ finishAsyncPrepare_l();
+ }
} else {
off_t size;
if (mDurationUs >= 0 && mCachedSource->getSize(&size) == OK) {
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 1f97bee..c4d2d4d 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -5466,7 +5466,7 @@
deletePackageLI(
pkgName, false,
dataDirExists ? PackageManager.DONT_DELETE_DATA : 0,
- res.removedInfo);
+ res.removedInfo, true);
}
}
}
@@ -5511,7 +5511,7 @@
// First delete the existing package while retaining the data directory
if (!deletePackageLI(pkgName, true, PackageManager.DONT_DELETE_DATA,
- res.removedInfo)) {
+ res.removedInfo, true)) {
// If the existing package was'nt successfully deleted
res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
deletedPkg = false;
@@ -5541,7 +5541,7 @@
deletePackageLI(
pkgName, true,
PackageManager.DONT_DELETE_DATA,
- res.removedInfo);
+ res.removedInfo, true);
}
// Since we failed to install the new package we need to restore the old
// package that we deleted.
@@ -6009,7 +6009,7 @@
synchronized (mInstallLock) {
res = deletePackageLI(packageName, deleteCodeAndResources,
- flags | REMOVE_CHATTY, info);
+ flags | REMOVE_CHATTY, info, true);
}
if(res && sendBroadCast) {
@@ -6070,7 +6070,7 @@
* delete a partially installed application.
*/
private void removePackageDataLI(PackageParser.Package p, PackageRemovedInfo outInfo,
- int flags) {
+ int flags, boolean writeSettings) {
String packageName = p.packageName;
if (outInfo != null) {
outInfo.removedPackage = packageName;
@@ -6123,8 +6123,10 @@
mSettings.mPreferredActivities.removeFilter(pa);
}
}
- // Save settings now
- mSettings.writeLP();
+ if (writeSettings) {
+ // Save settings now
+ mSettings.writeLP();
+ }
}
}
@@ -6132,7 +6134,7 @@
* Tries to delete system package.
*/
private boolean deleteSystemPackageLI(PackageParser.Package p,
- int flags, PackageRemovedInfo outInfo) {
+ int flags, PackageRemovedInfo outInfo, boolean writeSettings) {
ApplicationInfo applicationInfo = p.applicationInfo;
//applicable for non-partially installed applications only
if (applicationInfo == null) {
@@ -6164,7 +6166,8 @@
deleteCodeAndResources = false;
flags |= PackageManager.DONT_DELETE_DATA;
}
- boolean ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo);
+ boolean ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo,
+ writeSettings);
if (!ret) {
return false;
}
@@ -6185,13 +6188,16 @@
}
synchronized (mPackages) {
updatePermissionsLP(newPkg.packageName, newPkg, true, true, false);
- mSettings.writeLP();
+ if (writeSettings) {
+ mSettings.writeLP();
+ }
}
return true;
}
private boolean deleteInstalledPackageLI(PackageParser.Package p,
- boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo) {
+ boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo,
+ boolean writeSettings) {
ApplicationInfo applicationInfo = p.applicationInfo;
if (applicationInfo == null) {
Slog.w(TAG, "Package " + p.packageName + " has no applicationInfo.");
@@ -6202,7 +6208,7 @@
}
// Delete package data from internal structures and also remove data if flag is set
- removePackageDataLI(p, outInfo, flags);
+ removePackageDataLI(p, outInfo, flags, writeSettings);
// Delete application code and resources
if (deleteCodeAndResources) {
@@ -6219,7 +6225,8 @@
* This method handles package deletion in general
*/
private boolean deletePackageLI(String packageName,
- boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo) {
+ boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo,
+ boolean writeSettings) {
if (packageName == null) {
Slog.w(TAG, "Attempt to delete null packageName.");
return false;
@@ -6246,7 +6253,7 @@
if (dataOnly) {
// Delete application data first
- removePackageDataLI(p, outInfo, flags);
+ removePackageDataLI(p, outInfo, flags, writeSettings);
return true;
}
// At this point the package should have ApplicationInfo associated with it
@@ -6259,12 +6266,13 @@
Log.i(TAG, "Removing system package:"+p.packageName);
// When an updated system application is deleted we delete the existing resources as well and
// fall back to existing code in system partition
- ret = deleteSystemPackageLI(p, flags, outInfo);
+ ret = deleteSystemPackageLI(p, flags, outInfo, writeSettings);
} else {
Log.i(TAG, "Removing non-system package:"+p.packageName);
// Kill application pre-emptively especially for apps on sd.
killApplication(packageName, p.applicationInfo.uid);
- ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo);
+ ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo,
+ writeSettings);
}
return ret;
}
@@ -9739,7 +9747,7 @@
PackageRemovedInfo outInfo = new PackageRemovedInfo();
synchronized (mInstallLock) {
boolean res = deletePackageLI(pkgName, false,
- PackageManager.DONT_DELETE_DATA, outInfo);
+ PackageManager.DONT_DELETE_DATA, outInfo, false);
if (res) {
pkgList.add(pkgName);
} else {
@@ -9748,6 +9756,13 @@
}
}
}
+
+ synchronized (mPackages) {
+ // We didn't update the settings after removing each package;
+ // write them now for all packages.
+ mSettings.writeLP();
+ }
+
// We have to absolutely send UPDATED_MEDIA_STATUS only
// after confirming that all the receivers processed the ordered
// broadcast when packages get disabled, force a gc to clean things up.
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 717f63c..80dcc98 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -31,6 +31,7 @@
import android.content.ContentService;
import android.content.Context;
import android.content.pm.IPackageManager;
+import android.content.res.Configuration;
import android.database.ContentObserver;
import android.media.AudioService;
import android.os.Build;
@@ -44,6 +45,9 @@
import android.server.BluetoothA2dpService;
import android.server.BluetoothService;
import android.server.search.SearchManagerService;
+import android.view.Display;
+import android.view.WindowManager;
+import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -496,6 +500,16 @@
statusBar.systemReady();
}
wm.systemReady();
+
+ // Update the configuration for this context by hand, because we're going
+ // to start using it before the config change done in wm.systemReady() will
+ // propagate to it.
+ Configuration config = wm.computeNewConfiguration();
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ w.getDefaultDisplay().getMetrics(metrics);
+ context.getResources().updateConfiguration(config, metrics);
+
power.systemReady();
try {
pm.systemReady();
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index c59e20d..65a64cd 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -195,5 +195,22 @@
bridge will provide its own implementation.
+- References -
+--------------
+
+
+The JVM Specification 2nd edition:
+ http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html
+
+Understanding bytecode:
+ http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/
+
+Bytecode opcode list:
+ http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
+
+ASM user guide:
+ http://download.forge.objectweb.org/asm/asm-guide.pdf
+
+
--
end
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
new file mode 100644
index 0000000..9a48ea6
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes a method that has been converted to a delegate by layoutlib_create.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LayoutlibDelegate {
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index 7b55ed3e..590923f 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -28,9 +28,9 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
-import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
@@ -60,38 +60,55 @@
* old-FQCN to rename and they get erased as they get renamed. At the end, classes still
* left here are not in the code base anymore and thus were not renamed. */
private HashSet<String> mClassesNotRenamed;
- /** A map { FQCN => map { list of return types to delete from the FQCN } }. */
+ /** A map { FQCN => set { list of return types to delete from the FQCN } }. */
private HashMap<String, Set<String>> mDeleteReturns;
+ /** A map { FQCN => set { method names } } of methods to rewrite as delegates.
+ * The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
+ private final HashMap<String, Set<String>> mDelegateMethods;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
- *
+ *
* @param log Output logger.
* @param osDestJar The path of the destination JAR to create.
- * @param injectClasses The list of class from layoutlib_create to inject in layoutlib.
- * @param stubMethods The list of methods to stub out. Each entry must be in the form
- * "package.package.OuterClass$InnerClass#MethodName".
- * @param renameClasses The list of classes to rename, must be an even list: the binary FQCN
- * of class to replace followed by the new FQCN.
- * @param deleteReturns List of classes for which the methods returning them should be deleted.
- * The array contains a list of null terminated section starting with the name of the class
- * to rename in which the methods are deleted, followed by a list of return types identifying
- * the methods to delete.
+ * @param createInfo Creation parameters. Must not be null.
*/
- public AsmGenerator(Log log, String osDestJar,
- Class<?>[] injectClasses,
- String[] stubMethods,
- String[] renameClasses, String[] deleteReturns) {
+ public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
mLog = log;
mOsDestJar = osDestJar;
- mInjectClasses = injectClasses != null ? injectClasses : new Class<?>[0];
- mStubMethods = stubMethods != null ? new HashSet<String>(Arrays.asList(stubMethods)) :
- new HashSet<String>();
+ mInjectClasses = createInfo.getInjectedClasses();
+ mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
+
+ // Create the map/set of methods to change to delegates
+ mDelegateMethods = new HashMap<String, Set<String>>();
+ for (String signature : createInfo.getDelegateMethods()) {
+ int pos = signature.indexOf('#');
+ if (pos <= 0 || pos >= signature.length() - 1) {
+ continue;
+ }
+ String className = binaryToInternalClassName(signature.substring(0, pos));
+ String methodName = signature.substring(pos + 1);
+ Set<String> methods = mDelegateMethods.get(className);
+ if (methods == null) {
+ methods = new HashSet<String>();
+ mDelegateMethods.put(className, methods);
+ }
+ methods.add(methodName);
+ }
+ for (String className : createInfo.getDelegateClassNatives()) {
+ Set<String> methods = mDelegateMethods.get(className);
+ if (methods == null) {
+ methods = new HashSet<String>();
+ mDelegateMethods.put(className, methods);
+ }
+ methods.add(DelegateClassAdapter.ALL_NATIVES);
+ }
// Create the map of classes to rename.
mRenameClasses = new HashMap<String, String>();
mClassesNotRenamed = new HashSet<String>();
- int n = renameClasses == null ? 0 : renameClasses.length;
+ String[] renameClasses = createInfo.getRenamedClasses();
+ int n = renameClasses.length;
for (int i = 0; i < n; i += 2) {
assert i + 1 < n;
// The ASM class names uses "/" separators, whereas regular FQCN use "."
@@ -100,38 +117,37 @@
mRenameClasses.put(oldFqcn, newFqcn);
mClassesNotRenamed.add(oldFqcn);
}
-
+
// create the map of renamed class -> return type of method to delete.
mDeleteReturns = new HashMap<String, Set<String>>();
- if (deleteReturns != null) {
- Set<String> returnTypes = null;
- String renamedClass = null;
- for (String className : deleteReturns) {
- // if we reach the end of a section, add it to the main map
- if (className == null) {
- if (returnTypes != null) {
- mDeleteReturns.put(renamedClass, returnTypes);
- }
-
- renamedClass = null;
- continue;
+ String[] deleteReturns = createInfo.getDeleteReturns();
+ Set<String> returnTypes = null;
+ String renamedClass = null;
+ for (String className : deleteReturns) {
+ // if we reach the end of a section, add it to the main map
+ if (className == null) {
+ if (returnTypes != null) {
+ mDeleteReturns.put(renamedClass, returnTypes);
}
-
- // if the renamed class is null, this is the beginning of a section
- if (renamedClass == null) {
- renamedClass = binaryToInternalClassName(className);
- continue;
- }
-
- // just a standard return type, we add it to the list.
- if (returnTypes == null) {
- returnTypes = new HashSet<String>();
- }
- returnTypes.add(binaryToInternalClassName(className));
+
+ renamedClass = null;
+ continue;
}
+
+ // if the renamed class is null, this is the beginning of a section
+ if (renamedClass == null) {
+ renamedClass = binaryToInternalClassName(className);
+ continue;
+ }
+
+ // just a standard return type, we add it to the list.
+ if (returnTypes == null) {
+ returnTypes = new HashSet<String>();
+ }
+ returnTypes.add(binaryToInternalClassName(className));
}
}
-
+
/**
* Returns the list of classes that have not been renamed yet.
* <p/>
@@ -163,12 +179,12 @@
public void setDeps(Map<String, ClassReader> deps) {
mDeps = deps;
}
-
+
/** Gets the map of classes to output as-is, except if they have native methods */
public Map<String, ClassReader> getKeep() {
return mKeep;
}
-
+
/** Gets the map of dependencies that must be completely stubbed */
public Map<String, ClassReader> getDeps() {
return mDeps;
@@ -177,7 +193,7 @@
/** Generates the final JAR */
public void generate() throws FileNotFoundException, IOException {
TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
-
+
for (Class<?> clazz : mInjectClasses) {
String name = classToEntryPath(clazz);
InputStream is = ClassLoader.getSystemResourceAsStream(name);
@@ -186,7 +202,7 @@
name = classNameToEntryPath(transformName(cr.getClassName()));
all.put(name, b);
}
-
+
for (Entry<String, ClassReader> entry : mDeps.entrySet()) {
ClassReader cr = entry.getValue();
byte[] b = transform(cr, true /* stubNativesOnly */);
@@ -211,8 +227,8 @@
/**
* Writes the JAR file.
- *
- * @param outStream The file output stream were to write the JAR.
+ *
+ * @param outStream The file output stream were to write the JAR.
* @param all The map of all classes to output.
* @throws IOException if an I/O error has occurred
*/
@@ -236,7 +252,7 @@
String classNameToEntryPath(String className) {
return className.replaceAll("\\.", "/").concat(".class");
}
-
+
/**
* Utility method to get the JAR entry path from a Class name.
* e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
@@ -248,30 +264,32 @@
name = "$" + clazz.getSimpleName() + name;
clazz = parent;
}
- return classNameToEntryPath(clazz.getCanonicalName() + name);
+ return classNameToEntryPath(clazz.getCanonicalName() + name);
}
/**
* Transforms a class.
* <p/>
* There are 3 kind of transformations:
- *
+ *
* 1- For "mock" dependencies classes, we want to remove all code from methods and replace
* by a stub. Native methods must be implemented with this stub too. Abstract methods are
* left intact. Modified classes must be overridable (non-private, non-final).
* Native methods must be made non-final, non-private.
- *
+ *
* 2- For "keep" classes, we want to rewrite all native methods as indicated above.
* If a class has native methods, it must also be made non-private, non-final.
- *
+ *
* Note that unfortunately static methods cannot be changed to non-static (since static and
* non-static are invoked differently.)
*/
byte[] transform(ClassReader cr, boolean stubNativesOnly) {
boolean hasNativeMethods = hasNativeMethods(cr);
+
+ // Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass)
String className = cr.getClassName();
-
+
String newName = transformName(className);
// transformName returns its input argument if there's no need to rename the class
if (newName != className) {
@@ -288,13 +306,24 @@
// Rewrite the new class from scratch, without reusing the constant pool from the
// original class reader.
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
-
+
ClassVisitor rv = cw;
if (newName != className) {
rv = new RenameClassAdapter(cw, className, newName);
}
-
- TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
+
+ Set<String> delegateMethods = mDelegateMethods.get(className);
+ if (delegateMethods != null && !delegateMethods.isEmpty()) {
+ // If delegateMethods only contains one entry ALL_NATIVES and the class is
+ // known to have no native methods, just skip this step.
+ if (hasNativeMethods ||
+ !(delegateMethods.size() == 1 &&
+ delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) {
+ rv = new DelegateClassAdapter(mLog, rv, className, delegateMethods);
+ }
+ }
+
+ TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
mDeleteReturns.get(className),
newName, rv,
stubNativesOnly, stubNativesOnly || hasNativeMethods);
@@ -323,7 +352,7 @@
return newName + className.substring(pos);
}
}
-
+
return className;
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 2ed8641..92892784 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -16,11 +16,70 @@
package com.android.tools.layoutlib.create;
-public class CreateInfo {
+/**
+ * Describes the work to be done by {@link AsmGenerator}.
+ */
+public final class CreateInfo implements ICreateInfo {
+
+ /**
+ * Returns the list of class from layoutlib_create to inject in layoutlib.
+ * The list can be empty but must not be null.
+ */
+ public Class<?>[] getInjectedClasses() {
+ return INJECTED_CLASSES;
+ }
+
+ /**
+ * Returns the list of methods to rewrite as delegates.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDelegateMethods() {
+ return DELEGATE_METHODS;
+ }
+
+ /**
+ * Returns the list of classes on which to delegate all native methods.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDelegateClassNatives() {
+ return DELEGATE_CLASS_NATIVES;
+ }
+
+ /**
+ * Returns The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * The list can be empty but must not be null.
+ */
+ public String[] getOverriddenMethods() {
+ return OVERRIDDEN_METHODS;
+ }
+
+ /**
+ * Returns the list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ * The list can be empty but must not be null.
+ */
+ public String[] getRenamedClasses() {
+ return RENAMED_CLASSES;
+ }
+
+ /**
+ * Returns the list of classes for which the methods returning them should be deleted.
+ * The array contains a list of null terminated section starting with the name of the class
+ * to rename in which the methods are deleted, followed by a list of return types identifying
+ * the methods to delete.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDeleteReturns() {
+ return DELETE_RETURNS;
+ }
+
+ //-----
+
/**
* The list of class from layoutlib_create to inject in layoutlib.
*/
- public final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
+ private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
OverrideMethod.class,
MethodListener.class,
MethodAdapter.class,
@@ -28,19 +87,37 @@
};
/**
+ * The list of methods to rewrite as delegates.
+ */
+ private final static String[] DELEGATE_METHODS = new String[] {
+ // TODO: comment out once DelegateClass is working
+ // "android.view.View#isInEditMode",
+ // "android.content.res.Resources$Theme#obtainStyledAttributes",
+ };
+
+ /**
+ * The list of classes on which to delegate all native methods.
+ */
+ private final static String[] DELEGATE_CLASS_NATIVES = new String[] {
+ // TODO: comment out once DelegateClass is working
+ // "android.graphics.Paint"
+ };
+
+ /**
* The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName".
*/
- public final static String[] OVERRIDDEN_METHODS = new String[] {
- "android.view.View#isInEditMode",
- "android.content.res.Resources$Theme#obtainStyledAttributes",
- };
+ private final static String[] OVERRIDDEN_METHODS = new String[] {
+ // TODO: remove once DelegateClass is working
+ "android.view.View#isInEditMode",
+ "android.content.res.Resources$Theme#obtainStyledAttributes",
+ };
/**
* The list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
*/
- public final static String[] RENAMED_CLASSES =
+ private final static String[] RENAMED_CLASSES =
new String[] {
"android.graphics.Bitmap", "android.graphics._Original_Bitmap",
"android.graphics.BitmapFactory", "android.graphics._Original_BitmapFactory",
@@ -69,7 +146,7 @@
* to rename in which the methods are deleted, followed by a list of return types identifying
* the methods to delete.
*/
- public final static String[] REMOVED_METHODS =
+ private final static String[] DELETE_RETURNS =
new String[] {
"android.graphics.Paint", // class to delete methods from
"android.graphics.Paint$Align", // list of type identifying methods to delete
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
new file mode 100644
index 0000000..9cba8a0
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.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.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Set;
+
+/**
+ * A {@link DelegateClassAdapter} can transform some methods from a class into
+ * delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ */
+public class DelegateClassAdapter extends ClassAdapter {
+
+ public final static String ALL_NATIVES = "<<all_natives>>";
+
+ private final String mClassName;
+ private final Set<String> mDelegateMethods;
+ private final Log mLog;
+
+ /**
+ * Creates a new {@link DelegateClassAdapter} that can transform some methods
+ * from a class into delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ *
+ * @param log The logger object. Must not be null.
+ * @param cv the class visitor to which this adapter must delegate calls.
+ * @param className The internal class name of the class to visit,
+ * e.g. <code>com/android/SomeClass$InnerClass</code>.
+ * @param delegateMethods The set of method names to modify and/or the
+ * special constant {@link #ALL_NATIVES} to convert all native methods.
+ */
+ public DelegateClassAdapter(Log log,
+ ClassVisitor cv,
+ String className,
+ Set<String> delegateMethods) {
+ super(cv);
+ mLog = log;
+ mClassName = className;
+ mDelegateMethods = delegateMethods;
+ }
+
+ //----------------------------------
+ // Methods from the ClassAdapter
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+
+ boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
+
+ boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
+ mDelegateMethods.contains(name);
+
+ if (useDelegate) {
+ // remove native
+ access = access & ~Opcodes.ACC_NATIVE;
+ }
+
+ MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+ if (useDelegate) {
+ DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName,
+ name, desc, isStatic);
+ if (isNative) {
+ // A native has no code to visit, so we need to generate it directly.
+ a.generateCode();
+ } else {
+ return a;
+ }
+ }
+ return mw;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
new file mode 100644
index 0000000..21d6682
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * This method adapter rewrites a method by discarding the original code and generating
+ * a call to a delegate. Original annotations are passed along unchanged.
+ * <p/>
+ * Calls are delegated to a class named <code><className>_Delegate</code> with
+ * static methods matching the methods to be overridden here. The methods have the
+ * same return type. The argument type list is the same except the "this" reference is
+ * passed first for non-static methods.
+ * <p/>
+ * A new annotation is added.
+ * <p/>
+ * Note that native methods have, by definition, no code so there's nothing a visitor
+ * can visit. That means the caller must call {@link #generateCode()} directly for
+ * a native and use the visitor pattern for non-natives.
+ * <p/>
+ * Instances of this class are not re-usable. You need a new instance for each method.
+ */
+class DelegateMethodAdapter implements MethodVisitor {
+
+ /**
+ * Suffix added to delegate classes.
+ */
+ public static final String DELEGATE_SUFFIX = "_Delegate";
+
+ private static String CONSTRUCTOR = "<init>";
+ private static String CLASS_INIT = "<clinit>";
+
+ /** The parent method writer */
+ private MethodVisitor mParentVisitor;
+ /** Flag to output the first line number. */
+ private boolean mOutputFirstLineNumber = true;
+ /** The original method descriptor (return type + argument types.) */
+ private String mDesc;
+ /** True if the original method is static. */
+ private final boolean mIsStatic;
+ /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
+ private final String mClassName;
+ /** The method name. */
+ private final String mMethodName;
+ /** Logger object. */
+ private final Log mLog;
+ /** True if {@link #visitCode()} has been invoked. */
+ private boolean mVisitCodeCalled;
+
+ /**
+ * Creates a new {@link DelegateMethodAdapter} that will transform this method
+ * into a delegate call.
+ * <p/>
+ * See {@link DelegateMethodAdapter} for more details.
+ *
+ * @param log The logger object. Must not be null.
+ * @param mv the method visitor to which this adapter must delegate calls.
+ * @param className The internal class name of the class to visit,
+ * e.g. <code>com/android/SomeClass$InnerClass</code>.
+ * @param methodName The simple name of the method.
+ * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
+ * {@link Type#getArgumentTypes(String)})
+ * @param isStatic True if the method is declared static.
+ */
+ public DelegateMethodAdapter(Log log,
+ MethodVisitor mv,
+ String className,
+ String methodName,
+ String desc,
+ boolean isStatic) {
+ mLog = log;
+ mParentVisitor = mv;
+ mClassName = className;
+ mMethodName = methodName;
+ mDesc = desc;
+ mIsStatic = isStatic;
+
+ if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
+ // We're going to simplify by not supporting constructors.
+ // The only trick with a constructor is to find the proper super constructor
+ // and call it (and deciding if we should mirror the original method call to
+ // a custom constructor or call a default one.)
+ throw new UnsupportedOperationException(
+ String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",
+ className, methodName, desc));
+ }
+ }
+
+ /**
+ * Generates the new code for the method.
+ * <p/>
+ * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
+ * (since they have no code to visit).
+ * <p/>
+ * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
+ * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
+ * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
+ * this method will be invoked from {@link MethodVisitor#visitEnd()}.
+ */
+ public void generateCode() {
+ /*
+ * The goal is to generate a call to a static delegate method.
+ * If this method is not-static, the first parameter will be this.
+ * All the parameters must be passed and then the eventual return type returned.
+ *
+ * Example, let's say we have a method such as
+ * public void method_1(int a, Object b, ArrayList<String> c) { ... }
+ *
+ * We'll want to create a body that calls a delegate method like this:
+ * TheClass_Delegate.method_1(this, a, b, c);
+ *
+ * The generated class name is the current class name with "_Delegate" appended to it.
+ * One thing to realize is that we don't care about generics -- since generic types
+ * are erased at runtime, they have no influence on the method being called.
+ */
+
+ // Add our annotation
+ AnnotationVisitor aw = mParentVisitor.visitAnnotation(
+ Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
+ true); // visible at runtime
+ aw.visitEnd();
+
+ if (!mVisitCodeCalled) {
+ // If this is a direct call to generateCode() as done by DelegateClassAdapter
+ // for natives, visitCode() hasn't been called yet.
+ mParentVisitor.visitCode();
+ mVisitCodeCalled = true;
+ }
+
+ int numVars = 0;
+
+ // Push "this" for an instance method, which is always ALOAD 0
+ if (!mIsStatic) {
+ mParentVisitor.visitVarInsn(Opcodes.ALOAD, numVars++);
+ }
+
+ // Push all other arguments
+ Type[] argTypes = Type.getArgumentTypes(mDesc);
+ for (Type t : argTypes) {
+ int size = t.getSize();
+ mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), numVars);
+ numVars += size;
+ }
+
+ // Construct the descriptor of the delegate. For a static method, it's the same
+ // however for an instance method we need to pass the 'this' reference first
+ String desc = mDesc;
+ if (!mIsStatic && argTypes.length > 0) {
+ Type[] argTypes2 = new Type[argTypes.length + 1];
+
+ argTypes2[0] = Type.getObjectType(mClassName);
+ System.arraycopy(argTypes, 0, argTypes2, 1, argTypes.length);
+
+ desc = Type.getMethodDescriptor(Type.getReturnType(mDesc), argTypes2);
+ }
+
+ String delegateClassName = mClassName + DELEGATE_SUFFIX;
+
+ // Invoke the static delegate
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ delegateClassName,
+ mMethodName,
+ desc);
+
+ Type returnType = Type.getReturnType(mDesc);
+ mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+
+ mParentVisitor.visitMaxs(numVars, numVars);
+ mParentVisitor.visitEnd();
+
+ // For debugging now. Maybe we should collect these and store them in
+ // a text file for helping create the delegates. We could also compare
+ // the text file to a golden and break the build on unsupported changes
+ // or regressions. Even better we could fancy-print something that looks
+ // like the expected Java method declaration.
+ mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
+ }
+
+ /* Pass down to visitor writer. In this implementation, either do nothing. */
+ public void visitCode() {
+ mVisitCodeCalled = true;
+ mParentVisitor.visitCode();
+ }
+
+ /*
+ * visitMaxs is called just before visitEnd if there was any code to rewrite.
+ * Skip the original.
+ */
+ public void visitMaxs(int maxStack, int maxLocals) {
+ }
+
+ /**
+ * End of visiting. Generate the messaging code.
+ */
+ public void visitEnd() {
+ generateCode();
+ }
+
+ /* Writes all annotation from the original method. */
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return mParentVisitor.visitAnnotation(desc, visible);
+ }
+
+ /* Writes all annotation default values from the original method. */
+ public AnnotationVisitor visitAnnotationDefault() {
+ return mParentVisitor.visitAnnotationDefault();
+ }
+
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
+ }
+
+ /* Writes all attributes from the original method. */
+ public void visitAttribute(Attribute attr) {
+ mParentVisitor.visitAttribute(attr);
+ }
+
+ /*
+ * Only writes the first line number present in the original code so that source
+ * viewers can direct to the correct method, even if the content doesn't match.
+ */
+ public void visitLineNumber(int line, Label start) {
+ if (mOutputFirstLineNumber) {
+ mParentVisitor.visitLineNumber(line, start);
+ mOutputFirstLineNumber = false;
+ }
+ }
+
+ public void visitInsn(int opcode) {
+ // Skip original code.
+ }
+
+ public void visitLabel(Label label) {
+ // Skip original code.
+ }
+
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ // Skip original code.
+ }
+
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ // Skip original code.
+ }
+
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ // Skip original code.
+ }
+
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ // Skip original code.
+ }
+
+ public void visitIincInsn(int var, int increment) {
+ // Skip original code.
+ }
+
+ public void visitIntInsn(int opcode, int operand) {
+ // Skip original code.
+ }
+
+ public void visitJumpInsn(int opcode, Label label) {
+ // Skip original code.
+ }
+
+ public void visitLdcInsn(Object cst) {
+ // Skip original code.
+ }
+
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ // Skip original code.
+ }
+
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ // Skip original code.
+ }
+
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ // Skip original code.
+ }
+
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ // Skip original code.
+ }
+
+ public void visitTypeInsn(int opcode, String type) {
+ // Skip original code.
+ }
+
+ public void visitVarInsn(int opcode, int var) {
+ // Skip original code.
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
new file mode 100644
index 0000000..40c1706
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+/**
+ * Interface describing the work to be done by {@link AsmGenerator}.
+ */
+public interface ICreateInfo {
+
+ /**
+ * Returns the list of class from layoutlib_create to inject in layoutlib.
+ * The list can be empty but must not be null.
+ */
+ public abstract Class<?>[] getInjectedClasses();
+
+ /**
+ * Returns the list of methods to rewrite as delegates.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDelegateMethods();
+
+ /**
+ * Returns the list of classes on which to delegate all native methods.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDelegateClassNatives();
+
+ /**
+ * Returns The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getOverriddenMethods();
+
+ /**
+ * Returns the list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getRenamedClasses();
+
+ /**
+ * Returns the list of classes for which the methods returning them should be deleted.
+ * The array contains a list of null terminated section starting with the name of the class
+ * to rename in which the methods are deleted, followed by a list of return types identifying
+ * the methods to delete.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDeleteReturns();
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index 303f097..4adaff9 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -21,7 +21,28 @@
import java.util.Set;
-
+/**
+ * Entry point for the layoutlib_create tool.
+ * <p/>
+ * The tool does not currently rely on any external configuration file.
+ * Instead the configuration is mostly done via the {@link CreateInfo} class.
+ * <p/>
+ * For a complete description of the tool and its implementation, please refer to
+ * the "README.txt" file at the root of this project.
+ * <p/>
+ * For a quick test, invoke this as follows:
+ * <pre>
+ * $ make layoutlib
+ * </pre>
+ * which does:
+ * <pre>
+ * $ make layoutlib_create <bunch of framework jars>
+ * $ out/host/linux-x86/framework/bin/layoutlib_create \
+ * out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar \
+ * out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar \
+ * out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
+ * </pre>
+ */
public class Main {
public static void main(String[] args) {
@@ -42,12 +63,7 @@
}
try {
- AsmGenerator agen = new AsmGenerator(log, osDestJar[0],
- CreateInfo.INJECTED_CLASSES,
- CreateInfo.OVERRIDDEN_METHODS,
- CreateInfo.RENAMED_CLASSES,
- CreateInfo.REMOVED_METHODS
- );
+ AsmGenerator agen = new AsmGenerator(log, osDestJar[0], new CreateInfo());
AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
new String[] { "android.view.View" }, // derived from
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
index e294d56..f2d9755 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -26,7 +26,7 @@
import java.util.Set;
/**
- * Class adapter that can stub some or all of the methods of the class.
+ * Class adapter that can stub some or all of the methods of the class.
*/
class TransformClassAdapter extends ClassAdapter {
@@ -41,12 +41,12 @@
/**
* Creates a new class adapter that will stub some or all methods.
- * @param logger
- * @param stubMethods
+ * @param logger
+ * @param stubMethods list of method signatures to always stub out
* @param deleteReturns list of types that trigger the deletion of methods returning them.
* @param className The name of the class being modified
* @param cv The parent class writer visitor
- * @param stubNativesOnly True if only native methods should be stubbed. False if all
+ * @param stubNativesOnly True if only native methods should be stubbed. False if all
* methods should be stubbed.
* @param hasNative True if the method has natives, in which case its access should be
* changed.
@@ -67,10 +67,10 @@
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
-
+
// This class might be being renamed.
name = mClassName;
-
+
// remove protected or private and set as public
access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
access |= Opcodes.ACC_PUBLIC;
@@ -82,7 +82,7 @@
mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
super.visit(version, access, name, signature, superName, interfaces);
}
-
+
/* Visits the header of an inner class. */
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
@@ -101,7 +101,7 @@
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
-
+
if (mDeleteReturns != null) {
Type t = Type.getReturnType(desc);
if (t.getSort() == Type.OBJECT) {
@@ -130,16 +130,16 @@
(mStubAll ||
(access & Opcodes.ACC_NATIVE) != 0) ||
mStubMethods.contains(methodSignature)) {
-
+
boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
// remove abstract, final and native
access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
-
+
String invokeSignature = methodSignature + desc;
mLog.debug(" Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
-
+
MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature,
isStatic, isNative);
@@ -149,7 +149,7 @@
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
-
+
/* Visits a field. Makes it public. */
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature,
@@ -157,7 +157,7 @@
// change access to public
access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
access |= Opcodes.ACC_PUBLIC;
-
+
return super.visitField(access, name, desc, signature, value);
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index 603284e..d6dba6a 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertNotNull;
import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
-import com.android.tools.layoutlib.create.LogTest.MockLog;
import org.junit.After;
import org.junit.Before;
@@ -46,9 +45,9 @@
@Before
public void setUp() throws Exception {
- mLog = new LogTest.MockLog();
+ mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-
+
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
@@ -69,9 +68,9 @@
"mock_android.dummy.InnerTest$DerivingClass",
"mock_android.dummy.InnerTest$MyGenerics1",
"mock_android.dummy.InnerTest$MyIntEnum",
- "mock_android.dummy.InnerTest$MyStaticInnerClass",
- "mock_android.dummy.InnerTest$NotStaticInner1",
- "mock_android.dummy.InnerTest$NotStaticInner2",
+ "mock_android.dummy.InnerTest$MyStaticInnerClass",
+ "mock_android.dummy.InnerTest$NotStaticInner1",
+ "mock_android.dummy.InnerTest$NotStaticInner2",
"mock_android.view.View",
"mock_android.view.ViewGroup",
"mock_android.view.ViewGroup$LayoutParams",
@@ -83,7 +82,7 @@
},
map.keySet().toArray());
}
-
+
@Test
public void testFindClass() throws IOException, LogAbortException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -91,7 +90,7 @@
ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
zipClasses, found);
-
+
assertNotNull(cr);
assertEquals("mock_android/view/ViewGroup$LayoutParams", cr.getClassName());
assertArrayEquals(new String[] { "mock_android.view.ViewGroup$LayoutParams" },
@@ -172,14 +171,14 @@
"mock_android.widget.TableLayout",
},
found.keySet().toArray());
-
+
for (String key : found.keySet()) {
ClassReader value = found.get(key);
assertNotNull("No value for " + key, value);
assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
}
}
-
+
@Test
public void testDependencyVisitor() throws IOException, LogAbortException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -190,7 +189,7 @@
ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep);
DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
-
+
// get first level dependencies
cr.accept(visitor, 0 /* flags */);
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 7cdf79a..f4ff389 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -20,8 +20,6 @@
import static org.junit.Assert.assertArrayEquals;
-import com.android.tools.layoutlib.create.LogTest.MockLog;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -44,9 +42,9 @@
@Before
public void setUp() throws Exception {
- mLog = new LogTest.MockLog();
+ mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-
+
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
@@ -65,16 +63,41 @@
@Test
public void testClassRenaming() throws IOException, LogAbortException {
-
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar,
- null, // classes to inject in the final JAR
- null, // methods to force override
- new String[] { // classes to rename (so that we can replace them)
- "mock_android.view.View", "mock_android.view._Original_View",
- "not.an.actual.ClassName", "anoter.fake.NewClassName",
- },
- null // methods deleted from their return type.
- );
+
+ ICreateInfo ci = new ICreateInfo() {
+ public Class<?>[] getInjectedClasses() {
+ // classes to inject in the final JAR
+ return new Class<?>[0];
+ }
+
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ public String[] getRenamedClasses() {
+ // classes to rename (so that we can replace them)
+ return new String[] {
+ "mock_android.view.View", "mock_android.view._Original_View",
+ "not.an.actual.ClassName", "anoter.fake.NewClassName",
+ };
+ }
+
+ public String[] getDeleteReturns() {
+ // methods deleted from their return type.
+ return new String[0];
+ }
+ };
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
null, // derived from
@@ -83,7 +106,7 @@
});
aa.analyze();
agen.generate();
-
+
Set<String> notRenamed = agen.getClassesNotRenamed();
assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
index d6916ae..0135c40 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
@@ -33,8 +33,9 @@
@Test
public void testHasNative() throws IOException {
MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
- ClassReader cr = new ClassReader(
- "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithNative");
+ String className =
+ this.getClass().getCanonicalName() + "$" + ClassWithNative.class.getSimpleName();
+ ClassReader cr = new ClassReader(className);
cr.accept(cv, 0 /* flags */);
assertArrayEquals(new String[] { "native_method" }, cv.getMethodsFound());
@@ -44,14 +45,17 @@
@Test
public void testHasNoNative() throws IOException {
MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
- ClassReader cr = new ClassReader(
- "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithoutNative");
+ String className =
+ this.getClass().getCanonicalName() + "$" + ClassWithoutNative.class.getSimpleName();
+ ClassReader cr = new ClassReader(className);
cr.accept(cv, 0 /* flags */);
assertArrayEquals(new String[0], cv.getMethodsFound());
assertFalse(cv.hasNativeMethods());
}
+ //-------
+
/**
* Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
*/
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
new file mode 100644
index 0000000..9ad2e6e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+
+public class DelegateClassAdapterTest {
+
+ private MockLog mLog;
+
+ private static final String CLASS_NAME =
+ DelegateClassAdapterTest.class.getCanonicalName() + "$" +
+ ClassWithNative.class.getSimpleName();
+
+ @Before
+ public void setUp() throws Exception {
+ mLog = new MockLog();
+ mLog.setVerbose(true); // capture debug error too
+ }
+
+ /**
+ * Tests that a class not being modified still works.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testNoOp() throws Exception {
+ // create an instance of the class that will be modified
+ // (load the class in a distinct class loader so that we can trash its definition later)
+ ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
+ Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(CLASS_NAME);
+ ClassWithNative instance1 = clazz1.newInstance();
+ assertEquals(42, instance1.add(20, 22));
+ try {
+ instance1.callNativeInstance(10, 3.1415, new Object[0] );
+ fail("Test should have failed to invoke callTheNativeMethod [1]");
+ } catch (UnsatisfiedLinkError e) {
+ // This is expected to fail since the native method is not implemented.
+ }
+
+ // Now process it but tell the delegate to not modify any method
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ String internalClassName = CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+
+ // Load the generated class in a different class loader and try it again
+ final byte[] bytes = cw.toByteArray();
+
+ ClassLoader2 cl2 = new ClassLoader2(bytes) {
+ @Override
+ public void testModifiedInstance() throws Exception {
+ Class<?> clazz2 = loadClass(CLASS_NAME);
+ Object i2 = clazz2.newInstance();
+ assertNotNull(i2);
+ assertEquals(42, callAdd(i2, 20, 22));
+
+ try {
+ callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
+ fail("Test should have failed to invoke callTheNativeMethod [2]");
+ } catch (InvocationTargetException e) {
+ // This is expected to fail since the native method has NOT been
+ // overridden here.
+ assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
+ }
+
+ // Check that the native method does NOT have the new annotation
+ Method[] m = clazz2.getDeclaredMethods();
+ assertEquals("native_instance", m[2].getName());
+ assertTrue(Modifier.isNative(m[2].getModifiers()));
+ Annotation[] a = m[2].getAnnotations();
+ assertEquals(0, a.length);
+ }
+ };
+ cl2.testModifiedInstance();
+ }
+
+ /**
+ * {@link DelegateMethodAdapter} does not support overriding constructors yet,
+ * so this should fail with an {@link UnsupportedOperationException}.
+ *
+ * Although not tested here, the message of the exception should contain the
+ * constructor signature.
+ */
+ @Test(expected=UnsupportedOperationException.class)
+ public void testConstructorsNotSupported() throws IOException {
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+ String internalClassName = CLASS_NAME.replace('.', '/');
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add("<init>");
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+ }
+
+ @Test
+ public void testDelegateNative() throws Exception {
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+ String internalClassName = CLASS_NAME.replace('.', '/');
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+
+ // Load the generated class in a different class loader and try it
+ final byte[] bytes = cw.toByteArray();
+
+ try {
+ ClassLoader2 cl2 = new ClassLoader2(bytes) {
+ @Override
+ public void testModifiedInstance() throws Exception {
+ Class<?> clazz2 = loadClass(CLASS_NAME);
+ Object i2 = clazz2.newInstance();
+ assertNotNull(i2);
+
+ // Use reflection to access inner methods
+ assertEquals(42, callAdd(i2, 20, 22));
+
+ Object[] objResult = new Object[] { null };
+ int result = callCallNativeInstance(i2, 10, 3.1415, objResult);
+ assertEquals((int)(10 + 3.1415), result);
+ assertSame(i2, objResult[0]);
+
+ // Check that the native method now has the new annotation and is not native
+ Method[] m = clazz2.getDeclaredMethods();
+ assertEquals("native_instance", m[2].getName());
+ assertFalse(Modifier.isNative(m[2].getModifiers()));
+ Annotation[] a = m[2].getAnnotations();
+ assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
+ }
+ };
+ cl2.testModifiedInstance();
+
+ } catch (Throwable t) {
+ // For debugging, dump the bytecode of the class in case of unexpected error.
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ TraceClassVisitor tcv = new TraceClassVisitor(pw);
+
+ ClassReader cr2 = new ClassReader(bytes);
+ cr2.accept(tcv, 0 /* flags */);
+
+ String msg = "\n" + t.getClass().getCanonicalName();
+ if (t.getMessage() != null) {
+ msg += ": " + t.getMessage();
+ }
+ msg = msg + "\nBytecode dump:\n" + sw.toString();
+
+ // Re-throw exception with new message
+ RuntimeException ex = new RuntimeException(msg, t);
+ throw ex;
+ }
+ }
+
+ //-------
+
+ /**
+ * A class loader than can define and instantiate our dummy {@link ClassWithNative}.
+ * <p/>
+ * The trick here is that this class loader will test our modified version of ClassWithNative.
+ * Trying to do so in the original class loader generates all sort of link issues because
+ * there are 2 different definitions of the same class name. This class loader will
+ * define and load the class when requested by name and provide helpers to access the
+ * instance methods via reflection.
+ */
+ private abstract class ClassLoader2 extends ClassLoader {
+ private final byte[] mClassWithNative;
+
+ public ClassLoader2(byte[] classWithNative) {
+ super(null);
+ mClassWithNative = classWithNative;
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ try {
+ return super.findClass(name);
+ } catch (ClassNotFoundException e) {
+
+ if (CLASS_NAME.equals(name)) {
+ // Load the modified ClassWithNative from its bytes representation.
+ return defineClass(CLASS_NAME, mClassWithNative, 0, mClassWithNative.length);
+ }
+
+ try {
+ // Load everything else from the original definition into the new class loader.
+ ClassReader cr = new ClassReader(name);
+ ClassWriter cw = new ClassWriter(0);
+ cr.accept(cw, 0);
+ byte[] bytes = cw.toByteArray();
+ return defineClass(name, bytes, 0, bytes.length);
+
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ }
+
+ /**
+ * Accesses {@link ClassWithNative#add(int, int)} via reflection.
+ */
+ public int callAdd(Object instance, int a, int b) throws Exception {
+ Method m = instance.getClass().getMethod("add",
+ new Class<?>[] { int.class, int.class });
+
+ Object result = m.invoke(instance, new Object[] { a, b });
+ return ((Integer) result).intValue();
+ }
+
+ /**
+ * Accesses {@link ClassWithNative#callNativeInstance(int, double, Object[])}
+ * via reflection.
+ */
+ public int callCallNativeInstance(Object instance, int a, double d, Object[] o)
+ throws Exception {
+ Method m = instance.getClass().getMethod("callNativeInstance",
+ new Class<?>[] { int.class, double.class, Object[].class });
+
+ Object result = m.invoke(instance, new Object[] { a, d, o });
+ return ((Integer) result).intValue();
+ }
+
+ public abstract void testModifiedInstance() throws Exception;
+ }
+
+ /**
+ * Dummy test class with a native method.
+ * The native method is not defined and any attempt to invoke it will
+ * throw an {@link UnsatisfiedLinkError}.
+ */
+ public static class ClassWithNative {
+ public ClassWithNative() {
+ }
+
+ public int add(int a, int b) {
+ return a + b;
+ }
+
+ public int callNativeInstance(int a, double d, Object[] o) {
+ return native_instance(a, d, o);
+ }
+
+ private native int native_instance(int a, double d, Object[] o);
+ }
+
+ /**
+ * The delegate that receives the call to {@link ClassWithNative}'s overridden methods.
+ */
+ public static class ClassWithNative_Delegate {
+ public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
+ if (o != null && o.length > 0) {
+ o[0] = instance;
+ }
+ return (int)(a + d);
+ }
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
index 3f13158..1a5f653 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
@@ -24,33 +24,8 @@
public class LogTest {
- public static class MockLog extends Log {
- StringBuilder mOut = new StringBuilder();
- StringBuilder mErr = new StringBuilder();
-
- public String getOut() {
- return mOut.toString();
- }
-
- public String getErr() {
- return mErr.toString();
- }
-
- @Override
- protected void outPrintln(String msg) {
- mOut.append(msg);
- mOut.append('\n');
- }
-
- @Override
- protected void errPrintln(String msg) {
- mErr.append(msg);
- mErr.append('\n');
- }
- }
-
private MockLog mLog;
-
+
@Before
public void setUp() throws Exception {
mLog = new MockLog();
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
new file mode 100644
index 0000000..de750a3
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+
+public class MockLog extends Log {
+ StringBuilder mOut = new StringBuilder();
+ StringBuilder mErr = new StringBuilder();
+
+ public String getOut() {
+ return mOut.toString();
+ }
+
+ public String getErr() {
+ return mErr.toString();
+ }
+
+ @Override
+ protected void outPrintln(String msg) {
+ mOut.append(msg);
+ mOut.append('\n');
+ }
+
+ @Override
+ protected void errPrintln(String msg) {
+ mErr.append(msg);
+ mErr.append('\n');
+ }
+}
diff --git a/voip/jni/rtp/Android.mk b/voip/jni/rtp/Android.mk
index 5909c0d..76c43ba 100644
--- a/voip/jni/rtp/Android.mk
+++ b/voip/jni/rtp/Android.mk
@@ -22,6 +22,7 @@
LOCAL_SRC_FILES := \
AudioCodec.cpp \
AudioGroup.cpp \
+ EchoSuppressor.cpp \
RtpStream.cpp \
util.cpp \
rtp_jni.cpp
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index 5214518..9da560a 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -44,6 +44,7 @@
#include "JNIHelp.h"
#include "AudioCodec.h"
+#include "EchoSuppressor.h"
extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss);
@@ -766,7 +767,9 @@
}
LOGD("latency: output %d, input %d", track.latency(), record.latency());
- // TODO: initialize echo canceler here.
+ // Initialize echo canceler.
+ EchoSuppressor echo(sampleRate, sampleCount, sampleCount * 2 +
+ (track.latency() + record.latency()) * sampleRate / 1000);
// Give device socket a reasonable buffer size.
setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output));
@@ -839,7 +842,7 @@
if (mode == NORMAL) {
send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
} else {
- // TODO: Echo canceller runs here.
+ echo.run(output, input);
send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
}
}
diff --git a/voip/jni/rtp/EchoSuppressor.cpp b/voip/jni/rtp/EchoSuppressor.cpp
new file mode 100644
index 0000000..92015a9
--- /dev/null
+++ b/voip/jni/rtp/EchoSuppressor.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyrightm (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <math.h>
+
+#define LOG_TAG "Echo"
+#include <utils/Log.h>
+
+#include "EchoSuppressor.h"
+
+EchoSuppressor::EchoSuppressor(int sampleRate, int sampleCount, int tailLength)
+{
+ int scale = 1;
+ while (tailLength > 200 * scale) {
+ scale <<= 1;
+ }
+ if (scale > sampleCount) {
+ scale = sampleCount;
+ }
+
+ mScale = scale;
+ mSampleCount = sampleCount;
+ mWindowSize = sampleCount / scale;
+ mTailLength = (tailLength + scale - 1) / scale;
+ mRecordLength = (sampleRate + sampleCount - 1) / sampleCount;
+ mRecordOffset = 0;
+
+ mXs = new float[mTailLength + mWindowSize];
+ memset(mXs, 0, sizeof(float) * (mTailLength + mWindowSize));
+ mXYs = new float[mTailLength];
+ memset(mXYs, 0, sizeof(float) * mTailLength);
+ mXXs = new float[mTailLength];
+ memset(mXYs, 0, sizeof(float) * mTailLength);
+ mYY = 0;
+
+ mXYRecords = new float[mRecordLength * mTailLength];
+ memset(mXYRecords, 0, sizeof(float) * mRecordLength * mTailLength);
+ mXXRecords = new float[mRecordLength * mWindowSize];
+ memset(mXXRecords, 0, sizeof(float) * mRecordLength * mWindowSize);
+ mYYRecords = new float[mRecordLength];
+ memset(mYYRecords, 0, sizeof(float) * mRecordLength);
+
+ mLastX = 0;
+ mLastY = 0;
+}
+
+EchoSuppressor::~EchoSuppressor()
+{
+ delete [] mXs;
+ delete [] mXYs;
+ delete [] mXXs;
+ delete [] mXYRecords;
+ delete [] mXXRecords;
+ delete [] mYYRecords;
+}
+
+void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded)
+{
+ float *records;
+
+ // Update Xs.
+ for (int i = 0; i < mTailLength; ++i) {
+ mXs[i] = mXs[mWindowSize + i];
+ }
+ for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) {
+ float sum = 0;
+ for (int k = 0; k < mScale; ++k) {
+ float x = playbacked[j + k] >> 8;
+ mLastX += x;
+ sum += (mLastX >= 0) ? mLastX : -mLastX;
+ mLastX = 0.005f * mLastX - x;
+ }
+ mXs[mTailLength - 1 + i] = sum;
+ }
+
+ // Update XXs and XXRecords.
+ for (int i = 0; i < mTailLength - mWindowSize; ++i) {
+ mXXs[i] = mXXs[mWindowSize + i];
+ }
+ records = &mXXRecords[mRecordOffset * mWindowSize];
+ for (int i = 0, j = mTailLength - mWindowSize; i < mWindowSize; ++i, ++j) {
+ float xx = mXs[mTailLength - 1 + i] * mXs[mTailLength - 1 + i];
+ mXXs[j] = mXXs[j - 1] + xx - records[i];
+ records[i] = xx;
+ if (mXXs[j] < 0) {
+ mXXs[j] = 0;
+ }
+ }
+
+ // Compute Ys.
+ float ys[mWindowSize];
+ for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) {
+ float sum = 0;
+ for (int k = 0; k < mScale; ++k) {
+ float y = recorded[j + k] >> 8;
+ mLastY += y;
+ sum += (mLastY >= 0) ? mLastY : -mLastY;
+ mLastY = 0.005f * mLastY - y;
+ }
+ ys[i] = sum;
+ }
+
+ // Update YY and YYRecords.
+ float yy = 0;
+ for (int i = 0; i < mWindowSize; ++i) {
+ yy += ys[i] * ys[i];
+ }
+ mYY += yy - mYYRecords[mRecordOffset];
+ mYYRecords[mRecordOffset] = yy;
+ if (mYY < 0) {
+ mYY = 0;
+ }
+
+ // Update XYs and XYRecords.
+ records = &mXYRecords[mRecordOffset * mTailLength];
+ for (int i = 0; i < mTailLength; ++i) {
+ float xy = 0;
+ for (int j = 0;j < mWindowSize; ++j) {
+ xy += mXs[i + j] * ys[j];
+ }
+ mXYs[i] += xy - records[i];
+ records[i] = xy;
+ if (mXYs[i] < 0) {
+ mXYs[i] = 0;
+ }
+ }
+
+ // Computes correlations from XYs, XXs, and YY.
+ float weight = 1.0f / (mYY + 1);
+ float correlation = 0;
+ int latency = 0;
+ for (int i = 0; i < mTailLength; ++i) {
+ float c = mXYs[i] * mXYs[i] * weight / (mXXs[i] + 1);
+ if (c > correlation) {
+ correlation = c;
+ latency = i;
+ }
+ }
+
+ correlation = sqrtf(correlation);
+ if (correlation > 0.3f) {
+ float factor = 1.0f - correlation;
+ factor *= factor;
+ for (int i = 0; i < mSampleCount; ++i) {
+ recorded[i] *= factor;
+ }
+ }
+// LOGI("latency %5d, correlation %.10f", latency, correlation);
+
+
+ // Increase RecordOffset.
+ ++mRecordOffset;
+ if (mRecordOffset == mRecordLength) {
+ mRecordOffset = 0;
+ }
+}
diff --git a/voip/jni/rtp/EchoSuppressor.h b/voip/jni/rtp/EchoSuppressor.h
new file mode 100644
index 0000000..85decf5
--- /dev/null
+++ b/voip/jni/rtp/EchoSuppressor.h
@@ -0,0 +1,51 @@
+/*
+ * Copyrightm (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ECHO_SUPPRESSOR_H__
+#define __ECHO_SUPPRESSOR_H__
+
+#include <stdint.h>
+
+class EchoSuppressor
+{
+public:
+ // The sampleCount must be power of 2.
+ EchoSuppressor(int sampleRate, int sampleCount, int tailLength);
+ ~EchoSuppressor();
+ void run(int16_t *playbacked, int16_t *recorded);
+
+private:
+ int mScale;
+ int mSampleCount;
+ int mWindowSize;
+ int mTailLength;
+ int mRecordLength;
+ int mRecordOffset;
+
+ float *mXs;
+ float *mXYs;
+ float *mXXs;
+ float mYY;
+
+ float *mXYRecords;
+ float *mXXRecords;
+ float *mYYRecords;
+
+ float mLastX;
+ float mLastY;
+};
+
+#endif