Merge "Fix proguard flags."
diff --git a/apps/BugReportSender/Android.mk b/apps/BugReportSender/Android.mk
index b94f802..1c3a5e4 100644
--- a/apps/BugReportSender/Android.mk
+++ b/apps/BugReportSender/Android.mk
@@ -18,6 +18,6 @@
LOCAL_CERTIFICATE := platform
LOCAL_MODULE_TAGS := eng
LOCAL_PACKAGE_NAME := BugReportSender
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 4
LOCAL_SRC_FILES := $(call all-subdir-java-files)
include $(BUILD_PACKAGE)
diff --git a/apps/BugReportSender/AndroidManifest.xml b/apps/BugReportSender/AndroidManifest.xml
index aee89f1..4d1f97d 100644
--- a/apps/BugReportSender/AndroidManifest.xml
+++ b/apps/BugReportSender/AndroidManifest.xml
@@ -15,16 +15,17 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.bugreportsender" >
+ package="com.android.bugreportsender"
+ android:versionCode="11" >
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <!-- Minimum Donut, target Froyo -->
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="7" />
+
<application
android:icon="@drawable/icon"
- android:label="Bug Report Sender"
- android:versionCode="1" >
-
- <uses-sdk android:minSdkVersion="4" />
+ android:label="Bug Report Sender" >
<activity android:name="BugReportListActivity">
<intent-filter>
diff --git a/apps/Development/Android.mk b/apps/Development/Android.mk
index fa929b6..1cef548 100644
--- a/apps/Development/Android.mk
+++ b/apps/Development/Android.mk
@@ -3,7 +3,6 @@
LOCAL_MODULE_TAGS := eng
-LOCAL_STATIC_JAVA_LIBRARIES := android-common
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_SRC_FILES := $(call all-subdir-java-files) \
diff --git a/apps/Development/src/com/android/development/PointerLocation.java b/apps/Development/src/com/android/development/PointerLocation.java
index 88828b0..a8b64e3 100644
--- a/apps/Development/src/com/android/development/PointerLocation.java
+++ b/apps/Development/src/com/android/development/PointerLocation.java
@@ -16,7 +16,7 @@
package com.android.development;
-import com.android.common.ui.PointerLocationView;
+import com.android.internal.widget.PointerLocationView;
import android.app.Activity;
import android.os.Bundle;
diff --git a/build/sdk.atree b/build/sdk.atree
index a571949..a2d22a7 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -65,10 +65,11 @@
sdk/templates/icon_hdpi.png platforms/${PLATFORM_NAME}/templates/icon_hdpi.png
# sdk.git files
-sdk/files/ant.properties platforms/${PLATFORM_NAME}/templates/ant.properties
-sdk/files/android_rules.xml platforms/${PLATFORM_NAME}/templates/android_rules.xml
+sdk/files/ant.properties platforms/${PLATFORM_NAME}/templates/ant.properties
+sdk/files/android_rules.xml platforms/${PLATFORM_NAME}/templates/android_rules.xml
+sdk/files/android_lib_rules.xml platforms/${PLATFORM_NAME}/templates/android_lib_rules.xml
sdk/files/android_test_rules.xml platforms/${PLATFORM_NAME}/templates/android_test_rules.xml
-sdk/files/devices.xml tools/lib/devices.xml
+sdk/files/devices.xml tools/lib/devices.xml
# emacs support from sdk.git
sdk/files/android.el tools/lib/android.el
diff --git a/build/tools/make_windows_sdk.sh b/build/tools/make_windows_sdk.sh
index d30c956..6afc185 100755
--- a/build/tools/make_windows_sdk.sh
+++ b/build/tools/make_windows_sdk.sh
@@ -15,14 +15,11 @@
set -e # Fail this script as soon as a command fails -- fail early, fail fast
-# Set to 1 to force removal of old unzipped SDK. Only disable for debugging, as it
-# will make some rm/mv commands to fail.
-FORCE="1"
PROG_NAME="$0"
-SDK_ZIP="$1"
-DIST_DIR="$2"
-TEMP_DIR="$3"
+SDK_ZIP="$1"; shift
+DIST_DIR="$1"; shift
+TEMP_DIR="$1"; shift
[ -z "$TEMP_DIR" ] && TEMP_DIR=${TMP:-/tmp}
function die() {
@@ -51,6 +48,18 @@
[ -f "$SDK_ZIP" ] || usage
[ -d "$DIST_DIR" ] || usage
+
+ # We need mgwz.dll in the SDK when compiling with Cygwin 1.5
+ # Right now we don't support building with Cygwin 1.7 yet, as it lacks this DLL.
+ NEED_MGWZ=1
+ # We can skip this check for debug purposes.
+ echo $*
+ [[ "$1" == "-no-mgwz" ]] && NEED_MGWZ=""
+ CYG_MGWZ_PATH=/cygdrive/c/cygwin/bin/mgwz.dll
+ [[ -n $NEED_MGWZ && ! -f $CYG_MGWZ_PATH ]] && \
+ die "Cygwin is missing $CYG_MGWZ_PATH. Use -no-mgwz to override."
+
+
# Use the BUILD_ID as SDK_NUMBER if defined, otherwise try to get it from the
# provided zip filename.
if [ -f config/build_id.make ]; then
@@ -110,6 +119,9 @@
zipalign \
|| die "Build failed"
+ # Fix permissions. Git/cygwin may not make this +x as needed.
+ chmod +x prebuilt/windows/sdl/bin/sdl-config
+
# It's worth building the emulator with -j 4 so do it separately
make -j 4 emulator || die "Build failed"
}
@@ -123,14 +135,12 @@
TEMP_SDK_DIR="$TEMP_DIR/$DEST_NAME"
# Unzip current linux/mac SDK and rename using the windows name
- if [[ -n "$FORCE" || ! -d "$TEMP_SDK_DIR" ]]; then
- [ -e "$TEMP_SDK_DIR" ] && rm -rfv "$TEMP_SDK_DIR" # cleanup dest first if exists
- UNZIPPED=`basename "$SDK_ZIP"`
- UNZIPPED="$TEMP_DIR/${UNZIPPED/.zip/}"
- [ -e "$UNZIPPED" ] && rm -rfv "$UNZIPPED" # cleanup unzip dir (if exists)
- unzip "$SDK_ZIP" -d "$TEMP_DIR"
- mv -v "$UNZIPPED" "$TEMP_SDK_DIR"
- fi
+ [ -e "$TEMP_SDK_DIR" ] && rm -rfv "$TEMP_SDK_DIR" # cleanup dest first if exists
+ UNZIPPED=`basename "$SDK_ZIP"`
+ UNZIPPED="$TEMP_DIR/${UNZIPPED/.zip/}"
+ [ -e "$UNZIPPED" ] && rm -rfv "$UNZIPPED" # cleanup unzip dir (if exists)
+ unzip "$SDK_ZIP" -d "$TEMP_DIR"
+ mv -v "$UNZIPPED" "$TEMP_SDK_DIR"
# Assert that the package contains only one platform
PLATFORMS="$TEMP_SDK_DIR/platforms"
@@ -172,7 +182,7 @@
# cp -v external/qemu/NOTICE "$TOOLS"/emulator_NOTICE.txt
# We currently need libz from MinGW for aapt
- cp -v /cygdrive/c/cygwin/bin/mgwz.dll "$TOOLS"/
+ [[ -n $NEED_MGWZ ]] && cp -v $CYG_MGWZ_PATH "$TOOLS"/
# Update a bunch of bat files
cp -v sdk/files/post_tools_install.bat "$LIB"/
@@ -215,7 +225,7 @@
cp -v dalvik/dx/etc/dx.bat "$PLATFORM_TOOLS"/
mv -v "$TOOLS"/{aapt.exe,aidl.exe,dexdump.exe} "$PLATFORM_TOOLS"/
# Note: mgwz.dll must be both in SDK/tools for zipalign and in SDK/platform/XYZ/tools/ for aapt
- cp -v "$TOOLS"/mgwz.dll "$PLATFORM_TOOLS"/
+ [[ -n $NEED_MGWZ ]] && cp -v "$TOOLS"/mgwz.dll "$PLATFORM_TOOLS"/
# Fix EOL chars to make window users happy - fix all files at the top level only
# as well as all batch files including those in platforms/<name>/tools/
@@ -241,7 +251,7 @@
echo "Resulting SDK is in $DIST_DIR/$DEST_NAME_ZIP"
}
-check
+check $*
status
build
package
diff --git a/ide/eclipse/.classpath b/ide/eclipse/.classpath
index 693cbfd..eb2fa76 100644
--- a/ide/eclipse/.classpath
+++ b/ide/eclipse/.classpath
@@ -42,7 +42,7 @@
<classpathentry kind="src" path="frameworks/base/sax/java"/>
<classpathentry kind="src" path="frameworks/base/services/java"/>
<classpathentry kind="src" path="frameworks/base/telephony/java"/>
- <classpathentry kind="src" path="frameworks/base/test-runner"/>
+ <classpathentry kind="src" path="frameworks/base/test-runner/src"/>
<classpathentry kind="src" path="frameworks/base/vpn/java"/>
<classpathentry kind="src" path="frameworks/base/wifi/java"/>
<classpathentry kind="src" path="frameworks/policies/base/phone"/>
@@ -107,6 +107,6 @@
<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/google-common_intermediates/javalib.jar"/>
<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/gsf-client_intermediates/javalib.jar"/>
<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/mms-common_intermediates/javalib.jar"/>
- <classpathentry kind="lib" path="packages/apps/Calculator/arity-2.0.2.jar"/>
+ <classpathentry kind="lib" path="packages/apps/Calculator/arity-2.1.2.jar"/>
<classpathentry kind="output" path="out/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/classes"/>
</classpath>
diff --git a/ndk/apps/hello-gl2/project/AndroidManifest.xml b/ndk/apps/hello-gl2/project/AndroidManifest.xml
index 0ef6fb0..5a4d5f2 100644
--- a/ndk/apps/hello-gl2/project/AndroidManifest.xml
+++ b/ndk/apps/hello-gl2/project/AndroidManifest.xml
@@ -32,5 +32,6 @@
</intent-filter>
</activity>
</application>
+ <uses-feature android:glEsVersion="0x00020000"/>
<uses-sdk android:minSdkVersion="5"/>
</manifest>
diff --git a/ndk/apps/hello-gl2/project/src/com/android/gl2jni/GL2JNIView.java b/ndk/apps/hello-gl2/project/src/com/android/gl2jni/GL2JNIView.java
index 72b1dfb..060290a 100644
--- a/ndk/apps/hello-gl2/project/src/com/android/gl2jni/GL2JNIView.java
+++ b/ndk/apps/hello-gl2/project/src/com/android/gl2jni/GL2JNIView.java
@@ -33,6 +33,7 @@
import android.content.Context;
+import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.util.Log;
@@ -46,16 +47,26 @@
import javax.microedition.khronos.opengles.GL10;
/**
- * An implementation of SurfaceView that uses the dedicated surface for
- * displaying an OpenGL animation. This allows the animation to run in a
- * separate thread, without requiring that it be driven by the update mechanism
- * of the view hierarchy.
+ * A simple GLSurfaceView sub-class that demonstrate how to perform
+ * OpenGL ES 2.0 rendering into a GL Surface. Note the following important
+ * details:
*
- * The application-specific rendering code is delegated to a GLView.Renderer
- * instance.
+ * - The class must use a custom context factory to enable 2.0 rendering.
+ * See ContextFactory class definition below.
+ *
+ * - The class must use a custom EGLConfigChooser to be able to select
+ * an EGLConfig that supports 2.0. This is done by providing a config
+ * specification to eglChooseConfig() that has the attribute
+ * EGL10.ELG_RENDERABLE_TYPE containing the EGL_OPENGL_ES2_BIT flag
+ * set. See ConfigChooser class definition below.
+ *
+ * - The class must select the surface's format, then choose an EGLConfig
+ * that matches it exactly (with regards to red/green/blue/alpha channels
+ * bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
*/
class GL2JNIView extends GLSurfaceView {
private static String TAG = "GL2JNIView";
+ private static final boolean DEBUG = false;
public GL2JNIView(Context context) {
super(context);
@@ -68,10 +79,31 @@
}
private void init(boolean translucent, int depth, int stencil) {
+
+ /* By default, GLSurfaceView() creates a RGB_565 opaque surface.
+ * If we want a translucent one, we should change the surface's
+ * format here, using PixelFormat.TRANSLUCENT for GL Surfaces
+ * is interpreted as any 32-bit surface with alpha by SurfaceFlinger.
+ */
+ if (translucent) {
+ this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+ }
+
+ /* Setup the context factory for 2.0 rendering.
+ * See ContextFactory class definition below
+ */
setEGLContextFactory(new ContextFactory());
+
+ /* We need to choose an EGLConfig that matches the format of
+ * our surface exactly. This is going to be done in our
+ * custom config chooser. See ConfigChooser class definition
+ * below.
+ */
setEGLConfigChooser( translucent ?
- new ConfigChooser(8,8,8,8, depth, stencil) :
- new ConfigChooser(5,6,5,0, depth, stencil));
+ new ConfigChooser(8, 8, 8, 8, depth, stencil) :
+ new ConfigChooser(5, 6, 5, 0, depth, stencil) );
+
+ /* Set the renderer responsible for frame rendering */
setRenderer(new Renderer());
}
@@ -99,15 +131,6 @@
}
private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser {
- private static int EGL_OPENGL_ES2_BIT = 4;
- private static int[] s_configAttribs2 =
- {
- EGL10.EGL_RED_SIZE, 4,
- EGL10.EGL_GREEN_SIZE, 4,
- EGL10.EGL_BLUE_SIZE, 4,
- EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL10.EGL_NONE
- };
public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
mRedSize = r;
@@ -118,8 +141,24 @@
mStencilSize = stencil;
}
+ /* This EGL config specification is used to specify 2.0 rendering.
+ * We use a minimum size of 4 bits for red/green/blue, but will
+ * perform actual matching in chooseConfig() below.
+ */
+ private static int EGL_OPENGL_ES2_BIT = 4;
+ private static int[] s_configAttribs2 =
+ {
+ EGL10.EGL_RED_SIZE, 4,
+ EGL10.EGL_GREEN_SIZE, 4,
+ EGL10.EGL_BLUE_SIZE, 4,
+ EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL10.EGL_NONE
+ };
+
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+ /* Get the number of minimally matching EGL configurations
+ */
int[] num_config = new int[1];
egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config);
@@ -128,41 +167,46 @@
if (numConfigs <= 0) {
throw new IllegalArgumentException("No configs match configSpec");
}
+
+ /* Allocate then read the array of minimally matching EGL configs
+ */
EGLConfig[] configs = new EGLConfig[numConfigs];
egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
- // printConfigs(egl, display, configs);
+
+ if (DEBUG) {
+ printConfigs(egl, display, configs);
+ }
+ /* Now return the "best" one
+ */
return chooseConfig(egl, display, configs);
}
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
EGLConfig[] configs) {
- EGLConfig closestConfig = null;
- int closestDistance = 1000;
for(EGLConfig config : configs) {
int d = findConfigAttrib(egl, display, config,
EGL10.EGL_DEPTH_SIZE, 0);
int s = findConfigAttrib(egl, display, config,
EGL10.EGL_STENCIL_SIZE, 0);
- if (d >= mDepthSize && s>= mStencilSize) {
- int r = findConfigAttrib(egl, display, config,
- EGL10.EGL_RED_SIZE, 0);
- int g = findConfigAttrib(egl, display, config,
- EGL10.EGL_GREEN_SIZE, 0);
- int b = findConfigAttrib(egl, display, config,
- EGL10.EGL_BLUE_SIZE, 0);
- int a = findConfigAttrib(egl, display, config,
- EGL10.EGL_ALPHA_SIZE, 0);
- int distance = Math.abs(r - mRedSize)
- + Math.abs(g - mGreenSize)
- + Math.abs(b - mBlueSize)
- + Math.abs(a - mAlphaSize);
- if (distance < closestDistance) {
- closestDistance = distance;
- closestConfig = config;
- }
- }
+
+ // We need at least mDepthSize and mStencilSize bits
+ if (d < mDepthSize || s < mStencilSize)
+ continue;
+
+ // We want an *exact* match for red/green/blue/alpha
+ int r = findConfigAttrib(egl, display, config,
+ EGL10.EGL_RED_SIZE, 0);
+ int g = findConfigAttrib(egl, display, config,
+ EGL10.EGL_GREEN_SIZE, 0);
+ int b = findConfigAttrib(egl, display, config,
+ EGL10.EGL_BLUE_SIZE, 0);
+ int a = findConfigAttrib(egl, display, config,
+ EGL10.EGL_ALPHA_SIZE, 0);
+
+ if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize)
+ return config;
}
- return closestConfig;
+ return null;
}
private int findConfigAttrib(EGL10 egl, EGLDisplay display,
@@ -293,4 +337,3 @@
}
}
}
-
diff --git a/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.c b/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.c
index bac8659..35367c1 100644
--- a/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.c
+++ b/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.c
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
#include "helloneon-intrinsics.h"
#include <arm_neon.h>
diff --git a/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.h b/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.h
index 1f2ff9a..ad4c8db 100644
--- a/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.h
+++ b/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.h
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
#ifndef HELLONEON_INTRINSICS_H
#define HELLONEON_INTRINSICS_H
diff --git a/ndk/apps/hello-neon/project/jni/helloneon.c b/ndk/apps/hello-neon/project/jni/helloneon.c
index 3a2e928..ee19e08 100644
--- a/ndk/apps/hello-neon/project/jni/helloneon.c
+++ b/ndk/apps/hello-neon/project/jni/helloneon.c
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
#include <jni.h>
#include <time.h>
#include <stdio.h>
diff --git a/ndk/build/tools/build-toolchain.sh b/ndk/build/tools/build-toolchain.sh
index 80bb4e9..51f3fa7 100755
--- a/ndk/build/tools/build-toolchain.sh
+++ b/ndk/build/tools/build-toolchain.sh
@@ -389,14 +389,13 @@
}
# Unpack a given package in a target location
-# $1: package name
-# $2: target directory
+# $1: package name (e.g. toolchain)
+# $2: target directory (e.g. /tmp/foo)
#
unpack_package ()
{
- WORKSPACE=$ANDROID_NDK_ARCHIVE/$1
- SRCDIR=$2
SRCPKG=`var_value PKG_$1`
+ SRCDIR=$2
if ! timestamp_check $1 unpack; then
echo "Unpack : $1 sources"
echo " from $SRCPKG"
@@ -413,6 +412,45 @@
exit 1
fi
timestamp_set $1 unpack
+ timestamp_force $1 patch
+ fi
+}
+
+# Patch a given package at a target location
+# $1: package name (e.g. toolchain)
+# $2: target directory (e.g. /tmp/foo)
+# $3: patch directory (e.g. build/tools/toolchain-patches)
+#
+# The rationale here is that anything named like $3/<subpath>/<foo>.patch
+# will be applied with "patch -p1" under $2/<subpath>
+#
+# Patches are listed and applied in alphanumerical order of their names
+# as returned by 'find'. Consider using numbered prefixes like the patch
+# files generated by "git format-patch" are named.
+#
+patch_package ()
+{
+ SRCPKG=`var_value PKG_$1`
+ SRCDIR=$2
+ if ! timestamp_check $1 patch; then
+ PATCH_FILES=`(cd $3 && find . -name "*.patch") 2> /dev/null`
+ if [ -z "$PATCH_FILES" ] ; then
+ echo "Patch : none provided"
+ return
+ fi
+ for PATCH in $PATCH_FILES; do
+ echo "Patch : $1 sources"
+ echo " from $PATCH"
+ echo " into $SRCDIR"
+ PATCHDIR=`dirname $PATCH`
+ PATCHNAME=`basename $PATCH`
+ cd $SRCDIR/$PATCHDIR && patch -p1 < $3/$PATCH
+ if [ $? != 0 ] ; then
+ echo "Patch failure !! Please check toolchain package !"
+ exit 1
+ fi
+ done
+ timestamp_set $1 patch
timestamp_force $1 configure
fi
}
@@ -443,6 +481,7 @@
fi
unpack_package toolchain $ANDROID_TOOLCHAIN_SRC
+patch_package toolchain $ANDROID_TOOLCHAIN_SRC $ANDROID_NDK_ROOT/build/tools/toolchain-patches
# remove all info files from the unpacked toolchain sources
# they create countless little problems during the build
diff --git a/ndk/sources/cpufeatures/cpu-features.c b/ndk/sources/cpufeatures/cpu-features.c
index d165635..c46b884 100644
--- a/ndk/sources/cpufeatures/cpu-features.c
+++ b/ndk/sources/cpufeatures/cpu-features.c
@@ -1,3 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
#include <sys/system_properties.h>
#include <machine/cpu-features.h>
#include <pthread.h>
diff --git a/ndk/sources/cpufeatures/cpu-features.h b/ndk/sources/cpufeatures/cpu-features.h
index d5fa876..e1cafd6 100644
--- a/ndk/sources/cpufeatures/cpu-features.h
+++ b/ndk/sources/cpufeatures/cpu-features.h
@@ -1,3 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
#ifndef CPU_FEATURES_H
#define CPU_FEATURES_H
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index 6615d71..1e3f66b 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -29,6 +29,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
<!-- We will request access to the camera, saying we require a camera
of some sort but not one with autofocus capability. -->
@@ -651,6 +653,23 @@
</intent-filter>
</activity>
+ <activity android:name=".os.SmsMessagingDemo" android:label="OS/SMS Messaging">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".os.SmsReceivedDialog"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar"
+ android:launchMode="singleInstance" />
+
+ <receiver android:name=".os.SmsMessageReceiver" android:enabled="false">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_RECEIVED" />
+ </intent-filter>
+ </receiver>
+
<!-- ************************************* -->
<!-- ANIMATION PACKAGE SAMPLES -->
<!-- ************************************* -->
diff --git a/samples/ApiDemos/res/layout/sms_demo.xml b/samples/ApiDemos/res/layout/sms_demo.xml
new file mode 100644
index 0000000..1074f52
--- /dev/null
+++ b/samples/ApiDemos/res/layout/sms_demo.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Demonstrates sending and receiving SMS messages.
+ See corresponding Java code SmsMessagingDemo.java
+-->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="6dip">
+ <TextView
+ android:textColor="#ff0000"
+ android:textStyle="bold"
+ android:text="@string/sms_warning"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ <CheckBox
+ android:id="@+id/sms_enable_receiver"
+ android:text="@string/sms_enable_receiver"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <TableLayout
+ android:padding="6dip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:stretchColumns="1">
+ <TableRow android:layout_width="match_parent">
+ <TextView android:text="@string/sms_recipient_label" />
+ <EditText android:id="@+id/sms_recipient" />
+ </TableRow>
+ <TableRow>
+ <TextView android:text="@string/sms_content_label" />
+ <EditText android:id="@+id/sms_content" />
+ </TableRow>
+ <TableRow>
+ <Button
+ android:id="@+id/sms_send_message"
+ android:text="@string/sms_send_message"
+ android:layout_column="1" />
+ </TableRow>
+ <TableRow>
+ <TextView
+ android:id="@+id/sms_status"
+ android:layout_column="1" />
+ </TableRow>
+ </TableLayout>
+ </LinearLayout>
+</ScrollView>
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index c7bb84e..799d67f 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -879,5 +879,21 @@
<string name="appwidget_configure_instructions">This text will be shown before the date in our example widget.</string>
<string name="appwidget_prefix_default">Oh hai</string>
<string name="appwidget_text_format"><xliff:g id="prefix">%1$s</xliff:g>: <xliff:g id="time">%2$s</xliff:g></string>
+
+ <!-- ============================ -->
+ <!-- SMS Messaging examples strings -->
+ <!-- ============================ -->
+
+ <string name="sms_warning">
+ WARNING: this demo can send actual text messages (one at a time), so be sure to
+ test with the Android emulator or have a text messaging plan with your carrier.
+ </string>
+ <string name="sms_enable_receiver">Enable SMS broadcast receiver</string>
+ <string name="sms_recipient_label">Recipient #</string>
+ <string name="sms_content_label">Message Body</string>
+ <string name="sms_send_message">Send</string>
+ <string name="sms_speak_string_format">Message from "%1$s": %2$s</string>
+ <string name="reply">Reply</string>
+ <string name="dismiss">Dismiss</string>
</resources>
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
index 776a6fb..07ce0c6 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
@@ -21,8 +21,8 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
-import android.app.DeviceAdminReceiver;
-import android.app.DevicePolicyManager;
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@@ -339,7 +339,8 @@
}
boolean active = mDPM.isAdminActive(mDeviceAdminSample);
if (active) {
- mDPM.resetPassword(mPassword.getText().toString());
+ mDPM.resetPassword(mPassword.getText().toString(),
+ DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY);
}
}
};
diff --git a/samples/ApiDemos/src/com/example/android/apis/os/SmsMessageReceiver.java b/samples/ApiDemos/src/com/example/android/apis/os/SmsMessageReceiver.java
new file mode 100644
index 0000000..ec80955
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/os/SmsMessageReceiver.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.os;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.telephony.SmsMessage;
+
+public class SmsMessageReceiver extends BroadcastReceiver {
+ /** Tag string for our debug logs */
+ private static final String TAG = "SmsMessageReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle extras = intent.getExtras();
+ if (extras == null)
+ return;
+
+ Object[] pdus = (Object[]) extras.get("pdus");
+
+ for (int i = 0; i < pdus.length; i++) {
+ SmsMessage message = SmsMessage.createFromPdu((byte[]) pdus[i]);
+ String fromAddress = message.getOriginatingAddress();
+ String fromDisplayName = fromAddress;
+
+ Uri uri;
+ String[] projection;
+
+ // If targeting Donut or below, use
+ // Contacts.Phones.CONTENT_FILTER_URL and
+ // Contacts.Phones.DISPLAY_NAME
+ uri = Uri.withAppendedPath(
+ ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(fromAddress));
+ projection = new String[] { ContactsContract.PhoneLookup.DISPLAY_NAME };
+
+ // Query the filter URI
+ Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
+ if (cursor != null) {
+ if (cursor.moveToFirst())
+ fromDisplayName = cursor.getString(0);
+
+ cursor.close();
+ }
+
+ // Trigger the main activity to fire up a dialog that shows/reads the received messages
+ Intent di = new Intent();
+ di.setClass(context, SmsReceivedDialog.class);
+ di.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ di.putExtra(SmsReceivedDialog.SMS_FROM_ADDRESS_EXTRA, fromAddress);
+ di.putExtra(SmsReceivedDialog.SMS_FROM_DISPLAY_NAME_EXTRA, fromDisplayName);
+ di.putExtra(SmsReceivedDialog.SMS_MESSAGE_EXTRA, message.getMessageBody().toString());
+ context.startActivity(di);
+
+ // For the purposes of this demo, we'll only handle the first received message.
+ break;
+ }
+ }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/os/SmsMessagingDemo.java b/samples/ApiDemos/src/com/example/android/apis/os/SmsMessagingDemo.java
new file mode 100644
index 0000000..94f4e5a
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/os/SmsMessagingDemo.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.os;
+
+import java.util.List;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.telephony.SmsManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+
+import com.example.android.apis.R;
+
+public class SmsMessagingDemo extends Activity {
+ /** Tag string for our debug logs */
+ private static final String TAG = "SmsMessagingDemo";
+
+ public static final String SMS_RECIPIENT_EXTRA = "com.example.android.apis.os.SMS_RECIPIENT";
+
+ public static final String ACTION_SMS_SENT = "com.example.android.apis.os.SMS_SENT_ACTION";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.sms_demo);
+
+ if (getIntent().hasExtra(SMS_RECIPIENT_EXTRA)) {
+ ((TextView) findViewById(R.id.sms_recipient)).setText(getIntent().getExtras()
+ .getString(SMS_RECIPIENT_EXTRA));
+ ((TextView) findViewById(R.id.sms_content)).requestFocus();
+ }
+
+ // Enable or disable the broadcast receiver depending on the checked
+ // state of the checkbox.
+ CheckBox enableCheckBox = (CheckBox) findViewById(R.id.sms_enable_receiver);
+
+ final PackageManager pm = this.getPackageManager();
+ final ComponentName componentName = new ComponentName("com.example.android.apis",
+ "com.example.android.apis.os.SmsMessageReceiver");
+
+ enableCheckBox.setChecked(pm.getComponentEnabledSetting(componentName) ==
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+ enableCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ Log.d(TAG, (isChecked ? "Enabling" : "Disabling") + " SMS receiver");
+
+ pm.setComponentEnabledSetting(componentName,
+ isChecked ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+ });
+
+ final EditText recipientTextEdit = (EditText) SmsMessagingDemo.this
+ .findViewById(R.id.sms_recipient);
+ final EditText contentTextEdit = (EditText) SmsMessagingDemo.this
+ .findViewById(R.id.sms_content);
+ final TextView statusView = (TextView) SmsMessagingDemo.this.findViewById(R.id.sms_status);
+
+ // Watch for send button clicks and send text messages.
+ Button sendButton = (Button) findViewById(R.id.sms_send_message);
+ sendButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (TextUtils.isEmpty(recipientTextEdit.getText())) {
+ Toast.makeText(SmsMessagingDemo.this, "Please enter a message recipient.",
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (TextUtils.isEmpty(contentTextEdit.getText())) {
+ Toast.makeText(SmsMessagingDemo.this, "Please enter a message body.",
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ recipientTextEdit.setEnabled(false);
+ contentTextEdit.setEnabled(false);
+
+ SmsManager sms = SmsManager.getDefault();
+
+ List<String> messages = sms.divideMessage(contentTextEdit.getText().toString());
+
+ String recipient = recipientTextEdit.getText().toString();
+ for (String message : messages) {
+ sms.sendTextMessage(recipient, null, message, PendingIntent.getBroadcast(
+ SmsMessagingDemo.this, 0, new Intent(ACTION_SMS_SENT), 0), null);
+ }
+ }
+ });
+
+ // Register broadcast receivers for SMS sent and delivered intents
+ registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String message = null;
+ boolean error = true;
+ switch (getResultCode()) {
+ case Activity.RESULT_OK:
+ message = "Message sent!";
+ error = false;
+ break;
+ case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
+ message = "Error.";
+ break;
+ case SmsManager.RESULT_ERROR_NO_SERVICE:
+ message = "Error: No service.";
+ break;
+ case SmsManager.RESULT_ERROR_NULL_PDU:
+ message = "Error: Null PDU.";
+ break;
+ case SmsManager.RESULT_ERROR_RADIO_OFF:
+ message = "Error: Radio off.";
+ break;
+ }
+
+ recipientTextEdit.setEnabled(true);
+ contentTextEdit.setEnabled(true);
+ contentTextEdit.setText("");
+
+ statusView.setText(message);
+ statusView.setTextColor(error ? Color.RED : Color.GREEN);
+ }
+ }, new IntentFilter(ACTION_SMS_SENT));
+ }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/os/SmsReceivedDialog.java b/samples/ApiDemos/src/com/example/android/apis/os/SmsReceivedDialog.java
new file mode 100644
index 0000000..0df93a7
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/os/SmsReceivedDialog.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.os;
+
+import java.util.Locale;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeech.OnInitListener;
+import android.util.Log;
+
+import com.example.android.apis.R;
+
+public class SmsReceivedDialog extends Activity implements OnInitListener {
+ private static final String TAG = "SmsReceivedDialog";
+
+ private static final int DIALOG_SHOW_MESSAGE = 1;
+
+ public static final String SMS_FROM_ADDRESS_EXTRA = "com.example.android.apis.os.SMS_FROM_ADDRESS";
+ public static final String SMS_FROM_DISPLAY_NAME_EXTRA = "com.example.android.apis.os.SMS_FROM_DISPLAY_NAME";
+ public static final String SMS_MESSAGE_EXTRA = "com.example.android.apis.os.SMS_MESSAGE";
+
+ private TextToSpeech mTts;
+
+ private String mFromDisplayName;
+ private String mFromAddress;
+ private String mMessage;
+ private String mFullBodyString;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mFromAddress = getIntent().getExtras().getString(SMS_FROM_ADDRESS_EXTRA);
+ mFromDisplayName = getIntent().getExtras().getString(SMS_FROM_DISPLAY_NAME_EXTRA);
+ mMessage = getIntent().getExtras().getString(SMS_MESSAGE_EXTRA);
+
+ mFullBodyString = String.format(
+ getResources().getString(R.string.sms_speak_string_format),
+ mFromDisplayName,
+ mMessage);
+
+ showDialog(DIALOG_SHOW_MESSAGE);
+ mTts = new TextToSpeech(this, this);
+ }
+
+ public void onInit(int status) {
+ if (status == TextToSpeech.SUCCESS) {
+ int result = mTts.setLanguage(Locale.US);
+ if (result == TextToSpeech.LANG_MISSING_DATA
+ || result == TextToSpeech.LANG_NOT_SUPPORTED) {
+ Log.e(TAG, "TTS language is not available.");
+ } else {
+ mTts.speak(mFullBodyString, TextToSpeech.QUEUE_ADD, null);
+ }
+ } else {
+ // Initialization failed.
+ Log.e(TAG, "Could not initialize TTS.");
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case DIALOG_SHOW_MESSAGE:
+ return new AlertDialog.Builder(this)
+ .setIcon(android.R.drawable.ic_dialog_email)
+ .setTitle("Message Received")
+ .setMessage(mFullBodyString)
+ .setPositiveButton(R.string.reply, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ // Begin creating the reply with the SmsMessagingDemo activity
+ Intent i = new Intent();
+ i.setClass(SmsReceivedDialog.this, SmsMessagingDemo.class);
+ i.putExtra(SmsMessagingDemo.SMS_RECIPIENT_EXTRA, mFromAddress);
+ startActivity(i);
+
+ dialog.dismiss();
+ finish();
+ }
+ })
+ .setNegativeButton(R.string.dismiss, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dialog.dismiss();
+ finish();
+ }
+ })
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ }).create();
+ }
+ return null;
+ }
+}
diff --git a/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp b/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp
index 578fe6d..6eed39e 100644
--- a/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp
+++ b/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp
@@ -185,11 +185,18 @@
gLogI.log(kError_ANPLogType, " ------ %p the plugin did not request draw events", inst());
break;
case kLifecycle_ANPEventType:
- if (evt->data.lifecycle.action == kOnLoad_ANPLifecycleAction) {
- gLogI.log(kDebug_ANPLogType, " ------ %p the plugin received an onLoad event", inst());
- return 1;
+ switch (evt->data.lifecycle.action) {
+ case kOnLoad_ANPLifecycleAction:
+ gLogI.log(kDebug_ANPLogType, " ------ %p onLoad", inst());
+ return 1;
+ case kOnScreen_ANPLifecycleAction:
+ gLogI.log(kDebug_ANPLogType, " ------ %p onScreen", inst());
+ return 1;
+ case kOffScreen_ANPLifecycleAction:
+ gLogI.log(kDebug_ANPLogType, " ------ %p offScreen", inst());
+ return 1;
}
- break;
+ break; // end kLifecycle_ANPEventType
case kTouch_ANPEventType:
if (kDown_ANPTouchAction == evt->data.touch.action)
return kHandleLongPress_ANPTouchResult | kHandleDoubleTap_ANPTouchResult;
diff --git a/samples/BrowserPlugin/jni/paint/PaintPlugin.cpp b/samples/BrowserPlugin/jni/paint/PaintPlugin.cpp
index e478897..1ec86c2 100644
--- a/samples/BrowserPlugin/jni/paint/PaintPlugin.cpp
+++ b/samples/BrowserPlugin/jni/paint/PaintPlugin.cpp
@@ -37,6 +37,7 @@
extern ANPSurfaceInterfaceV0 gSurfaceI;
extern ANPSystemInterfaceV0 gSystemI;
extern ANPTypefaceInterfaceV0 gTypefaceI;
+extern ANPWindowInterfaceV0 gWindowI;
///////////////////////////////////////////////////////////////////////////////
@@ -49,6 +50,7 @@
memset(&m_drawingSurface, 0, sizeof(m_drawingSurface));
memset(&m_inputToggle, 0, sizeof(m_inputToggle));
memset(&m_colorToggle, 0, sizeof(m_colorToggle));
+ memset(&m_fullScreenToggle, 0, sizeof(m_fullScreenToggle));
memset(&m_clearSurface, 0, sizeof(m_clearSurface));
// initialize the drawing surface
@@ -170,7 +172,7 @@
m_inputToggle.top - fontMetrics.fTop, m_paintSurface);
// draw the color selector button
- m_colorToggle.left = (W/2) - (buttonWidth/2);
+ m_colorToggle.left = (W/3) - (buttonWidth/2);
m_colorToggle.top = H - buttonHeight - 5;
m_colorToggle.right = m_colorToggle.left + buttonWidth;
m_colorToggle.bottom = m_colorToggle.top + buttonHeight;
@@ -179,6 +181,17 @@
gCanvasI.drawText(canvas, colorText, strlen(colorText), m_colorToggle.left + 5,
m_colorToggle.top - fontMetrics.fTop, m_paintSurface);
+ // draw the full-screen toggle button
+ m_fullScreenToggle.left = ((W*2)/3) - (buttonWidth/2);
+ m_fullScreenToggle.top = H - buttonHeight - 5;
+ m_fullScreenToggle.right = m_fullScreenToggle.left + buttonWidth;
+ m_fullScreenToggle.bottom = m_fullScreenToggle.top + buttonHeight;
+ gCanvasI.drawRect(canvas, &m_fullScreenToggle, m_paintButton);
+ const char* fullScreenText = "Full";
+ gCanvasI.drawText(canvas, fullScreenText, strlen(fullScreenText),
+ m_fullScreenToggle.left + 5,
+ m_fullScreenToggle.top - fontMetrics.fTop, m_paintSurface);
+
// draw the clear canvas button
m_clearSurface.left = W - buttonWidth - 5;
m_clearSurface.top = H - buttonHeight - 5;
@@ -306,6 +319,8 @@
toggleInputMethod();
else if (rect == &m_colorToggle)
togglePaintColor();
+ else if (rect == &m_fullScreenToggle)
+ gWindowI.requestFullScreen(inst());
else if (rect == &m_clearSurface)
drawCleanPlugin();
}
@@ -316,6 +331,12 @@
switch (evt->data.other[0]) {
case kSurfaceCreated_CustomEvent:
gLogI.log(kDebug_ANPLogType, " ---- customEvent: surfaceCreated");
+ /* The second draw call is added to cover up a problem in this
+ plugin and is not a recommended usage pattern. This plugin
+ does not correctly make partial updates to the double
+ buffered surface and this second call hides that problem.
+ */
+ drawCleanPlugin();
drawCleanPlugin();
break;
case kSurfaceChanged_CustomEvent: {
@@ -358,6 +379,8 @@
return &m_inputToggle;
else if (fx > m_colorToggle.left && fx < m_colorToggle.right && fy > m_colorToggle.top && fy < m_colorToggle.bottom)
return &m_colorToggle;
+ else if (fx > m_fullScreenToggle.left && fx < m_fullScreenToggle.right && fy > m_fullScreenToggle.top && fy < m_fullScreenToggle.bottom)
+ return &m_fullScreenToggle;
else if (fx > m_clearSurface.left && fx < m_clearSurface.right && fy > m_clearSurface.top && fy < m_clearSurface.bottom)
return &m_clearSurface;
else
diff --git a/samples/BrowserPlugin/jni/paint/PaintPlugin.h b/samples/BrowserPlugin/jni/paint/PaintPlugin.h
index 4867a70..035e51b 100644
--- a/samples/BrowserPlugin/jni/paint/PaintPlugin.h
+++ b/samples/BrowserPlugin/jni/paint/PaintPlugin.h
@@ -58,6 +58,7 @@
ANPRectF m_drawingSurface;
ANPRectF m_inputToggle;
ANPRectF m_colorToggle;
+ ANPRectF m_fullScreenToggle;
ANPRectF m_clearSurface;
ANPPaint* m_paintSurface;
diff --git a/testrunner/coverage_targets.xml b/testrunner/coverage_targets.xml
index 869c905..09da0c8 100644
--- a/testrunner/coverage_targets.xml
+++ b/testrunner/coverage_targets.xml
@@ -52,9 +52,7 @@
<src path="wifi/java" />
</coverage_target>
<coverage_target name="android.test.runner"
- build_path="frameworks/base/test-runner" type="JAVA_LIBRARIES">
- <src path="." />
- </coverage_target>
+ build_path="frameworks/base/test-runner" type="JAVA_LIBRARIES" />
<!-- apps -->
<coverage_target name="ApiDemos" build_path="development/samples/ApiDemos"
diff --git a/testrunner/runtest.py b/testrunner/runtest.py
index 495b6a7..d53312f 100755
--- a/testrunner/runtest.py
+++ b/testrunner/runtest.py
@@ -120,6 +120,12 @@
help="Restrict test to a specific java package")
parser.add_option("-z", "--size", dest="test_size",
help="Restrict test to a specific test size")
+ parser.add_option("--annotation", dest="test_annotation",
+ help="Include only those tests tagged with a specific"
+ " annotation")
+ parser.add_option("--not-annotation", dest="test_not_annotation",
+ help="Exclude any tests tagged with a specific"
+ " annotation")
parser.add_option("-u", "--user-tests-file", dest="user_tests_file",
metavar="FILE", default=user_test_default,
help="Alternate source of user test definitions")
diff --git a/testrunner/test_defs.xml b/testrunner/test_defs.xml
index 9b3b7ca..381190c 100644
--- a/testrunner/test_defs.xml
+++ b/testrunner/test_defs.xml
@@ -80,9 +80,9 @@
continuous="true" />
<test name="frameworks-testrunner"
- build_path="frameworks/base/test-runner/tests"
+ build_path="frameworks/base/test-runner"
package="com.android.frameworks.testrunner.tests"
- coverage_target="framework"
+ coverage_target="android.test.runner"
continuous="true" />
<test name="frameworks-vpn"
diff --git a/testrunner/test_defs/instrumentation_test.py b/testrunner/test_defs/instrumentation_test.py
index c932a0b..f0a8656 100644
--- a/testrunner/test_defs/instrumentation_test.py
+++ b/testrunner/test_defs/instrumentation_test.py
@@ -131,6 +131,10 @@
instrumentation_args["suiteAssignment"] = "true"
if options.coverage:
instrumentation_args["coverage"] = "true"
+ if options.test_annotation:
+ instrumentation_args["annotation"] = options.test_annotation
+ if options.test_not_annotation:
+ instrumentation_args["notAnnotation"] = options.test_not_annotation
if options.preview:
adb_cmd = adb.PreviewInstrumentationCommand(
package_name=self.GetPackageName(),
diff --git a/tools/apkcheck/Android.mk b/tools/apkcheck/Android.mk
new file mode 100644
index 0000000..c388939
--- /dev/null
+++ b/tools/apkcheck/Android.mk
@@ -0,0 +1,42 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# We use copy-file-to-new-target so that the installed
+# script file's timestamp is at least as new as the
+# .jar file it wraps.
+
+# the execution script
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := apkcheck
+
+include $(BUILD_SYSTEM)/base_rules.mk
+
+$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/apkcheck$(COMMON_JAVA_PACKAGE_SUFFIX)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/apkcheck | $(ACP)
+ @echo "Copy: $(PRIVATE_MODULE) ($@)"
+ $(copy-file-to-new-target)
+ $(hide) chmod 755 $@
+
+# the other stuff
+# ============================================================
+subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
+ src \
+ ))
+
+include $(subdirs)
diff --git a/tools/apkcheck/README.txt b/tools/apkcheck/README.txt
new file mode 100644
index 0000000..70dcd9a
--- /dev/null
+++ b/tools/apkcheck/README.txt
@@ -0,0 +1,179 @@
+Android APK Checker
+
+This compares the set of classes, fields, and methods used by an Android
+application against the published API. It identifies and reports the
+use of any unpublished members or methods.
+
+The public API description files live in the source tree, in
+frameworks/base/api/. The tip-of-tree version is in "current.xml",
+and each officially released API has a numbered file (e.g. "6.xml").
+They're generated from the sources, and can take into acount javadoc
+annotations like "@hide" in comments.
+
+The dependency set for an APK can be generated with "dexdeps". It finds
+all classes, fields, and methods that are referenced by classes.dex but not
+defined locally. The tool can't easily tell anything about a dependency
+beyond the name (e.g. whether a class is a static or non-static inner
+class), so while the output from dexdeps is similar in structure to the
+API XML file, it has much less detail.
+
+
+==== Usage ====
+
+% apkcheck [options] public-api.xml apk1.xml ...
+
+Provide the public API data file of choice, and one or more XML files
+generated by dexdeps. The time required to parse and manipulate the
+public API XML file is generally much larger than the time required to
+analyze the APK, so if you have a large set of APKs it's best to run them
+through in large batches.
+
+Options:
+
+ --help
+ Show options summary.
+
+ --uses-library=<lib.xml>
+ Load additional public API list. This is intended for APKs that
+ use "uses-library" directives to pull in external libraries. Since
+ the external libraries are not part of the public API, their use
+ would otherwise be flagged as illegal by apkcheck.
+
+ --ignore-package=<package-name>
+ Ignore errors generated by references to the named package (e.g.
+ "com.google.android.maps"). Warnings will be generated instead.
+ Useful for ignoring references to shared library content when
+ XML API data is not available.
+
+ --[no-]warn
+ Enable or disable warning messages. These are disabled by default.
+
+ --[no-]error
+ Enable or disable error messages. These are enabled by default. If
+ you disable both warnings and errors you will only see a summary.
+
+In some cases involving generic signatures it may not be possible
+to accurately reconstruct the public API. Some popular cases have
+been hard-coded into the program. They can be included by specifying
+"--uses-library=BUILTIN".
+
+Example use:
+
+% dexdeps out/target/product/sapphire/system/app/Gmail.apk > Gmail.apk.xml
+% apkcheck --uses-library=BUILTIN frameworks/base/api/current.xml Gmail.apk.xml
+Gmail.apk.xml: summary: 0 errors, 15 warnings
+
+
+==== Limitations ====
+
+The API XML files have some ambiguous entries and are missing important
+pieces. A summary of the issues follows.
+
+(1) Class names are not in binary form
+
+Example:
+
+ type="android.os.Parcelable.Creator"
+
+This could be a Creator class in the package android.os.Parcelable,
+or Parcelable.Creator in the package android.os. We can guess based on
+capitalization, but that's unreliable.
+
+The API XML does specify each package in a <package> tag, so we should have
+the full set of packages available. From this we can remove one element
+at a time from the right until we match a known package. This will work
+unless "android.os" and "android.os.Parcelable" are both valid packages.
+
+
+(2) Public enums are not enumerated
+
+Enumeration classes are included, and always have two methods ("valueOf"
+and "values"). What isn't included are entries for the fields representing
+the enumeration values. This makes it look like an APK is referring
+to non-public fields in the class.
+
+If apkcheck sees a reference to an unknown field, and the field's defining
+class appears to be an Enum (the superclass is java.lang.Enum), we emit
+a warning instead of an error.
+
+
+(3) Public annotation methods are not listed
+
+Annotation classes have trivial entries that show only the class name
+and "implements java.lang.annotation.Annotation". It is not possible
+to verify that a method call on an annotation is valid.
+
+If apkcheck sees a method call to an unknown method, and the class appears
+to be an annotation (extends Object, implements Annotation, defines no
+fields or methods), we emit a warning instead of an error.
+
+
+(4) Covariant return types
+
+Suppose a class defines a method "public Foo gimmeFoo()". Any subclass
+that overrides that method must also return Foo, so it would seem that
+there's no need to emit a method entry for gimmeFoo() in the subclasses.
+
+However, it's possible to override gimmeFoo with "public MegaFoo
+gimmeFoo()" so long as MegaFoo is an instance of Foo. In that case it
+is necessary to emit a new method entry, but the public API XML generator
+does not.
+
+If apkcheck can't find an exact match for a method reference, but can
+find a method that matches on everything but the return type, it will
+emit a warning instead of an error. (We could be more thorough and try
+to verify that the return types are related, but that's more trouble than
+it's worth.)
+
+
+(5) Generic signatures
+
+When generic signatures are used, the public API file will contain
+entries like these:
+
+ <parameter name="key" type="K">
+ <parameter name="others" type="E...">
+ <parameter name="map" type="java.util.Map<? extends K, ? extends V>">
+
+The generic types are generally indistinguishable from classes in the
+default package (i.e. that have no package name). In most cases they're
+a single letter, so apkcheck includes a kluge that converts single-letter
+class names to java.lang.Object.
+
+This often works, but falls apart in a few cases. For example:
+
+ public <T extends Parcelable> T getParcelableExtra(String name) {
+ return mExtras == null ? null : mExtras.<T>getParcelable(name);
+ }
+
+This is emitted as:
+
+ <method name="getParcelableExtra" return="T">
+
+which gets converted to java.lang.Object. Unfortunately the APK wants
+a method with a more specific return type (android.os.Parcelable), so
+the lookup fails.
+
+There is no way to recover the actual type, because the generic signature
+details are not present in the XML. This particular case will be handled
+as a covariant return type. When the generic type is in the parameter
+list, though, this isn't handled so easily.
+
+These cases are relatively few, so they were handled by baking the
+signatures into the code (--uses-library=BUILTIN). (At some point it
+may be worthwhile to try a little harder here.)
+
+
+(6) Use of opaque non-public types
+
+Some classes are not meant for public consumption, but are still referred
+to by application code. For example, an opaque type might be passed to
+the app as a cookie.
+
+Another example is the Dalvik annotation classes, like
+dalvik.annotation.InnerClass. These are emitted by "dx", and referenced
+from the DEX file, but not intended to be used by application code.
+
+If an APK refers to a non-public class, but doesn't access any fields
+or methods, a warning is emitted instead of an error.
+
diff --git a/tools/apkcheck/etc/apkcheck b/tools/apkcheck/etc/apkcheck
new file mode 100644
index 0000000..78af93a
--- /dev/null
+++ b/tools/apkcheck/etc/apkcheck
@@ -0,0 +1,46 @@
+#!/bin/bash
+#
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+libdir=`dirname $progdir`/framework
+
+javaOpts=""
+while expr "x$1" : 'x-J' >/dev/null; do
+ opt=`expr "$1" : '-J\(.*\)'`
+ javaOpts="${javaOpts} -${opt}"
+ shift
+done
+
+exec java $javaOpts -jar $libdir/apkcheck.jar "$@"
diff --git a/tools/apkcheck/etc/manifest.txt b/tools/apkcheck/etc/manifest.txt
new file mode 100644
index 0000000..aa4fef2
--- /dev/null
+++ b/tools/apkcheck/etc/manifest.txt
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.apkcheck.ApkCheck
diff --git a/tools/apkcheck/src/Android.mk b/tools/apkcheck/src/Android.mk
new file mode 100644
index 0000000..abc813a
--- /dev/null
+++ b/tools/apkcheck/src/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+
+# apkcheck java library
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_JAR_MANIFEST := ../etc/manifest.txt
+
+LOCAL_MODULE:= apkcheck
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/tools/apkcheck/src/com/android/apkcheck/ApiDescrHandler.java b/tools/apkcheck/src/com/android/apkcheck/ApiDescrHandler.java
new file mode 100644
index 0000000..427a20f
--- /dev/null
+++ b/tools/apkcheck/src/com/android/apkcheck/ApiDescrHandler.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apkcheck;
+
+import org.xml.sax.*;
+import org.xml.sax.helpers.*;
+import java.io.*;
+
+
+/**
+ * Provides implementation for SAX parser.
+ */
+class ApiDescrHandler extends DefaultHandler {
+ /*
+ * Uber-container.
+ */
+ private ApiList mApiList;
+
+ /*
+ * Temporary objects, used as containers while we accumulate the
+ * innards.
+ */
+ private PackageInfo mCurrentPackage = null;
+ private ClassInfo mCurrentClass = null;
+ private MethodInfo mCurrentMethod = null;
+
+ /**
+ * Constructs an ApiDescrHandler.
+ *
+ * @param fileName Source file name, used for debugging.
+ */
+ public ApiDescrHandler(ApiList apiList) {
+ mApiList = apiList;
+ }
+
+ /**
+ * Returns the ApiList in its current state. Generally only
+ * makes sense to call here after parsing is completed.
+ */
+ public ApiList getApiList() {
+ return mApiList;
+ }
+
+ /**
+ * Processes start tags. If the file is malformed we will likely
+ * NPE, but this is captured by the caller.
+ *
+ * We currently assume that packages and classes only appear once,
+ * so all classes associated with a package are wrapped in a singular
+ * instance of <package>. We may want to remove this assumption
+ * by attempting to find an existing package/class with the same name.
+ */
+ @Override
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) {
+
+ if (qName.equals("package")) {
+ /* top-most element */
+ mCurrentPackage = mApiList.getOrCreatePackage(
+ attributes.getValue("name"));
+ } else if (qName.equals("class") || qName.equals("interface")) {
+ /* get class, gather fields/methods and interfaces */
+ mCurrentClass = mCurrentPackage.getOrCreateClass(
+ attributes.getValue("name"),
+ attributes.getValue("extends"),
+ attributes.getValue("static"));
+ } else if (qName.equals("implements")) {
+ /* add name of interface to current class */
+ mCurrentClass.addInterface(attributes.getValue("name"));
+ } else if (qName.equals("method")) {
+ /* hold object while we gather parameters */
+ mCurrentMethod = new MethodInfo(attributes.getValue("name"),
+ attributes.getValue("return"));
+ } else if (qName.equals("constructor")) {
+ /* like "method", but has no name or return type */
+ mCurrentMethod = new MethodInfo("<init>", "void");
+
+ /*
+ * If this is a non-static inner class, we want to add the
+ * "hidden" outer class parameter as the first parameter.
+ * We can tell if it's an inner class because the class name
+ * will include a '$' (it has been normalized already).
+ */
+ String staticClass = mCurrentClass.getStatic();
+ if (staticClass == null) {
+ /*
+ * We're parsing an APK file, which means we can't know
+ * if the class we're referencing is static or not. We
+ * also already have the "secret" first parameter
+ * represented in the method parameter list, so we don't
+ * need to insert it here.
+ */
+ } else if ("false".equals(staticClass)) {
+ String className = mCurrentClass.getName();
+ int dollarIndex = className.indexOf('$');
+ if (dollarIndex >= 0) {
+ String outerClass = className.substring(0, dollarIndex);
+ //System.out.println("--- inserting " +
+ // mCurrentPackage.getName() + "." + outerClass +
+ // " into constructor for " + className);
+ mCurrentMethod.addParameter(mCurrentPackage.getName() +
+ "." + outerClass);
+ }
+ }
+ } else if (qName.equals("field")) {
+ /* add to current class */
+ FieldInfo fInfo = new FieldInfo(attributes.getValue("name"),
+ attributes.getValue("type"));
+ mCurrentClass.addField(fInfo);
+ } else if (qName.equals("parameter")) {
+ /* add to current method */
+ mCurrentMethod.addParameter(attributes.getValue("type"));
+ }
+ }
+
+ /**
+ * Processes end tags. Generally these add the under-construction
+ * item to the appropriate container.
+ */
+ @Override
+ public void endElement(String uri, String localName, String qName) {
+ if (qName.equals("method") || qName.equals("constructor")) {
+ /* add method to class */
+ mCurrentClass.addMethod(mCurrentMethod);
+ mCurrentMethod = null;
+ } else if (qName.equals("class") || qName.equals("interface")) {
+ mCurrentClass = null;
+ } else if (qName.equals("package")) {
+ mCurrentPackage = null;
+ }
+ }
+}
+
diff --git a/tools/apkcheck/src/com/android/apkcheck/ApiList.java b/tools/apkcheck/src/com/android/apkcheck/ApiList.java
new file mode 100644
index 0000000..9eb7001
--- /dev/null
+++ b/tools/apkcheck/src/com/android/apkcheck/ApiList.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apkcheck;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * Holds a list of API members, including classes, fields, and methods.
+ */
+public class ApiList {
+ private HashMap<String,PackageInfo> mPackageList;
+ private String mDebugString;
+ private int mWarnings, mErrors;
+
+ /**
+ * Constructs an ApiList.
+ *
+ * @param debugString Identification string useful for debugging.
+ */
+ public ApiList(String debugString) {
+ mPackageList = new HashMap<String,PackageInfo>();
+ mDebugString = debugString;
+ }
+
+ /**
+ * Returns the source filename. Useful for debug messages only.
+ */
+ public String getDebugString() {
+ return mDebugString;
+ }
+
+ /**
+ * Increment the number of warnings associated with this API list.
+ */
+ public void incrWarnings() {
+ mWarnings++;
+ }
+
+ /**
+ * Increment the errors of warnings associated with this API list.
+ */
+ public void incrErrors() {
+ mErrors++;
+ }
+
+ /**
+ * Returns the number of warnings associated with this API list.
+ */
+ public int getWarningCount() {
+ return mWarnings;
+ }
+
+ /**
+ * Returns the number of errors associated with this API list.
+ */
+ public int getErrorCount() {
+ return mErrors;
+ }
+
+ /**
+ * Retrieves the named package.
+ *
+ * @return the package, or null if no match was found
+ */
+ public PackageInfo getPackage(String name) {
+ return mPackageList.get(name);
+ }
+
+ /**
+ * Retrieves the named package, creating it if it doesn't already
+ * exist.
+ */
+ public PackageInfo getOrCreatePackage(String name) {
+ PackageInfo pkgInfo = mPackageList.get(name);
+ if (pkgInfo == null) {
+ pkgInfo = new PackageInfo(name);
+ mPackageList.put(name, pkgInfo);
+ }
+ return pkgInfo;
+ }
+
+ /**
+ * Returns an iterator for the set of known packages.
+ */
+ public Iterator<PackageInfo> getPackageIterator() {
+ return mPackageList.values().iterator();
+ }
+}
+
diff --git a/tools/apkcheck/src/com/android/apkcheck/ApkCheck.java b/tools/apkcheck/src/com/android/apkcheck/ApkCheck.java
new file mode 100644
index 0000000..65d8ca0
--- /dev/null
+++ b/tools/apkcheck/src/com/android/apkcheck/ApkCheck.java
@@ -0,0 +1,431 @@
+/*
+ * 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.apkcheck;
+
+import org.xml.sax.*;
+import org.xml.sax.helpers.*;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+
+
+/**
+ * Checks an APK's dependencies against the published API specification.
+ *
+ * We need to read two XML files (spec and APK) and perform some operations
+ * on the elements. The file formats are similar but not identical, so
+ * we distill it down to common elements.
+ *
+ * We may also want to read some additional API lists representing
+ * libraries that would be included with a "uses-library" directive.
+ *
+ * For performance we want to allow processing of multiple APKs so
+ * we don't have to re-parse the spec file each time.
+ */
+public class ApkCheck {
+ /* keep track of current APK file name, for error messages */
+ private static ApiList sCurrentApk;
+
+ /* show warnings? */
+ private static boolean sShowWarnings = false;
+ /* show errors? */
+ private static boolean sShowErrors = true;
+
+ /* names of packages we're allowed to ignore */
+ private static HashSet<String> sIgnorablePackages = new HashSet<String>();
+
+
+ /**
+ * Program entry point.
+ */
+ public static void main(String[] args) {
+ ApiList apiDescr = new ApiList("public-api");
+
+ if (args.length < 2) {
+ usage();
+ return;
+ }
+
+ /* process args */
+ int idx;
+ for (idx = 0; idx < args.length; idx++) {
+ if (args[idx].equals("--help")) {
+ usage();
+ return;
+ } else if (args[idx].startsWith("--uses-library=")) {
+ String libName = args[idx].substring(args[idx].indexOf('=')+1);
+ if ("BUILTIN".equals(libName)) {
+ Reader reader = Builtin.getReader();
+ if (!parseXml(apiDescr, reader, "BUILTIN"))
+ return;
+ } else {
+ if (!parseApiDescr(apiDescr, libName))
+ return;
+ }
+ } else if (args[idx].startsWith("--ignore-package=")) {
+ String pkgName = args[idx].substring(args[idx].indexOf('=')+1);
+ sIgnorablePackages.add(pkgName);
+ } else if (args[idx].equals("--warn")) {
+ sShowWarnings = true;
+ } else if (args[idx].equals("--no-warn")) {
+ sShowWarnings = false;
+ } else if (args[idx].equals("--error")) {
+ sShowErrors = true;
+ } else if (args[idx].equals("--no-error")) {
+ sShowErrors = false;
+
+ } else if (args[idx].startsWith("--")) {
+ if (args[idx].equals("--")) {
+ // remainder are filenames, even if they start with "--"
+ idx++;
+ break;
+ } else {
+ // unknown option specified
+ System.err.println("ERROR: unknown option " +
+ args[idx] + " (use \"--help\" for usage info)");
+ return;
+ }
+ } else {
+ break;
+ }
+ }
+ if (idx > args.length - 2) {
+ usage();
+ return;
+ }
+
+ /* parse base API description */
+ if (!parseApiDescr(apiDescr, args[idx++]))
+ return;
+
+ /* "flatten" superclasses and interfaces */
+ sCurrentApk = apiDescr;
+ flattenInherited(apiDescr);
+
+ /* walk through list of libs we want to scan */
+ for ( ; idx < args.length; idx++) {
+ ApiList apkDescr = new ApiList(args[idx]);
+ sCurrentApk = apkDescr;
+ boolean success = parseApiDescr(apkDescr, args[idx]);
+ if (!success) {
+ if (idx < args.length-1)
+ System.err.println("Skipping...");
+ continue;
+ }
+
+ check(apiDescr, apkDescr);
+ System.out.println(args[idx] + ": summary: " +
+ apkDescr.getErrorCount() + " errors, " +
+ apkDescr.getWarningCount() + " warnings\n");
+ }
+ }
+
+ /**
+ * Prints usage statement.
+ */
+ static void usage() {
+ System.err.println("Android APK checker v1.0");
+ System.err.println("Copyright (C) 2010 The Android Open Source Project\n");
+ System.err.println("Usage: apkcheck [options] public-api.xml apk1.xml ...\n");
+ System.err.println("Options:");
+ System.err.println(" --help show this message");
+ System.err.println(" --uses-library=lib.xml load additional public API list");
+ System.err.println(" --ignore-package=pkg don't show errors for references to this package");
+ System.err.println(" --[no-]warn enable or disable display of warnings");
+ System.err.println(" --[no-]error enable or disable display of errors");
+ }
+
+ /**
+ * Opens the file and passes it to parseXml.
+ *
+ * TODO: allow '-' as an alias for stdin?
+ */
+ static boolean parseApiDescr(ApiList apiList, String fileName) {
+ boolean result = false;
+
+ try {
+ FileReader fileReader = new FileReader(fileName);
+ result = parseXml(apiList, fileReader, fileName);
+ fileReader.close();
+ } catch (IOException ioe) {
+ System.err.println("Error opening " + fileName);
+ }
+ return result;
+ }
+
+ /**
+ * Parses an XML file holding an API description.
+ *
+ * @param fileReader Data source.
+ * @param apiList Container to add stuff to.
+ * @param fileName Input file name, only used for debug messages.
+ */
+ static boolean parseXml(ApiList apiList, Reader reader,
+ String fileName) {
+ //System.out.println("--- parsing " + fileName);
+ try {
+ XMLReader xmlReader = XMLReaderFactory.createXMLReader();
+ ApiDescrHandler handler = new ApiDescrHandler(apiList);
+ xmlReader.setContentHandler(handler);
+ xmlReader.setErrorHandler(handler);
+ xmlReader.parse(new InputSource(reader));
+
+ //System.out.println("--- parsing complete");
+ //dumpApi(apiList);
+ return true;
+ } catch (SAXParseException ex) {
+ System.err.println("Error parsing " + fileName + " line " +
+ ex.getLineNumber() + ": " + ex.getMessage());
+ } catch (Exception ex) {
+ System.err.println("Error while reading " + fileName + ": " +
+ ex.getMessage());
+ ex.printStackTrace();
+ }
+
+ // failed
+ return false;
+ }
+
+ /**
+ * Expands lists of fields and methods to recursively include superclass
+ * and interface entries.
+ *
+ * The API description files have entries for every method a class
+ * declares, even if it's present in the superclass (e.g. toString()).
+ * Removal of one of these methods doesn't constitute an API change,
+ * though, so if we don't find a method in a class we need to hunt
+ * through its superclasses.
+ *
+ * We can walk up the hierarchy while analyzing the target APK,
+ * or we can "flatten" the methods declared by the superclasses and
+ * interfaces before we begin the analysis. Expanding up front can be
+ * beneficial if we're analyzing lots of APKs in one go, but detrimental
+ * to startup time if we just want to look at one small APK.
+ *
+ * It also means filling the field/method hash tables with lots of
+ * entries that never get used, possibly worsening the hash table
+ * hit rate.
+ *
+ * We only need to do this for the public API list. The dexdeps output
+ * doesn't have this sort of information anyway.
+ */
+ static void flattenInherited(ApiList pubList) {
+ Iterator<PackageInfo> pkgIter = pubList.getPackageIterator();
+ while (pkgIter.hasNext()) {
+ PackageInfo pubPkgInfo = pkgIter.next();
+
+ Iterator<ClassInfo> classIter = pubPkgInfo.getClassIterator();
+ while (classIter.hasNext()) {
+ ClassInfo pubClassInfo = classIter.next();
+
+ pubClassInfo.flattenClass(pubList);
+ }
+ }
+ }
+
+ /**
+ * Checks the APK against the public API.
+ *
+ * Run through and find the mismatches.
+ *
+ * @return true if all is well
+ */
+ static boolean check(ApiList pubList, ApiList apkDescr) {
+
+ Iterator<PackageInfo> pkgIter = apkDescr.getPackageIterator();
+ while (pkgIter.hasNext()) {
+ PackageInfo apkPkgInfo = pkgIter.next();
+ PackageInfo pubPkgInfo = pubList.getPackage(apkPkgInfo.getName());
+ boolean badPackage = false;
+
+ if (pubPkgInfo == null) {
+ // "illegal package" not a tremendously useful message
+ //apkError("Illegal package ref: " + apkPkgInfo.getName());
+ badPackage = true;
+ }
+
+ Iterator<ClassInfo> classIter = apkPkgInfo.getClassIterator();
+ while (classIter.hasNext()) {
+ ClassInfo apkClassInfo = classIter.next();
+
+ if (badPackage) {
+ /*
+ * The package is not present in the public API file,
+ * but simply saying "bad package" isn't all that
+ * useful, so we emit the names of each of the classes.
+ */
+ if (isIgnorable(apkPkgInfo)) {
+ apkWarning("Ignoring class ref: " +
+ apkPkgInfo.getName() + "." + apkClassInfo.getName());
+ } else {
+ apkError("Illegal class ref: " +
+ apkPkgInfo.getName() + "." + apkClassInfo.getName());
+ }
+ } else {
+ checkClass(pubPkgInfo, apkClassInfo);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks the class against the public API. We check the class
+ * itself and then any fields and methods.
+ */
+ static boolean checkClass(PackageInfo pubPkgInfo, ClassInfo classInfo) {
+
+ ClassInfo pubClassInfo = pubPkgInfo.getClass(classInfo.getName());
+
+ if (pubClassInfo == null) {
+ if (isIgnorable(pubPkgInfo)) {
+ apkWarning("Ignoring class ref: " +
+ pubPkgInfo.getName() + "." + classInfo.getName());
+ } else if (classInfo.hasNoFieldMethod()) {
+ apkWarning("Hidden class referenced: " +
+ pubPkgInfo.getName() + "." + classInfo.getName());
+ } else {
+ apkError("Illegal class ref: " +
+ pubPkgInfo.getName() + "." + classInfo.getName());
+ // could list specific fields/methods used
+ }
+ return false;
+ }
+
+ /*
+ * Check the contents of classInfo against pubClassInfo.
+ */
+ Iterator<FieldInfo> fieldIter = classInfo.getFieldIterator();
+ while (fieldIter.hasNext()) {
+ FieldInfo apkFieldInfo = fieldIter.next();
+ String nameAndType = apkFieldInfo.getNameAndType();
+ FieldInfo pubFieldInfo = pubClassInfo.getField(nameAndType);
+ if (pubFieldInfo == null) {
+ if (pubClassInfo.isEnum()) {
+ apkWarning("Enum field ref: " + pubPkgInfo.getName() +
+ "." + classInfo.getName() + "." + nameAndType);
+ } else {
+ apkError("Illegal field ref: " + pubPkgInfo.getName() +
+ "." + classInfo.getName() + "." + nameAndType);
+ }
+ }
+ }
+
+ Iterator<MethodInfo> methodIter = classInfo.getMethodIterator();
+ while (methodIter.hasNext()) {
+ MethodInfo apkMethodInfo = methodIter.next();
+ String nameAndDescr = apkMethodInfo.getNameAndDescriptor();
+ MethodInfo pubMethodInfo = pubClassInfo.getMethod(nameAndDescr);
+ if (pubMethodInfo == null) {
+ pubMethodInfo = pubClassInfo.getMethodIgnoringReturn(nameAndDescr);
+ if (pubMethodInfo == null) {
+ if (pubClassInfo.isAnnotation()) {
+ apkWarning("Annotation method ref: " +
+ pubPkgInfo.getName() + "." + classInfo.getName() +
+ "." + nameAndDescr);
+ } else {
+ apkError("Illegal method ref: " + pubPkgInfo.getName() +
+ "." + classInfo.getName() + "." + nameAndDescr);
+ }
+ } else {
+ apkWarning("Possibly covariant method ref: " +
+ pubPkgInfo.getName() + "." + classInfo.getName() +
+ "." + nameAndDescr);
+ }
+ }
+ }
+
+
+ return true;
+ }
+
+ /**
+ * Returns true if the package is in the "ignored" list.
+ */
+ static boolean isIgnorable(PackageInfo pkgInfo) {
+ return sIgnorablePackages.contains(pkgInfo.getName());
+ }
+
+ /**
+ * Prints a warning message about an APK problem.
+ */
+ public static void apkWarning(String msg) {
+ if (sShowWarnings) {
+ System.out.println("(warn) " + sCurrentApk.getDebugString() +
+ ": " + msg);
+ }
+ sCurrentApk.incrWarnings();
+ }
+
+ /**
+ * Prints an error message about an APK problem.
+ */
+ public static void apkError(String msg) {
+ if (sShowErrors) {
+ System.out.println(sCurrentApk.getDebugString() + ": " + msg);
+ }
+ sCurrentApk.incrErrors();
+ }
+
+ /**
+ * Recursively dumps the contents of the API. Sort order is not
+ * specified.
+ */
+ private static void dumpApi(ApiList apiList) {
+ Iterator<PackageInfo> iter = apiList.getPackageIterator();
+ while (iter.hasNext()) {
+ PackageInfo pkgInfo = iter.next();
+ dumpPackage(pkgInfo);
+ }
+ }
+
+ private static void dumpPackage(PackageInfo pkgInfo) {
+ Iterator<ClassInfo> iter = pkgInfo.getClassIterator();
+ System.out.println("PACKAGE " + pkgInfo.getName());
+ while (iter.hasNext()) {
+ ClassInfo classInfo = iter.next();
+ dumpClass(classInfo);
+ }
+ }
+
+ private static void dumpClass(ClassInfo classInfo) {
+ System.out.println(" CLASS " + classInfo.getName());
+ Iterator<FieldInfo> fieldIter = classInfo.getFieldIterator();
+ while (fieldIter.hasNext()) {
+ FieldInfo fieldInfo = fieldIter.next();
+ dumpField(fieldInfo);
+ }
+ Iterator<MethodInfo> methIter = classInfo.getMethodIterator();
+ while (methIter.hasNext()) {
+ MethodInfo methInfo = methIter.next();
+ dumpMethod(methInfo);
+ }
+ }
+
+ private static void dumpMethod(MethodInfo methInfo) {
+ System.out.println(" METHOD " + methInfo.getNameAndDescriptor());
+ }
+
+ private static void dumpField(FieldInfo fieldInfo) {
+ System.out.println(" FIELD " + fieldInfo.getNameAndType());
+ }
+}
+
diff --git a/tools/apkcheck/src/com/android/apkcheck/Builtin.java b/tools/apkcheck/src/com/android/apkcheck/Builtin.java
new file mode 100644
index 0000000..8e3b196
--- /dev/null
+++ b/tools/apkcheck/src/com/android/apkcheck/Builtin.java
@@ -0,0 +1,111 @@
+/*
+ * 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.apkcheck;
+
+import java.io.StringReader;
+
+/**
+ * Class containing "built-in" API description entries.
+ *
+ * There are some bugs in the API description file that we can't work around
+ * (notably some ambiguity with generic types). The easiest way to cope
+ * is to supply the correct definitions in an add-on file. Rather than
+ * cart around an extra file, we bake them in here.
+ */
+public class Builtin {
+ private Builtin() {}
+
+ private static final String BUILTIN =
+ "<api>\n" +
+ " <package name=\"java.util\">\n" +
+ " <class name=\"EnumSet\"\n" +
+ " extends=\"java.util.AbstractSet\">\n" +
+ " <method name=\"of\" return=\"java.util.EnumSet\">\n" +
+ " <parameter name=\"e\" type=\"java.lang.Enum\"/>\n" +
+ " </method>\n" +
+ " <method name=\"of\" return=\"java.util.EnumSet\">\n" +
+ " <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
+ " <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
+ " </method>\n" +
+ " <method name=\"of\" return=\"java.util.EnumSet\">\n" +
+ " <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
+ " <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
+ " <parameter name=\"e3\" type=\"java.lang.Enum\"/>\n" +
+ " </method>\n" +
+ " <method name=\"of\" return=\"java.util.EnumSet\">\n" +
+ " <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
+ " <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
+ " <parameter name=\"e3\" type=\"java.lang.Enum\"/>\n" +
+ " <parameter name=\"e4\" type=\"java.lang.Enum\"/>\n" +
+ " </method>\n" +
+ " <method name=\"of\" return=\"java.util.EnumSet\">\n" +
+ " <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
+ " <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
+ " <parameter name=\"e3\" type=\"java.lang.Enum\"/>\n" +
+ " <parameter name=\"e4\" type=\"java.lang.Enum\"/>\n" +
+ " <parameter name=\"e5\" type=\"java.lang.Enum\"/>\n" +
+ " </method>\n" +
+ " </class>\n" +
+
+ " </package>\n" +
+ " <package name=\"android.os\">\n" +
+
+ " <class name=\"RemoteCallbackList\"\n" +
+ " extends=\"java.lang.Object\">\n" +
+ " <method name=\"register\" return=\"boolean\">\n" +
+ " <parameter name=\"callback\" type=\"android.os.IInterface\"/>\n" +
+ " </method>\n" +
+ " <method name=\"unregister\" return=\"boolean\">\n" +
+ " <parameter name=\"callback\" type=\"android.os.IInterface\"/>\n" +
+ " </method>\n" +
+ " </class>\n" +
+
+ " <class name=\"AsyncTask\"\n" +
+ " extends=\"java.lang.Object\">\n" +
+ " <method name=\"onPostExecute\" return=\"void\">\n" +
+ " <parameter name=\"result\" type=\"java.lang.Object\"/>\n" +
+ " </method>\n" +
+ " <method name=\"onProgressUpdate\" return=\"void\">\n" +
+ " <parameter name=\"values\" type=\"java.lang.Object[]\"/>\n" +
+ " </method>\n" +
+ " <method name=\"execute\" return=\"android.os.AsyncTask\">\n" +
+ " <parameter name=\"params\" type=\"java.lang.Object[]\"/>\n" +
+ " </method>\n" +
+ " </class>\n" +
+
+ " </package>\n" +
+ " <package name=\"android.widget\">\n" +
+
+ " <class name=\"AutoCompleteTextView\"\n" +
+ " extends=\"android.widget.EditText\">\n" +
+ " <method name=\"setAdapter\" return=\"void\">\n" +
+ " <parameter name=\"adapter\" type=\"android.widget.ListAdapter\"/>\n" +
+ " </method>\n" +
+ " </class>\n" +
+
+ " </package>\n" +
+ "</api>\n"
+ ;
+
+ /**
+ * Returns the built-in definition "file".
+ */
+ public static StringReader getReader() {
+ return new StringReader(BUILTIN);
+ }
+}
+
diff --git a/tools/apkcheck/src/com/android/apkcheck/ClassInfo.java b/tools/apkcheck/src/com/android/apkcheck/ClassInfo.java
new file mode 100644
index 0000000..be2c1b1
--- /dev/null
+++ b/tools/apkcheck/src/com/android/apkcheck/ClassInfo.java
@@ -0,0 +1,358 @@
+/*
+ * 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.apkcheck;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * Container representing a class or interface with fields and methods.
+ */
+public class ClassInfo {
+ private String mName;
+ // methods are hashed on name:descriptor
+ private HashMap<String,MethodInfo> mMethodList;
+ // fields are hashed on name:type
+ private HashMap<String,FieldInfo> mFieldList;
+
+ private String mSuperclassName;
+
+ // is this a static inner class?
+ private String mIsStatic;
+
+ // holds the name of the superclass and all declared interfaces
+ private ArrayList<String> mSuperNames;
+
+ // is this an enumerated type?
+ private boolean mIsEnum;
+ // is this an annotation type?
+ private boolean mIsAnnotation;
+
+ private boolean mFlattening = false;
+ private boolean mFlattened = false;
+
+ /**
+ * Constructs a new ClassInfo with the provided class name.
+ *
+ * @param className Binary class name without the package name,
+ * e.g. "AlertDialog$Builder".
+ * @param superclassName Fully-qualified binary or non-binary superclass
+ * name (e.g. "java.lang.Enum").
+ * @param isStatic Class static attribute, may be "true", "false", or null.
+ */
+ public ClassInfo(String className, String superclassName, String isStatic) {
+ mName = className;
+ mMethodList = new HashMap<String,MethodInfo>();
+ mFieldList = new HashMap<String,FieldInfo>();
+ mSuperNames = new ArrayList<String>();
+ mIsStatic = isStatic;
+
+ /*
+ * Record the superclass name, and add it to the interface list
+ * since we'll need to do the same "flattening" work on it.
+ *
+ * Interfaces and java.lang.Object have a null value.
+ */
+ if (superclassName != null) {
+ mSuperclassName = superclassName;
+ mSuperNames.add(superclassName);
+ }
+ }
+
+ /**
+ * Returns the name of the class.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the name of the superclass.
+ */
+ public String getSuperclassName() {
+ return mSuperclassName;
+ }
+
+ /**
+ * Returns the "static" attribute.
+ *
+ * This is actually tri-state:
+ * "true" means it is static
+ * "false" means it's not static
+ * null means it's unknown
+ *
+ * The "unknown" state is associated with the APK input, while the
+ * known states are from the public API definition.
+ *
+ * This relates to the handling of the "secret" first parameter to
+ * constructors of non-static inner classes.
+ */
+ public String getStatic() {
+ return mIsStatic;
+ }
+
+ /**
+ * Returns whether or not this class is an enumerated type.
+ */
+ public boolean isEnum() {
+ assert mFlattened;
+ return mIsEnum;
+ }
+
+ /**
+ * Returns whether or not this class is an annotation type.
+ */
+ public boolean isAnnotation() {
+ assert mFlattened;
+ return mIsAnnotation;
+ }
+
+ /**
+ * Adds a field to the list.
+ */
+ public void addField(FieldInfo fieldInfo) {
+ mFieldList.put(fieldInfo.getNameAndType(), fieldInfo);
+ }
+
+ /**
+ * Retrives a field from the list.
+ *
+ * @param nameAndType fieldName:type
+ */
+ public FieldInfo getField(String nameAndType) {
+ return mFieldList.get(nameAndType);
+ }
+
+ /**
+ * Returns an iterator over all known fields.
+ */
+ public Iterator<FieldInfo> getFieldIterator() {
+ return mFieldList.values().iterator();
+ }
+
+ /**
+ * Adds a method to the list.
+ */
+ public void addMethod(MethodInfo methInfo) {
+ mMethodList.put(methInfo.getNameAndDescriptor(), methInfo);
+ }
+
+ /**
+ * Returns an iterator over all known methods.
+ */
+ public Iterator<MethodInfo> getMethodIterator() {
+ return mMethodList.values().iterator();
+ }
+
+ /**
+ * Retrieves a method from the list.
+ *
+ * @param nameAndDescr methodName:descriptor
+ */
+ public MethodInfo getMethod(String nameAndDescr) {
+ return mMethodList.get(nameAndDescr);
+ }
+
+ /**
+ * Retrieves a method from the list, matching on the part of the key
+ * before the return type.
+ *
+ * The API file doesn't include an entry for a method that overrides
+ * a method in the superclass. Ordinarily this is a good thing, but
+ * if the override uses a covariant return type then the reference
+ * to it in the APK won't match.
+ *
+ * @param nameAndDescr methodName:descriptor
+ */
+ public MethodInfo getMethodIgnoringReturn(String nameAndDescr) {
+ String shortKey = nameAndDescr.substring(0, nameAndDescr.indexOf(')')+1);
+
+ Iterator<MethodInfo> iter = getMethodIterator();
+ while (iter.hasNext()) {
+ MethodInfo methInfo = iter.next();
+ String nad = methInfo.getNameAndDescriptor();
+ if (nad.startsWith(shortKey))
+ return methInfo;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns true if the method and field lists are empty.
+ */
+ public boolean hasNoFieldMethod() {
+ return mMethodList.size() == 0 && mFieldList.size() == 0;
+ }
+
+ /**
+ * Adds an interface to the list of classes implemented by this class.
+ */
+ public void addInterface(String interfaceName) {
+ mSuperNames.add(interfaceName);
+ }
+
+ /**
+ * Flattens a class. This involves copying all methods and fields
+ * declared by the superclass and interfaces (and, recursively, their
+ * superclasses and interfaces) into the local structure.
+ *
+ * The public API file must be fully parsed before calling here.
+ *
+ * This also detects if we're an Enum or Annotation.
+ */
+ public void flattenClass(ApiList apiList) {
+ if (mFlattened)
+ return;
+
+ /*
+ * Recursive class definitions aren't allowed in Java code, but
+ * there could be one in the API definition file.
+ */
+ if (mFlattening) {
+ throw new RuntimeException("Recursive invoke; current class is "
+ + mName);
+ }
+ mFlattening = true;
+
+ /*
+ * Normalize the ambiguous types. This requires regenerating the
+ * field and method lists, because the signature is used as the
+ * hash table key.
+ */
+ normalizeTypes(apiList);
+
+ /*
+ * Figure out if this class is an enumerated type.
+ */
+ mIsEnum = "java.lang.Enum".equals(mSuperclassName);
+
+ /*
+ * Figure out if this class is an annotation type. We expect it
+ * to extend Object, implement java.lang.annotation.Annotation,
+ * and declare no fields or methods. (If the API XML file is
+ * fixed, it will declare methods; but at that point having special
+ * handling for annotations will be unnecessary.)
+ */
+ if ("java.lang.Object".equals(mSuperclassName) &&
+ mSuperNames.contains("java.lang.annotation.Annotation") &&
+ hasNoFieldMethod())
+ {
+ mIsAnnotation = true;
+ }
+
+ /*
+ * Flatten our superclass and interfaces.
+ */
+ for (int i = 0; i < mSuperNames.size(); i++) {
+ /*
+ * The contents of mSuperNames are in an ambiguous form.
+ * Normalize it to binary form before working with it.
+ */
+ String interfaceName = TypeUtils.ambiguousToBinaryName(mSuperNames.get(i),
+ apiList);
+ ClassInfo classInfo = lookupClass(interfaceName, apiList);
+ if (classInfo == null) {
+ ApkCheck.apkWarning("Class " + interfaceName +
+ " not found (super of " + mName + ")");
+ continue;
+ }
+
+ /* flatten it */
+ classInfo.flattenClass(apiList);
+
+ /* copy everything from it in here */
+ mergeFrom(classInfo);
+ }
+
+ mFlattened = true;
+ }
+
+ /**
+ * Normalizes the type names used in field and method descriptors.
+ *
+ * We call the field/method normalization function, which updates how
+ * it thinks of itself (and may be called multiple times from different
+ * classes). We then have to re-add it to the hash map because the
+ * key may have changed. (We're using an iterator, so we create a
+ * new hashmap and replace the old.)
+ */
+ private void normalizeTypes(ApiList apiList) {
+ Iterator<String> keyIter;
+
+ HashMap<String,FieldInfo> tmpFieldList = new HashMap<String,FieldInfo>();
+ keyIter = mFieldList.keySet().iterator();
+ while (keyIter.hasNext()) {
+ String key = keyIter.next();
+ FieldInfo fieldInfo = mFieldList.get(key);
+ fieldInfo.normalizeType(apiList);
+ tmpFieldList.put(fieldInfo.getNameAndType(), fieldInfo);
+ }
+ mFieldList = tmpFieldList;
+
+ HashMap<String,MethodInfo> tmpMethodList = new HashMap<String,MethodInfo>();
+ keyIter = mMethodList.keySet().iterator();
+ while (keyIter.hasNext()) {
+ String key = keyIter.next();
+ MethodInfo methodInfo = mMethodList.get(key);
+ methodInfo.normalizeTypes(apiList);
+ tmpMethodList.put(methodInfo.getNameAndDescriptor(), methodInfo);
+ }
+ mMethodList = tmpMethodList;
+ }
+
+ /**
+ * Merges the fields and methods from "otherClass" into this class.
+ *
+ * Redundant entries will be merged. We don't specify who the winner
+ * will be.
+ */
+ private void mergeFrom(ClassInfo otherClass) {
+ /*System.out.println("merging into " + getName() + ": fields=" +
+ mFieldList.size() + "/" + otherClass.mFieldList.size() +
+ ", methods=" +
+ mMethodList.size() + "/" + otherClass.mMethodList.size());*/
+
+ mFieldList.putAll(otherClass.mFieldList);
+ mMethodList.putAll(otherClass.mMethodList);
+
+ /*System.out.println(" now fields=" + mFieldList.size() +
+ ", methods=" + mMethodList.size());*/
+ }
+
+
+ /**
+ * Finds the named class in the ApiList.
+ *
+ * @param className Fully-qualified dot notation (e.g. "java.lang.String")
+ * @param apiList The hierarchy to search in.
+ * @return The class or null if not found.
+ */
+ private static ClassInfo lookupClass(String fullname, ApiList apiList) {
+ String packageName = TypeUtils.packageNameOnly(fullname);
+ String className = TypeUtils.classNameOnly(fullname);
+
+ PackageInfo pkg = apiList.getPackage(packageName);
+ if (pkg == null)
+ return null;
+ return pkg.getClass(className);
+ }
+}
+
diff --git a/tools/apkcheck/src/com/android/apkcheck/FieldInfo.java b/tools/apkcheck/src/com/android/apkcheck/FieldInfo.java
new file mode 100644
index 0000000..4ab0665
--- /dev/null
+++ b/tools/apkcheck/src/com/android/apkcheck/FieldInfo.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apkcheck;
+
+/**
+ * Container representing a method with parameters.
+ */
+public class FieldInfo {
+ private String mName;
+ private String mType;
+ private String mNameAndType;
+ private boolean mTypeNormalized;
+
+ /**
+ * Constructs a FieldInfo.
+ *
+ * @param name Field name.
+ * @param type Fully-qualified binary or non-binary type name.
+ */
+ public FieldInfo(String name, String type) {
+ mName = name;
+ mType = type;
+ }
+
+ /**
+ * Returns the combined name and type. This value is used as a hash
+ * table key.
+ */
+ public String getNameAndType() {
+ if (mNameAndType == null)
+ mNameAndType = mName + ":" + TypeUtils.typeToDescriptor(mType);
+ return mNameAndType;
+ }
+
+ /**
+ * Normalize the type used in fields.
+ */
+ public void normalizeType(ApiList apiList) {
+ if (!mTypeNormalized) {
+ String type = TypeUtils.ambiguousToBinaryName(mType, apiList);
+ if (!type.equals(mType)) {
+ /* name changed, force regen on name+type */
+ mType = type;
+ mNameAndType = null;
+ }
+ mTypeNormalized = true;
+ }
+ }
+}
+
diff --git a/tools/apkcheck/src/com/android/apkcheck/MethodInfo.java b/tools/apkcheck/src/com/android/apkcheck/MethodInfo.java
new file mode 100644
index 0000000..79e4cd5
--- /dev/null
+++ b/tools/apkcheck/src/com/android/apkcheck/MethodInfo.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apkcheck;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Container representing a method with parameters.
+ */
+public class MethodInfo {
+ private String mName;
+ private String mReturn;
+ private String mNameAndDescriptor;
+ private ArrayList<String> mParameters;
+ private boolean mParametersNormalized;
+
+ /**
+ * Constructs MethodInfo. Tuck the method return type away for
+ * later construction of the signature.
+ */
+ public MethodInfo(String name, String returnType) {
+ mName = name;
+ mReturn = returnType;
+ mParameters = new ArrayList<String>();
+ }
+
+ /**
+ * Returns the method signature. This is generated when needed.
+ */
+ public String getNameAndDescriptor() {
+ if (mNameAndDescriptor == null) {
+ StringBuilder newSig = new StringBuilder(mName);
+ newSig.append(":(");
+ for (int i = 0; i < mParameters.size(); i++) {
+ String humanType = mParameters.get(i);
+ String sigType = TypeUtils.typeToDescriptor(humanType);
+ newSig.append(sigType);
+ }
+ newSig.append(")");
+ newSig.append(TypeUtils.typeToDescriptor(mReturn));
+ mNameAndDescriptor = newSig.toString();
+ }
+ return mNameAndDescriptor;
+ }
+
+ /**
+ * Adds a parameter to the method. The "type" is a primitive or
+ * object type, formatted in human-centric form. For now we just
+ * store it.
+ */
+ public void addParameter(String type) {
+ mParameters.add(type);
+ if (mNameAndDescriptor != null) {
+ System.err.println("WARNING: late add of params to method");
+ mNameAndDescriptor = null; // force regen
+ }
+ }
+
+ /**
+ * Normalizes the types in parameter lists to unambiguous binary form.
+ *
+ * The public API file must be fully parsed before calling here,
+ * because we need the full set of package names.
+ */
+ public void normalizeTypes(ApiList apiList) {
+ if (!mParametersNormalized) {
+ mReturn = TypeUtils.ambiguousToBinaryName(mReturn, apiList);
+
+ for (int i = 0; i < mParameters.size(); i++) {
+ String fixed = TypeUtils.ambiguousToBinaryName(mParameters.get(i),
+ apiList);
+ mParameters.set(i, fixed);
+ }
+
+ mNameAndDescriptor = null; // force regen
+ mParametersNormalized = true;
+ }
+ }
+}
+
diff --git a/tools/apkcheck/src/com/android/apkcheck/PackageInfo.java b/tools/apkcheck/src/com/android/apkcheck/PackageInfo.java
new file mode 100644
index 0000000..533f917
--- /dev/null
+++ b/tools/apkcheck/src/com/android/apkcheck/PackageInfo.java
@@ -0,0 +1,79 @@
+/*
+ * 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.apkcheck;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * Container representing a package of classes and interfaces.
+ */
+public class PackageInfo {
+ private String mName;
+ private HashMap<String,ClassInfo> mClassList;
+
+ public PackageInfo(String name) {
+ mName = name;
+ mClassList = new HashMap<String,ClassInfo>();
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Retrieves the named class.
+ *
+ * @return the package, or null if no match was found
+ */
+ public ClassInfo getClass(String name) {
+ return mClassList.get(name);
+ }
+
+ /**
+ * Retrieves the named class, creating it if it doesn't already
+ * exist.
+ *
+ * @param className Binary or non-binary class name without the
+ * package name, e.g. "AlertDialog.Builder".
+ * @param superclassName Fully-qualified binary or non-binary superclass
+ * name (e.g. "java.lang.Enum").
+ * @param isStatic Class static attribute, may be "true", "false", or null.
+ */
+ public ClassInfo getOrCreateClass(String className, String superclassName,
+ String isStatic) {
+ String fixedName = TypeUtils.simpleClassNameToBinary(className);
+ ClassInfo classInfo = mClassList.get(fixedName);
+ if (classInfo == null) {
+ //System.out.println("--- creating entry for class " + fixedName +
+ // " (super=" + superclassName + ")");
+ classInfo = new ClassInfo(fixedName, superclassName, isStatic);
+ mClassList.put(fixedName, classInfo);
+ } else {
+ //System.out.println("--- returning existing class " + name);
+ }
+ return classInfo;
+ }
+
+ /**
+ * Returns an iterator for the set of classes in this package.
+ */
+ public Iterator<ClassInfo> getClassIterator() {
+ return mClassList.values().iterator();
+ }
+}
+
diff --git a/tools/apkcheck/src/com/android/apkcheck/TypeUtils.java b/tools/apkcheck/src/com/android/apkcheck/TypeUtils.java
new file mode 100644
index 0000000..6cfe031
--- /dev/null
+++ b/tools/apkcheck/src/com/android/apkcheck/TypeUtils.java
@@ -0,0 +1,279 @@
+/*
+ * 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.apkcheck;
+
+import java.util.HashMap;
+
+public class TypeUtils {
+ private void TypeUtils() {}
+
+ /*
+ * Conversions for the primitive types, as well as a few things
+ * that show up a lot so we can avoid the string manipulation.
+ */
+ private static final HashMap<String,String> sQuickConvert;
+ static {
+ sQuickConvert = new HashMap<String,String>();
+
+ sQuickConvert.put("boolean", "Z");
+ sQuickConvert.put("byte", "B");
+ sQuickConvert.put("char", "C");
+ sQuickConvert.put("short", "S");
+ sQuickConvert.put("int", "I");
+ sQuickConvert.put("float", "F");
+ sQuickConvert.put("long", "J");
+ sQuickConvert.put("double", "D");
+ sQuickConvert.put("void", "V");
+ sQuickConvert.put("java.lang.Object", "Ljava/lang/Object;");
+ sQuickConvert.put("java.lang.String", "Ljava/lang/String;");
+ sQuickConvert.put("java.util.ArrayList", "Ljava/util/ArrayList;");
+ sQuickConvert.put("java.util.HashMap", "Ljava/util/HashMap;");
+ };
+
+ /*
+ * Convert a human-centric type into something suitable for a method
+ * signature. Examples:
+ *
+ * int --> I
+ * float[] --> [F
+ * java.lang.String --> Ljava/lang/String;
+ */
+ public static String typeToDescriptor(String type) {
+ String quick = sQuickConvert.get(type);
+ if (quick != null)
+ return quick;
+
+ int arrayDepth = 0;
+ int firstPosn = -1;
+ int posn = -1;
+ while ((posn = type.indexOf('[', posn+1)) != -1) {
+ if (firstPosn == -1)
+ firstPosn = posn;
+ arrayDepth++;
+ }
+
+ /* if we found an array, strip the brackets off */
+ if (firstPosn != -1)
+ type = type.substring(0, firstPosn);
+
+ StringBuilder builder = new StringBuilder();
+ while (arrayDepth-- > 0)
+ builder.append("[");
+
+ /* retry quick convert */
+ quick = sQuickConvert.get(type);
+ if (quick != null) {
+ builder.append(quick);
+ } else {
+ builder.append("L");
+ builder.append(type.replace('.', '/'));
+ builder.append(";");
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Converts a "simple" class name into a "binary" class name. For
+ * example:
+ *
+ * SharedPreferences.Editor --> SharedPreferences$Editor
+ *
+ * Do not use this on fully-qualified class names.
+ */
+ public static String simpleClassNameToBinary(String className) {
+ return className.replace('.', '$');
+ }
+
+ /**
+ * Returns the class name portion of a fully-qualified binary class name.
+ */
+ static String classNameOnly(String typeName) {
+ int start = typeName.lastIndexOf(".");
+ if (start < 0) {
+ return typeName;
+ } else {
+ return typeName.substring(start+1);
+ }
+ }
+
+ /**
+ * Returns the package portion of a fully-qualified binary class name.
+ */
+ static String packageNameOnly(String typeName) {
+ int end = typeName.lastIndexOf(".");
+ if (end < 0) {
+ /* lives in default package */
+ return "";
+ } else {
+ return typeName.substring(0, end);
+ }
+ }
+
+
+ /**
+ * Normalizes a full class name to binary form.
+ *
+ * For example, "android.view.View.OnClickListener" could be in
+ * the "android.view" package or the "android.view.View" package.
+ * Checking capitalization is unreliable. We do have a full list
+ * of package names from the file though, so there's an excellent
+ * chance that we can identify the package that way. (Of course, we
+ * can only do this after we have finished parsing the file.)
+ *
+ * If the name has two or more dots, we need to compare successively
+ * shorter strings until we find a match in the package list.
+ *
+ * Do not call this on previously-returned output, as that may
+ * confuse the code that deals with generic signatures.
+ */
+ public static String ambiguousToBinaryName(String typeName, ApiList apiList) {
+ /*
+ * In some cases this can be a generic signature:
+ * <parameter name="collection" type="java.util.Collection<? extends E>">
+ * <parameter name="interfaces" type="java.lang.Class<?>[]">
+ * <parameter name="object" type="E">
+ *
+ * If we see a '<', strip out everything from it to the '>'. That
+ * does pretty much the right thing, though we have to deal with
+ * nested stuff like "<? extends Map<String>>".
+ *
+ * Handling the third item is ugly. If the string is a single
+ * character, change it to java.lang.Object. This is generally
+ * insufficient and also ambiguous with respect to classes in the
+ * default package, but we don't have much choice here, and it gets
+ * us through the standard collection classes. Note this is risky
+ * if somebody tries to normalize a string twice, since we could be
+ * "promoting" a primitive type.
+ */
+ typeName = stripAngleBrackets(typeName);
+ if (typeName.length() == 1) {
+ //System.out.println("converting X to Object: " + typeName);
+ typeName = "java.lang.Object";
+ } else if (typeName.length() == 3 &&
+ typeName.substring(1, 3).equals("[]")) {
+ //System.out.println("converting X[] to Object[]: " + typeName);
+ typeName = "java.lang.Object[]";
+ } else if (typeName.length() == 4 &&
+ typeName.substring(1, 4).equals("...")) {
+ //System.out.println("converting X... to Object[]: " + typeName);
+ typeName = "java.lang.Object[]";
+ }
+
+ /*
+ * Catch-all for varargs, which come in different varieties:
+ * java.lang.Object...
+ * java.lang.Class...
+ * java.lang.CharSequence...
+ * int...
+ * Progress...
+ *
+ * The latter is a generic type that we didn't catch above because
+ * it's not using a single-character descriptor.
+ *
+ * The method reference for "java.lang.Class..." will be looking
+ * for java.lang.Class[], not java.lang.Object[], so we don't want
+ * to do a blanket conversion. Similarly, "int..." turns into int[].
+ *
+ * There's not much we can do with "Progress...", unless we want
+ * to write off the default package and filter out primitive types.
+ * Probably easier to fix it up elsewhere.
+ */
+ int ellipsisIndex = typeName.indexOf("...");
+ if (ellipsisIndex >= 0) {
+ String newTypeName = typeName.substring(0, ellipsisIndex) + "[]";
+ //System.out.println("vararg " + typeName + " --> " + newTypeName);
+ typeName = newTypeName;
+ }
+
+ /*
+ * It's possible the code that generates API definition files
+ * has been fixed. If we see a '$', just return the original.
+ */
+ if (typeName.indexOf('$') >= 0)
+ return typeName;
+
+ int lastDot = typeName.lastIndexOf('.');
+ if (lastDot < 0)
+ return typeName;
+
+ /*
+ * What we have looks like some variation of these:
+ * package.Class
+ * Class.InnerClass
+ * long.package.name.Class
+ * long.package.name.Class.InnerClass
+ *
+ * We cut it off at the last '.' and test to see if it's a known
+ * package name. If not, keep moving left until we run out of dots.
+ */
+ int nextDot = lastDot;
+ while (nextDot >= 0) {
+ String testName = typeName.substring(0, nextDot);
+ if (apiList.getPackage(testName) != null) {
+ break;
+ }
+
+ nextDot = typeName.lastIndexOf('.', nextDot-1);
+ }
+
+ if (nextDot < 0) {
+ /* no package name found, convert all dots */
+ System.out.println("+++ no pkg name found on " + typeName + typeName.length());
+ typeName = typeName.replace('.', '$');
+ } else if (nextDot == lastDot) {
+ /* class name is last element; original string is fine */
+ } else {
+ /* in the middle; zap the dots in the inner class name */
+ String oldClassName = typeName;
+ typeName = typeName.substring(0, nextDot+1) +
+ typeName.substring(nextDot+1).replace('.', '$');
+ //System.out.println("+++ " + oldClassName + " --> " + typeName);
+ }
+
+ return typeName;
+ }
+
+ /**
+ * Strips out everything between '<' and '>'. This will handle
+ * nested brackets, but we're not expecting to see multiple instances
+ * in series (i.e. "<foo<bar>>" is expected, but
+ * "<foo>STUFF<bar> is not).
+ *
+ * @return the stripped string
+ */
+ private static String stripAngleBrackets(String str) {
+ /*
+ * Since we only expect to see one "run", we can just find the
+ * first '<' and the last '>'. Ideally we'd verify that they're
+ * not mismatched, but we're assuming the input file is generally
+ * correct.
+ */
+ int ltIndex = str.indexOf('<');
+ if (ltIndex < 0)
+ return str;
+
+ int gtIndex = str.lastIndexOf('>');
+ if (gtIndex < 0) {
+ System.err.println("ERROR: found '<' without '>': " + str);
+ return str; // not much we can do
+ }
+
+ return str.substring(0, ltIndex) + str.substring(gtIndex+1);
+ }
+}
+
diff --git a/tools/findunused/findunusedstrings b/tools/findunused/findunusedstrings
index 7057527..9615abc 100755
--- a/tools/findunused/findunusedstrings
+++ b/tools/findunused/findunusedstrings
@@ -28,7 +28,8 @@
if [ -d $app/res ]
then
pushd $app > /dev/null
- for i in $(grep -Rs "\(string\|plurals\) name=" res | sed 's/.*<\(string\|plurals\) name="//'|sed 's/".*$//'|sort -u)
+ # Two sed's were needed because the | operator is not supported on the mac
+ for i in $(grep -Rs "\(string\|plurals\) name=" res | sed 's/.*string name=\"//' | sed 's/.*plurals name=\"//'|sed 's/".*$//'|sort -u)
do
echo $i $(grep -Rws R.plurals.$i\\\|R.string.$i\\\|@string/$i .|wc -l)
done | grep ' 0$' | {