Merge "Add RecognizerIntent.ACTION_VOICE_SEARCH_SETTINGS, which we'll trigger from system settings. For now it'll just be triggered from within the voice search app if you choose the settings menu item."
diff --git a/Android.mk b/Android.mk
index ded8173..568352e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -75,6 +75,7 @@
core/java/android/app/IActivityWatcher.aidl \
core/java/android/app/IAlarmManager.aidl \
core/java/android/app/IBackupAgent.aidl \
+ core/java/android/app/IDevicePolicyManager.aidl \
core/java/android/app/IInstrumentationWatcher.aidl \
core/java/android/app/INotificationManager.aidl \
core/java/android/app/ISearchManager.aidl \
@@ -107,9 +108,11 @@
core/java/android/database/IContentObserver.aidl \
core/java/android/hardware/ISensorService.aidl \
core/java/android/net/IConnectivityManager.aidl \
+ core/java/android/net/INetworkManagementEventObserver.aidl \
core/java/android/os/ICheckinService.aidl \
core/java/android/os/IMessenger.aidl \
core/java/android/os/IMountService.aidl \
+ core/java/android/os/INetworkManagementService.aidl \
core/java/android/os/INetStatService.aidl \
core/java/android/os/IParentalControlCallback.aidl \
core/java/android/os/IPermissionController.aidl \
diff --git a/api/current.xml b/api/current.xml
index b690e42..527ffe3 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -177,6 +177,17 @@
visibility="public"
>
</field>
+<field name="BIND_DEVICE_ADMIN"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.permission.BIND_DEVICE_ADMIN""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="BIND_INPUT_METHOD"
type="java.lang.String"
transient="false"
@@ -188,6 +199,17 @@
visibility="public"
>
</field>
+<field name="BIND_WALLPAPER"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.permission.BIND_WALLPAPER""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="BLUETOOTH"
type="java.lang.String"
transient="false"
@@ -19831,6 +19853,602 @@
</parameter>
</method>
</interface>
+<class name="DeviceAdmin"
+ extends="android.content.BroadcastReceiver"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="DeviceAdmin"
+ type="android.app.DeviceAdmin"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getManager"
+ return="android.app.DevicePolicyManager"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
+<method name="getWho"
+ return="android.content.ComponentName"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
+<method name="onDisabled"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="onEnabled"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="onPasswordChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="onPasswordFailed"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="onPasswordSucceeded"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="onReceive"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<field name="ACTION_DEVICE_ADMIN_DISABLED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.app.action.DEVICE_ADMIN_DISABLED""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_DEVICE_ADMIN_ENABLED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.app.action.DEVICE_ADMIN_ENABLED""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_PASSWORD_CHANGED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.app.action.ACTION_PASSWORD_CHANGED""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_PASSWORD_FAILED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.app.action.ACTION_PASSWORD_FAILED""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_PASSWORD_SUCCEEDED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.app.action.ACTION_PASSWORD_SUCCEEDED""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DEVICE_ADMIN_META_DATA"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.app.device_admin""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="DeviceAdminInfo"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<constructor name="DeviceAdminInfo"
+ type="android.app.DeviceAdminInfo"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="receiver" type="android.content.pm.ResolveInfo">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+<exception name="XmlPullParserException" type="org.xmlpull.v1.XmlPullParserException">
+</exception>
+</constructor>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="dump"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pw" type="android.util.Printer">
+</parameter>
+<parameter name="prefix" type="java.lang.String">
+</parameter>
+</method>
+<method name="getActivityInfo"
+ return="android.content.pm.ActivityInfo"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getComponent"
+ return="android.content.ComponentName"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPackageName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getReceiverName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="loadIcon"
+ return="android.graphics.drawable.Drawable"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pm" type="android.content.pm.PackageManager">
+</parameter>
+</method>
+<method name="loadLabel"
+ return="java.lang.CharSequence"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pm" type="android.content.pm.PackageManager">
+</parameter>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="DevicePolicyManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="getCurrentFailedPasswordAttempts"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMaximumTimeToLock"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMinimumPasswordLength"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPasswordMode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isActivePasswordSufficient"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isAdminActive"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="who" type="android.content.ComponentName">
+</parameter>
+</method>
+<method name="lockNow"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="removeActiveAdmin"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="who" type="android.content.ComponentName">
+</parameter>
+</method>
+<method name="resetPassword"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="password" type="java.lang.String">
+</parameter>
+</method>
+<method name="setMaximumTimeToLock"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="timeMs" type="long">
+</parameter>
+</method>
+<method name="setMinimumPasswordLength"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="length" type="int">
+</parameter>
+</method>
+<method name="setPasswordMode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="mode" type="int">
+</parameter>
+</method>
+<method name="wipeData"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="ACTION_ADD_DEVICE_ADMIN"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.app.action.ADD_DEVICE_ADMIN""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_SET_NEW_PASSWORD"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.app.action.SET_NEW_PASSWORD""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_DEVICE_ADMIN"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.app.extra.DEVICE_ADMIN""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PASSWORD_MODE_ALPHANUMERIC"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3000"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PASSWORD_MODE_NUMERIC"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2000"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PASSWORD_MODE_SOMETHING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1000"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="PASSWORD_MODE_UNSPECIFIED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<class name="Dialog"
extends="java.lang.Object"
abstract="false"
@@ -31821,6 +32439,17 @@
visibility="public"
>
</method>
+<method name="getPackageCodePath"
+ return="java.lang.String"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getPackageManager"
return="android.content.pm.PackageManager"
abstract="true"
@@ -31843,6 +32472,17 @@
visibility="public"
>
</method>
+<method name="getPackageResourcePath"
+ return="java.lang.String"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getResources"
return="android.content.res.Resources"
abstract="true"
@@ -32576,6 +33216,17 @@
visibility="public"
>
</field>
+<field name="DEVICE_POLICY_SERVICE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""device_policy""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="DROPBOX_SERVICE"
type="java.lang.String"
transient="false"
@@ -40740,6 +41391,17 @@
visibility="public"
>
</field>
+<field name="resourceDirs"
+ type="java.lang.String[]"
+ transient="false"
+ volatile="false"
+ value="null"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="sharedLibraryFiles"
type="java.lang.String[]"
transient="false"
@@ -52921,6 +53583,17 @@
<parameter name="stroke" type="android.gesture.GestureStroke">
</parameter>
</method>
+<method name="clone"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="describeContents"
return="int"
abstract="false"
@@ -54110,6 +54783,17 @@
<parameter name="t" type="long">
</parameter>
</constructor>
+<method name="clone"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<field name="timestamp"
type="long"
transient="false"
@@ -54434,6 +55118,17 @@
visibility="public"
>
</method>
+<method name="clone"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="computeOrientedBoundingBox"
return="android.gesture.OrientedBoundingBox"
abstract="false"
@@ -130017,6 +130712,17 @@
visibility="public"
>
</field>
+<field name="ACTION_SEARCH_SETTINGS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.search.action.SEARCH_SETTINGS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="ACTION_SECURITY_SETTINGS"
type="java.lang.String"
transient="false"
@@ -198538,6 +199244,223 @@
</parameter>
</method>
</interface>
+<class name="NumberPicker"
+ extends="android.widget.LinearLayout"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="NumberPicker"
+ type="android.widget.NumberPicker"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<constructor name="NumberPicker"
+ type="android.widget.NumberPicker"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+</constructor>
+<method name="changeCurrent"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="current" type="int">
+</parameter>
+</method>
+<method name="getBeginRange"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</method>
+<method name="getCurrent"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getEndRange"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</method>
+<method name="setCurrent"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="current" type="int">
+</parameter>
+</method>
+<method name="setFormatter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="formatter" type="android.widget.NumberPicker.Formatter">
+</parameter>
+</method>
+<method name="setOnChangeListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.widget.NumberPicker.OnChangedListener">
+</parameter>
+</method>
+<method name="setRange"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="start" type="int">
+</parameter>
+<parameter name="end" type="int">
+</parameter>
+</method>
+<method name="setRange"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="start" type="int">
+</parameter>
+<parameter name="end" type="int">
+</parameter>
+<parameter name="displayedValues" type="java.lang.String[]">
+</parameter>
+</method>
+<method name="setSpeed"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="speed" type="long">
+</parameter>
+</method>
+<field name="TWO_DIGIT_FORMATTER"
+ type="android.widget.NumberPicker.Formatter"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<interface name="NumberPicker.Formatter"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="toString"
+ return="java.lang.String"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="int">
+</parameter>
+</method>
+</interface>
+<interface name="NumberPicker.OnChangedListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onChanged"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="picker" type="android.widget.NumberPicker">
+</parameter>
+<parameter name="oldVal" type="int">
+</parameter>
+<parameter name="newVal" type="int">
+</parameter>
+</method>
+</interface>
<class name="PopupWindow"
extends="java.lang.Object"
abstract="false"
diff --git a/camera/libcameraservice/CameraService.cpp b/camera/libcameraservice/CameraService.cpp
index fae1f26..a8e217e 100644
--- a/camera/libcameraservice/CameraService.cpp
+++ b/camera/libcameraservice/CameraService.cpp
@@ -235,6 +235,7 @@
// Callback is disabled by default
mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP;
+ mOrientation = 0;
cameraService->incUsers();
LOGV("Client::Client X (pid %d)", callingPid);
}
@@ -570,7 +571,8 @@
// wait in the createOverlay call if the previous overlay is in the
// process of being destroyed.
for (int retry = 0; retry < 50; ++retry) {
- mOverlayRef = mSurface->createOverlay(w, h, OVERLAY_FORMAT_DEFAULT);
+ mOverlayRef = mSurface->createOverlay(w, h, OVERLAY_FORMAT_DEFAULT,
+ mOrientation);
if (mOverlayRef != NULL) break;
LOGW("Overlay create failed - retrying");
usleep(20000);
@@ -601,15 +603,9 @@
CameraParameters params(mHardware->getParameters());
params.getPreviewSize(&w, &h);
- uint32_t transform = 0;
- if (params.getOrientation() ==
- CameraParameters::CAMERA_ORIENTATION_PORTRAIT) {
- LOGV("portrait mode");
- transform = ISurface::BufferHeap::ROT_90;
- }
ISurface::BufferHeap buffers(w, h, w, h,
PIXEL_FORMAT_YCbCr_420_SP,
- transform,
+ mOrientation,
0,
mHardware->getPreviewHeap());
@@ -919,12 +915,6 @@
if (mSurface != 0 && !mUseOverlay) {
int w, h;
CameraParameters params(mHardware->getParameters());
- uint32_t transform = 0;
- if (params.getOrientation() == CameraParameters::CAMERA_ORIENTATION_PORTRAIT) {
- LOGV("portrait mode");
- transform = ISurface::BufferHeap::ROT_90;
- }
-
if (size == NULL) {
params.getPictureSize(&w, &h);
} else {
@@ -935,7 +925,7 @@
LOGV("Snapshot image width=%d, height=%d", w, h);
}
ISurface::BufferHeap buffers(w, h, w, h,
- PIXEL_FORMAT_YCbCr_420_SP, transform, 0, mHardware->getRawHeap());
+ PIXEL_FORMAT_YCbCr_420_SP, mOrientation, 0, mHardware->getRawHeap());
mSurface->registerBuffers(buffers);
}
@@ -1200,6 +1190,15 @@
}
CameraParameters p(params);
+
+ // The orientation parameter is actually for CameraService, not for the camera driver.
+ if (p.getOrientation() == CameraParameters::CAMERA_ORIENTATION_PORTRAIT) {
+ LOGV("portrait mode");
+ mOrientation = ISurface::BufferHeap::ROT_90;
+ } else {
+ mOrientation = 0;
+ }
+
return mHardware->setParameters(p);
}
diff --git a/camera/libcameraservice/CameraService.h b/camera/libcameraservice/CameraService.h
index 3e3e54f..b3d20f6 100644
--- a/camera/libcameraservice/CameraService.h
+++ b/camera/libcameraservice/CameraService.h
@@ -182,6 +182,7 @@
sp<CameraService> mCameraService;
sp<ISurface> mSurface;
int mPreviewCallbackFlag;
+ int mOrientation;
sp<MediaPlayer> mMediaPlayerClick;
sp<MediaPlayer> mMediaPlayerBeep;
diff --git a/camera/tests/CameraServiceTest/CameraServiceTest.cpp b/camera/tests/CameraServiceTest/CameraServiceTest.cpp
index 29320e0..f89d9d3 100644
--- a/camera/tests/CameraServiceTest/CameraServiceTest.cpp
+++ b/camera/tests/CameraServiceTest/CameraServiceTest.cpp
@@ -283,7 +283,7 @@
virtual void postBuffer(ssize_t offset);
virtual void unregisterBuffers();
virtual sp<OverlayRef> createOverlay(
- uint32_t w, uint32_t h, int32_t format);
+ uint32_t w, uint32_t h, int32_t format, int32_t orientation);
virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage);
// new functions
@@ -346,7 +346,8 @@
}
}
-sp<OverlayRef> MSurface::createOverlay(uint32_t w, uint32_t h, int32_t format) {
+sp<OverlayRef> MSurface::createOverlay(uint32_t w, uint32_t h, int32_t format,
+ int32_t orientation) {
// We don't expect this to be called in current hardware.
ASSERT(0);
sp<OverlayRef> dummy;
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 4953f5d..68373cb 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -92,6 +92,11 @@
return;
}
+ if ("mountsd".equals(op)) {
+ runMountSd();
+ return;
+ }
+
if ("uninstall".equals(op)) {
runUninstall();
return;
@@ -637,6 +642,37 @@
}
}
+ private void runMountSd() {
+ String opt;
+ boolean mount = false;
+ while ((opt=nextOption()) != null) {
+ if (opt.equals("-m")) {
+ String mountStr = nextOptionData();
+ if (mountStr == null) {
+ System.err.println("Error: no value specified for -m");
+ showUsage();
+ return;
+ }
+ if ("true".equalsIgnoreCase(mountStr)) {
+ mount = true;
+ } else if ("false".equalsIgnoreCase(mountStr)) {
+ mount = false;
+ } else {
+ System.err.println("Error: no value specified for -m");
+ showUsage();
+ return;
+ }
+ }
+ }
+
+ try {
+ mPm.updateExternalMediaStatus(mount);
+ } catch (RemoteException e) {
+ System.err.println(e.toString());
+ System.err.println(PM_NOT_RUNNING_ERR);
+ }
+ }
+
class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
boolean finished;
boolean result;
@@ -826,6 +862,7 @@
System.err.println(" pm path PACKAGE");
System.err.println(" pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] PATH");
System.err.println(" pm uninstall [-k] PACKAGE");
+ System.err.println(" pm mountsd [-m true/false]");
System.err.println(" pm enable PACKAGE_OR_COMPONENT");
System.err.println(" pm disable PACKAGE_OR_COMPONENT");
System.err.println("");
@@ -862,6 +899,9 @@
System.err.println(" -k: keep the data and cache directories around.");
System.err.println("after the package removal.");
System.err.println("");
+ System.err.println("The mountsd command simulates mounting/unmounting sdcard.Options:");
+ System.err.println(" -m: true or false.");
+ System.err.println("");
System.err.println("The enable and disable commands change the enabled state of");
System.err.println("a given package or component (written as \"package/class\").");
}
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index ad6540a..e65cdf1 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -34,12 +34,14 @@
#include <media/stagefright/MetaData.h>
#include <media/stagefright/OMXClient.h>
#include <media/stagefright/OMXCodec.h>
+#include <media/mediametadataretriever.h>
using namespace android;
static long gNumRepetitions;
static long gMaxNumFrames; // 0 means decode all available.
static long gReproduceBug; // if not -1.
+static bool gPreferSoftwareCodec;
static int64_t getNowUs() {
struct timeval tv;
@@ -59,7 +61,9 @@
rawSource = source;
} else {
rawSource = OMXCodec::Create(
- client->interface(), meta, false /* createEncoder */, source);
+ client->interface(), meta, false /* createEncoder */, source,
+ NULL /* matchComponentName */,
+ gPreferSoftwareCodec ? OMXCodec::kPreferSoftwareCodecs : 0);
if (rawSource == NULL) {
fprintf(stderr, "Failed to instantiate decoder for '%s'.\n", mime);
@@ -219,6 +223,8 @@
fprintf(stderr, " -m max-number-of-frames-to-decode in each pass\n");
fprintf(stderr, " -b bug to reproduce\n");
fprintf(stderr, " -p(rofiles) dump decoder profiles supported\n");
+ fprintf(stderr, " -t(humbnail) extract video thumbnail\n");
+ fprintf(stderr, " -s(oftware) prefer software codec\n");
}
int main(int argc, char **argv) {
@@ -227,12 +233,14 @@
bool audioOnly = false;
bool listComponents = false;
bool dumpProfiles = false;
+ bool extractThumbnail = false;
gNumRepetitions = 1;
gMaxNumFrames = 0;
gReproduceBug = -1;
+ gPreferSoftwareCodec = false;
int res;
- while ((res = getopt(argc, argv, "han:lm:b:p")) >= 0) {
+ while ((res = getopt(argc, argv, "han:lm:b:pts")) >= 0) {
switch (res) {
case 'a':
{
@@ -274,6 +282,18 @@
break;
}
+ case 't':
+ {
+ extractThumbnail = true;
+ break;
+ }
+
+ case 's':
+ {
+ gPreferSoftwareCodec = true;
+ break;
+ }
+
case '?':
case 'h':
default:
@@ -288,6 +308,34 @@
argc -= optind;
argv += optind;
+ if (extractThumbnail) {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder = sm->getService(String16("media.player"));
+ sp<IMediaPlayerService> service =
+ interface_cast<IMediaPlayerService>(binder);
+
+ CHECK(service.get() != NULL);
+
+ sp<IMediaMetadataRetriever> retriever =
+ service->createMetadataRetriever(getpid());
+
+ CHECK(retriever != NULL);
+
+ for (int k = 0; k < argc; ++k) {
+ const char *filename = argv[k];
+
+ CHECK_EQ(retriever->setDataSource(filename), OK);
+ CHECK_EQ(retriever->setMode(METADATA_MODE_FRAME_CAPTURE_ONLY), OK);
+
+ sp<IMemory> mem = retriever->captureFrame();
+
+ printf("captureFrame(%s) => %s\n",
+ filename, mem != NULL ? "OK" : "FAILED");
+ }
+
+ return 0;
+ }
+
if (dumpProfiles) {
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder = sm->getService(String16("media.player"));
@@ -389,7 +437,8 @@
sp<MetaData> meta;
size_t i;
for (i = 0; i < numTracks; ++i) {
- meta = extractor->getTrackMetaData(i);
+ meta = extractor->getTrackMetaData(
+ i, MediaExtractor::kIncludeExtensiveMetaData);
const char *mime;
meta->findCString(kKeyMIMEType, &mime);
@@ -403,6 +452,12 @@
}
}
+ int64_t thumbTimeUs;
+ if (meta->findInt64(kKeyThumbnailTime, &thumbTimeUs)) {
+ printf("thumbnailTime: %lld us (%.2f secs)\n",
+ thumbTimeUs, thumbTimeUs / 1E6);
+ }
+
mediaSource = extractor->getTrack(i);
}
diff --git a/common/java/com/android/common/DomainNameValidator.java b/common/java/com/android/common/DomainNameValidator.java
index ad44a7d..25dc007 100644
--- a/common/java/com/android/common/DomainNameValidator.java
+++ b/common/java/com/android/common/DomainNameValidator.java
@@ -166,19 +166,13 @@
}
}
} catch (CertificateParsingException e) {
- // one way we can get here is if an alternative name starts with
- // '*' character, which is contrary to one interpretation of the
- // spec (a valid DNS name must start with a letter); there is no
- // good way around this, and in order to be compatible we proceed
- // to check the common name (ie, ignore alternative names)
- if (LOG_ENABLED) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage = "failed to parse certificate";
- }
-
- Log.v(TAG, "DomainNameValidator.matchDns(): " + errorMessage);
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage = "failed to parse certificate";
}
+
+ Log.w(TAG, "DomainNameValidator.matchDns(): " + errorMessage);
+ return false;
}
if (!hasDns) {
diff --git a/common/tests/res/raw/alt_ip_only.crt b/common/tests/res/raw/alt_ip_only.crt
new file mode 100644
index 0000000..3ac9f5a
--- /dev/null
+++ b/common/tests/res/raw/alt_ip_only.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICsjCCAZqgAwIBAgIJALrC37YAXFIeMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
+BAYTAkpQMCAXDTEwMDExMjIxMzk0NloYDzIwNjQxMDE1MjEzOTQ2WjANMQswCQYD
+VQQGEwJKUDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALr8s/4Abpby
+IYks5YCJE2nbWH7kj6XbwnRzsVP9RVC33bPoQ1M+2ZY24HqkigjQS/HEXR0s0bYh
+dewNUnTj1uGyGs6cYzsbu7x114vmVYqjxUo3hKjwfYiPeF6f3IE1vpLI7I2G32gq
+Zwm9c1/vXNHIdWQxCpFcuPA8P3YGfoApFX4pQPFplBUNAQqnjdmA68cbxxMC+1F3
+mX42D7iIEVwyVpah5HjyxjIZQlf3X7QBj0bCmkL+ibIHTALrkNNwNM6i4xzYLz/5
+14GkN9ncHY87eSOk6r53ptER6mQMhCe9qPRjSHnpWTTyj6IXTaYe+dDQw657B80w
+cSHL7Ed25zUCAwEAAaMTMBEwDwYDVR0RBAgwBocEwKgKATANBgkqhkiG9w0BAQUF
+AAOCAQEAgrwrtOWZT3fbi1AafpGaAiOBWSJqYqRhtQy0AfiZBxv1U0XaYqmZmpnq
+DVAqr0NkljowD28NBrxIFO5gBNum2ZOPDl2/5vjFn+IirUCJ9u9wS7zYkTCW2lQR
+xE7Ic3mfWv7wUbKDfjlWqP1IDHUxwkrBTAl+HnwOPiaKKk1ttwcrgS8AHlqASe03
+mlwnvJ+Stk54IneRaegL0L93sNAy63RZqnPCTxGz7eHcFwX8Jdr4sbxTxQqV6pIc
+WPjHQcWfpkFzAF5wyOq0kveVfx0g5xPhOVDd+U+q7WastbXICpCoHp9FxISmZVik
+sAyifp8agkYdzaSh55fFmKXlFnRsQw==
+-----END CERTIFICATE-----
diff --git a/common/tests/res/raw/subject_alt_only.crt b/common/tests/res/raw/subject_alt_only.crt
new file mode 100644
index 0000000..d5808fb
--- /dev/null
+++ b/common/tests/res/raw/subject_alt_only.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICvTCCAaWgAwIBAgIJALbA0TZk2YmNMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
+BAYTAkpQMCAXDTEwMDExMjIwNTg1NFoYDzIwNjQxMDE1MjA1ODU0WjANMQswCQYD
+VQQGEwJKUDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMEg6acVC9V4
+xNGoLNVLPbqBc8IvMvcsc88dF6MW3d9VagX3aeWU8c79tI/KOV/1AOakH7WYxw/w
+yD8aOX7+9BK1Hu0qKKKbSM+ycqaMthXd6xytrNDsIx5WiGUz8zTko0Gk3orIR7p7
+rPcNzB/zwtESkscqPv85aEn7S/yClNkzLfEzm3CtaYOc0tfhBMyzi/ipXzGMxUmx
+PvOLr3v/Oz5pZEQw7Kxlm4+tAtn7bJlHziQ1UW4WPIy+T3hySBEpODFiqZi7Ok3X
+Zjxdii62fgo5B2Ee7q5Amo0mUIwcQTDjJ2CLAqzYnSh3tpiPJGjEIjmRyCoMQ1bx
+7D+y7nSPIq8CAwEAAaMeMBwwGgYDVR0RBBMwEYIPd3d3LmV4YW1wbGUuY29tMA0G
+CSqGSIb3DQEBBQUAA4IBAQBsGEh+nHc0l9FJTzWqvG3qs7i6XoJZdtThCDx4HjKJ
+8GMrJtreNN4JvIxn7KC+alVbnILjzCRO+c3rsnpxKBi5cp2imjuw5Kf/x2Seimb9
+UvZbaJvBVOzy4Q1IGef9bLy3wZzy2/WfBFyvPTAkgkRaX7LN2jnYOYVhNoNFrwqe
+EWxkA6fzrpyseUEFeGFFjGxRSRCDcQ25Eq6d9rkC1x21zNtt4QwZBO0wHrTy155M
+JPRynf9244Pn0Sr/wsnmdsTRFIFYynrc51hQ7DkwbUxpcaewkZzilru/SwZ3+pPT
+9JSqm5hJ1pg5WDlPkW7c/1VA0/141N52Q8MIU+2ZpuOj
+-----END CERTIFICATE-----
diff --git a/common/tests/res/raw/subject_only.crt b/common/tests/res/raw/subject_only.crt
new file mode 100644
index 0000000..11b34e7
--- /dev/null
+++ b/common/tests/res/raw/subject_only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC0TCCAbmgAwIBAgIJANCQbJPPw31SMA0GCSqGSIb3DQEBBQUAMCcxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wIBcNMTAwMTEyMjA1ODE4
+WhgPMjA2NDEwMTUyMDU4MThaMCcxCzAJBgNVBAYTAkpQMRgwFgYDVQQDEw93d3cu
+ZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDsdUJk
+4KxADA3vlDHxNbyC27Ozw4yiSVzPTHUct471YmdDRW3orO2P5a5hRnUGV70gjH9X
+MU4oeOdWYAgXB9pxfLyr6621k1+uNrmaZtzp0ECH9twcwxNJJFDZsN7o9vt7V6Ej
+NN9weeqDr/aeQXo07a12vyVfR6jWO8jHB0e4aemwZNoYjNvM69fivQTse2ZoRVfj
+eSHhjRTX6I8ry4a31Hwt+fT1QiWWNN6o7+WOtpJAhX3eg4smhSD1svi2kOT8tdUe
+NS4hWlmXmumU9G4tI8PBurcLNTm7PB2lUlbn/IV18WavqKE/Uy/1WgAx+a1EJNdp
+i07AG1PsqaONKkf1AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAJrNsuL7fZZNC8gL
+BdePJ7DYW2e7mXANU3bCBe2BZqmXKQxKwibZnEsqA+yMLqcSd8uxISlyHY2tw9wT
+4wB9KPIttfNLbwn/rk+MbOTHpvyF60d9WhJJVUkPBl8D4VuPSl+VnlA54kU9dtZN
++ZYdxYbNtSsI/Flz9SCoOV79W9GhN+uYJhv6RwyIMIHeMpZpyX1xSUVx5dZlmerQ
+WAUvghDH3fFRt2ZdnA4OXoKkTAaM3Pv7PUMsnah8bux6MQi0AuLMWFWOI1H34koH
+rs2oQLwOLnuifH52ey9+tJguabo+brlYYigAuWWFEzJfBzikDkIwnE/L7wlrypIk
+taXDWI4=
+-----END CERTIFICATE-----
diff --git a/common/tests/res/raw/subject_with_alt_names.crt b/common/tests/res/raw/subject_with_alt_names.crt
new file mode 100644
index 0000000..6963c7e
--- /dev/null
+++ b/common/tests/res/raw/subject_with_alt_names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBDCCAeygAwIBAgIJALv14qjcuhw9MA0GCSqGSIb3DQEBBQUAMCcxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wIBcNMTAwMTEyMjA1OTM4
+WhgPMjA2NDEwMTUyMDU5MzhaMCcxCzAJBgNVBAYTAkpQMRgwFgYDVQQDEw93d3cu
+ZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCiTVgU
+kBO9KNYZZLmiPR0eBrk8u61CLnm35BGKW8EFpDaINLbbIFIQvqOMekURON/N+xFY
+D8roo7aFZVuHWAUqFcOJ4e6NmviK5qocLihtzAexsw4f4AzZxM3A8kcLlWLyAt7e
+EVLxhcMHogY7GaF6q+33Z8p+zp6x3tj07mwyPrriCLse2PeRsRunZl/fp/VvRlr6
+YbC7CbRrhnIv5nqohs8BsbBiiFpxQftsMQmiXhY2LUzqY2RXUIOw24fHjoQkHTL2
+4z5nUM3b6ueQe+CBnobUS6fzK/36Nct4dRpev9i/ORdRLuIDKJ+QR16G1V/BJYBR
+dAK+3iXvg6z8vP1XAgMBAAGjMTAvMC0GA1UdEQQmMCSCEHd3dzIuZXhhbXBsZS5j
+b22CEHd3dzMuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADggEBAJQNf38uXm3h
+0vsF+Yd6/HqM48Su7tWnTDAfTXnQZZkzjzITq3JXzquMXICktAVN2cLnT9zPfRAE
+8V8A3BNO5zXiR5W3o/mJP5HQ3/WxpzBGM2N+YmDCJyBoQrIVaAZaXAZUaBBvn5A+
+kEVfGWquwIFuvA67xegbJOCRLD4eUzRdNsn5+NFiakWO1tkFqEzqyQ0PNPviRjgu
+z9NxdPvd1JQOhydkucsPKJzlEBbGyL5QL/Jkot3Qy+FOeuNzgQUfAGtQgzRrsZDK
+hrTVypLSoRXuTB2aWilu4p6aNh84xTdyqo2avtNr2MiQMZIcdamBq8LdBIAShFXI
+h5G2eVGXH/Y=
+-----END CERTIFICATE-----
diff --git a/common/tests/res/raw/subject_with_wild_alt_name.crt b/common/tests/res/raw/subject_with_wild_alt_name.crt
new file mode 100644
index 0000000..19b1174
--- /dev/null
+++ b/common/tests/res/raw/subject_with_wild_alt_name.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8DCCAdigAwIBAgIJAL/oWJ64VAdXMA0GCSqGSIb3DQEBBQUAMCcxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wIBcNMTAwMTEyMjEwMDAx
+WhgPMjA2NDEwMTUyMTAwMDFaMCcxCzAJBgNVBAYTAkpQMRgwFgYDVQQDEw93d3cu
+ZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbx1QB
+92iea7VybLYICA4MX4LWipYrRsgXUXQrcIQ3YLTQ9rH0VwScrHL4O4JDxgXCQnR+
+4VOzD42q1KXHJAqzqGUYCNPyvZEzkGCnQ4FBIUEmxZd5SNEefJVH3Z6GizYJomTh
+p78yDcoqymD9umxRC2cWFu8GscfFGMVyhsqLlOofu7UWOs22mkXPo43jDx+VOAoV
+n48YP3P57a2Eo0gcd4zVL00y62VegqBO/1LW38aTS7teiCBFc1TkNYa5I40yN9lP
+rB9ICHYQWyzf/7OxU9iauEK2w6DmSsQoLs9JzEhgeNZddkcc77ciSUCo2Hx0VpOJ
+BFyf2rbryJeAk+FDAgMBAAGjHTAbMBkGA1UdEQQSMBCCDiouZXhhbXBsZTIuY29t
+MA0GCSqGSIb3DQEBBQUAA4IBAQA2a14pRL+4laJ8sscQlucaDB/oSdb0cwhk4IkE
+kKl/ZKr6rKwPZ81sJRgzvI4imLbUAKt4AJHdpI9cIQUq1gw9bzil7LKwmFtFSPmC
+MYb1iadaYrvp7RE4yXrWCcSbU0hup9JQLHTrHLlqLtRuU48NHMvWYThBcS9Q/hQp
+nJ/JxYy3am99MHALWLAfuRxQXhE4C5utDmBwI2KD6A8SA30s+CnuegmkYScuSqBu
+Y3R0HZvKzNIU3pwAm69HCJoG+/9MZEIDJb0WJc5UygxDT45XE9zQMQe4dBOTaNXT
++ntgaB62kE10HzrzpqXAgoAWxWK4RzFcUpBWw9qYq9xOCewJ
+-----END CERTIFICATE-----
diff --git a/common/tests/res/raw/wild_alt_name_only.crt b/common/tests/res/raw/wild_alt_name_only.crt
new file mode 100644
index 0000000..fafdebf
--- /dev/null
+++ b/common/tests/res/raw/wild_alt_name_only.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICuzCCAaOgAwIBAgIJAP82tgcvmAGxMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
+BAYTAkpQMCAXDTEwMDExMjIxMDAyN1oYDzIwNjQxMDE1MjEwMDI3WjANMQswCQYD
+VQQGEwJKUDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALs528EQbcB1
+x4BwxthQBZrgDJzoO7KPV3dhGYoeP8EnRjapZm+T/sj9P/O4HvfxjnB+fsjYSdmE
+WWUtnFrP7wtG9DUC748Ea2PMV8WFhOG58dqBNIko5XzkHB7SxkNZD5S/0KQYMGLr
+rchDsDlmsEf2Qb6qiqpNEU70aSkExZJcH+B9nWdeBpsVFu7wtezwSWEc2NUa2bhW
+gcXQ/aafwHZ4o2PyGwy0sgS/UifqO9tEllC2tPleSNJOmYsVudv5Bz4Q0GG38BSz
+Pc0IcOoln0ZWpXbGr03V2vlXWCwzaFAl3I1T3O7YVqDiaSWoP+d0tHZzmw8aJLXd
+B+KaUUGxRPsCAwEAAaMcMBowGAYDVR0RBBEwD4INKi5leGFtcGxlLmNvbTANBgkq
+hkiG9w0BAQUFAAOCAQEAJbVan4QgJ0cvpJnK9UWIVJNC+UbP87RC5go2fQiTnmGv
+prOrIuMqz1+vGcpIheLTLctJRHPoadXq0+UbQEIaU3pQbY6C4nNdfl+hcvmJeqrt
+kOCcvmIamO68iNvTSeszuHuu4O38PefrW2Xd0nn7bjFZrzBzHFhTudmnqNliP3ue
+KKQpqkUt5lCytnH8V/u/UCWdvVx5LnUa2XFGVLi3ongBIojW5fvF+yxn9ADqxdrI
+va++ow5r1VxQXFJc0ZPzsDo+6TlktoDHaRQJGMqQomqHWT4i7F5UZgf6BHGfEUPU
+qep+GsF3QRHSBtpObWkVDZNFvky3a1iZ2q25+hFIqQ==
+-----END CERTIFICATE-----
diff --git a/common/tests/src/com/android/common/DomainNameValidatorTest.java b/common/tests/src/com/android/common/DomainNameValidatorTest.java
index 4fdd4cdc..b825be4 100644
--- a/common/tests/src/com/android/common/DomainNameValidatorTest.java
+++ b/common/tests/src/com/android/common/DomainNameValidatorTest.java
@@ -15,6 +15,11 @@
*/
package com.android.common;
+import com.android.common.tests.R;
+
+import android.test.AndroidTestCase;
+
+import java.io.InputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@@ -25,6 +30,7 @@
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
@@ -37,22 +43,16 @@
import javax.security.auth.x500.X500Principal;
-import junit.framework.TestCase;
-
-public class DomainNameValidatorTest extends TestCase {
+public class DomainNameValidatorTest extends AndroidTestCase {
private static final int ALT_UNKNOWN = 0;
private static final int ALT_DNS_NAME = 2;
private static final int ALT_IPA_NAME = 7;
/**
- * Tests {@link DomainNameValidator#match}
+ * Tests {@link DomainNameValidator#match}, using a simple {@link X509Certificate}
+ * implementation.
*/
public void testMatch() {
- // TODO Use actual X509Certificate objects, instead of StubX509Certificate.
- // Comment in DomainNameValidator suggests X509Certificate fails to parse a certificate
- // if subject alternative names contain a domain name that begins with '*'.
- // This test won't cover this kind of errors.
-
checkMatch("11", new StubX509Certificate("cn=imap.g.com"), "imap.g.com", true);
checkMatch("12", new StubX509Certificate("cn=imap2.g.com"), "imap.g.com", false);
checkMatch("13", new StubX509Certificate("cn=sub.imap.g.com"), "imap.g.com", false);
@@ -74,7 +74,6 @@
.addSubjectAlternativeName(ALT_DNS_NAME, "*.g.com")
, "imap.g.com", true);
-
// host name is ip address
checkMatch("31", new StubX509Certificate("")
.addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4")
@@ -170,6 +169,70 @@
}
/**
+ * Test {@link DomainNameValidator#match} with actual certificates.
+ */
+ public void testWithActualCert() throws Exception {
+ // subject_only
+ //
+ // subject: C=JP, CN=www.example.com
+ // subject alt names: n/a
+ checkWithActualCert("11", R.raw.subject_only, "www.example.com", true);
+ checkWithActualCert("12", R.raw.subject_only, "www2.example.com", false);
+
+ // subject_alt_only
+ //
+ // subject: C=JP (no CN)
+ // subject alt names: DNS:www.example.com
+ checkWithActualCert("21", R.raw.subject_alt_only, "www.example.com", true);
+ checkWithActualCert("22", R.raw.subject_alt_only, "www2.example.com", false);
+
+ // subject_with_alt_names
+ //
+ // subject: C=JP, CN=www.example.com
+ // subject alt names: DNS:www2.example.com, DNS:www3.example.com
+ // * Subject should be ignored, because it has subject alt names.
+ checkWithActualCert("31", R.raw.subject_with_alt_names, "www.example.com", false);
+ checkWithActualCert("32", R.raw.subject_with_alt_names, "www2.example.com", true);
+ checkWithActualCert("33", R.raw.subject_with_alt_names, "www3.example.com", true);
+ checkWithActualCert("34", R.raw.subject_with_alt_names, "www4.example.com", false);
+
+ // subject_with_wild_alt_name
+ //
+ // subject: C=JP, CN=www.example.com
+ // subject alt names: DNS:*.example2.com
+ // * Subject should be ignored, because it has subject alt names.
+ checkWithActualCert("41", R.raw.subject_with_wild_alt_name, "www.example.com", false);
+ checkWithActualCert("42", R.raw.subject_with_wild_alt_name, "www2.example.com", false);
+ checkWithActualCert("43", R.raw.subject_with_wild_alt_name, "www.example2.com", true);
+ checkWithActualCert("44", R.raw.subject_with_wild_alt_name, "abc.example2.com", true);
+ checkWithActualCert("45", R.raw.subject_with_wild_alt_name, "www.example3.com", false);
+
+ // wild_alt_name_only
+ //
+ // subject: C=JP
+ // subject alt names: DNS:*.example.com
+ checkWithActualCert("51", R.raw.wild_alt_name_only, "www.example.com", true);
+ checkWithActualCert("52", R.raw.wild_alt_name_only, "www2.example.com", true);
+ checkWithActualCert("53", R.raw.wild_alt_name_only, "www.example2.com", false);
+
+ // wild_alt_name_only
+ //
+ // subject: C=JP
+ // subject alt names: IP Address:192.168.10.1
+ checkWithActualCert("61", R.raw.alt_ip_only, "192.168.10.1", true);
+ checkWithActualCert("61", R.raw.alt_ip_only, "192.168.10.2", false);
+ }
+
+ private void checkWithActualCert(String message, int resId, String domain,
+ boolean expected) throws Exception {
+ CertificateFactory factory = CertificateFactory.getInstance("X509");
+ InputStream certStream = getContext().getResources().openRawResource(resId);
+ X509Certificate certificate = (X509Certificate) factory.generateCertificate(certStream);
+
+ checkMatch(message, certificate, domain, expected);
+ }
+
+ /**
* Minimal {@link X509Certificate} implementation for {@link DomainNameValidator}.
*/
private static class StubX509Certificate extends X509Certificate {
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java
index d89b877..fe05393 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ApplicationContext.java
@@ -186,6 +186,7 @@
private boolean mRestricted;
private AccountManager mAccountManager; // protected by mSync
private DropBoxManager mDropBoxManager = null;
+ private DevicePolicyManager mDevicePolicyManager = null;
private final Object mSync = new Object();
@@ -895,6 +896,8 @@
return getWallpaperManager();
} else if (DROPBOX_SERVICE.equals(name)) {
return getDropBoxManager();
+ } else if (DEVICE_POLICY_SERVICE.equals(name)) {
+ return getDevicePolicyManager();
}
return null;
@@ -1064,6 +1067,16 @@
return mDropBoxManager;
}
+ private DevicePolicyManager getDevicePolicyManager() {
+ synchronized (mSync) {
+ if (mDevicePolicyManager == null) {
+ mDevicePolicyManager = new DevicePolicyManager(this,
+ mMainThread.getHandler());
+ }
+ }
+ return mDevicePolicyManager;
+ }
+
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index a4b692f..832f599 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -82,6 +82,11 @@
public long time;
/**
+ * Set if the app is on the system image.
+ */
+ public boolean systemApp;
+
+ /**
* If this report is of type {@link #TYPE_CRASH}, contains an instance
* of CrashInfo describing the crash; otherwise null.
*/
@@ -113,6 +118,7 @@
dest.writeString(installerPackageName);
dest.writeString(processName);
dest.writeLong(time);
+ dest.writeInt(systemApp ? 1 : 0);
switch (type) {
case TYPE_CRASH:
@@ -130,6 +136,7 @@
installerPackageName = in.readString();
processName = in.readString();
time = in.readLong();
+ systemApp = in.readInt() == 1;
switch (type) {
case TYPE_CRASH:
@@ -331,6 +338,7 @@
pw.println(prefix + "installerPackageName: " + installerPackageName);
pw.println(prefix + "processName: " + processName);
pw.println(prefix + "time: " + time);
+ pw.println(prefix + "systemApp: " + systemApp);
switch (type) {
case TYPE_CRASH:
diff --git a/core/java/android/app/BackupAgent.java b/core/java/android/app/BackupAgent.java
index b207998..2a58677 100644
--- a/core/java/android/app/BackupAgent.java
+++ b/core/java/android/app/BackupAgent.java
@@ -21,6 +21,7 @@
import android.backup.BackupDataOutput;
import android.content.Context;
import android.content.ContextWrapper;
+import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -116,7 +117,9 @@
public void doBackup(ParcelFileDescriptor oldState,
ParcelFileDescriptor data,
ParcelFileDescriptor newState) throws RemoteException {
- // !!! TODO - real implementation; for now just invoke the callbacks directly
+ // Ensure that we're running with the app's normal permission level
+ long token = Binder.clearCallingIdentity();
+
if (DEBUG) Log.v(TAG, "doBackup() invoked");
BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
try {
@@ -127,12 +130,16 @@
} catch (RuntimeException ex) {
Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw ex;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
public void doRestore(ParcelFileDescriptor data, int appVersionCode,
ParcelFileDescriptor newState) throws RemoteException {
- // !!! TODO - real implementation; for now just invoke the callbacks directly
+ // Ensure that we're running with the app's normal permission level
+ long token = Binder.clearCallingIdentity();
+
if (DEBUG) Log.v(TAG, "doRestore() invoked");
BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
try {
@@ -143,6 +150,8 @@
} catch (RuntimeException ex) {
Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw ex;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
}
diff --git a/core/java/android/app/DeviceAdmin.java b/core/java/android/app/DeviceAdmin.java
new file mode 100644
index 0000000..16832db
--- /dev/null
+++ b/core/java/android/app/DeviceAdmin.java
@@ -0,0 +1,221 @@
+/*
+ * 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 android.app;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Base class for implementing a device administration component. This
+ * class provides a convenience for interpreting the raw intent actions
+ * that are sent by the system.
+ *
+ * <p>When publishing your DeviceAdmin subclass as a receiver, it must
+ * handle {@link #ACTION_DEVICE_ADMIN_ENABLED} and require the
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission. A typical
+ * manifest entry would look like:</p>
+ *
+ * <pre>{@include development/samples/ApiDemos/AndroidManifest.xml
+ * device_admin_declaration}</pre>
+ *
+ * <p>The meta-data referenced here provides addition information specific
+ * to the device administrator, as parsed by the {@link DeviceAdminInfo} class.
+ * A typical file would be:</p>
+ *
+ * <pre>{@include development/samples/ApiDemos/res/xml/sample_device_admin.xml
+ * meta_data}</pre>
+ */
+public class DeviceAdmin extends BroadcastReceiver {
+ private static String TAG = "DevicePolicy";
+ private static boolean DEBUG = false;
+ private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+
+ /**
+ * This is the primary action that a device administrator must implement to be
+ * allowed to manage a device. This will be set to the receiver
+ * when the user enables it for administration. You will generally
+ * handle this in {@link DeviceAdmin#onEnabled(Context, Intent)}. To be
+ * supported, the receiver must also require the
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission so
+ * that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_ADMIN_ENABLED
+ = "android.app.action.DEVICE_ADMIN_ENABLED";
+
+ /**
+ * Action sent to a device administrator when the user has disabled
+ * it. Upon return, the application no longer has access to the
+ * protected device policy manager APIs. You will generally
+ * handle this in {@link DeviceAdmin#onDisabled(Context, Intent)}. Note
+ * that this action will be
+ * sent the receiver regardless of whether it is explicitly listed in
+ * its intent filter.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_ADMIN_DISABLED
+ = "android.app.action.DEVICE_ADMIN_DISABLED";
+
+ /**
+ * Action sent to a device administrator when the user has changed the
+ * password of their device. You can at this point check the characteristics
+ * of the new password with {@link DevicePolicyManager#getPasswordMode()
+ * DevicePolicyManager.getActivePasswordMode()} and
+ * {@link DevicePolicyManager#getMinimumPasswordLength()
+ * DevicePolicyManager.getMinimumPasswordLength()}. You will generally
+ * handle this in {@link DeviceAdmin#onPasswordChanged(Context, Intent)}.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PASSWORD_CHANGED
+ = "android.app.action.ACTION_PASSWORD_CHANGED";
+
+ /**
+ * Action sent to a device administrator when the user has failed at
+ * attempted to enter the password. You can at this point check the
+ * number of failed password attempts there have been with
+ * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts()
+ * DevicePolicyManager.getCurrentFailedPasswordAttempts()}. You will generally
+ * handle this in {@link DeviceAdmin#onPasswordFailed(Context, Intent)}.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PASSWORD_FAILED
+ = "android.app.action.ACTION_PASSWORD_FAILED";
+
+ /**
+ * Action sent to a device administrator when the user has successfully
+ * entered their password, after failing one or more times.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PASSWORD_SUCCEEDED
+ = "android.app.action.ACTION_PASSWORD_SUCCEEDED";
+
+ /**
+ * Name under which an DevicePolicy component publishes information
+ * about itself. This meta-data must reference an XML resource containing
+ * a device-admin tag. XXX TO DO: describe syntax.
+ */
+ public static final String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
+
+ private DevicePolicyManager mManager;
+ private ComponentName mWho;
+
+ /**
+ * Retrieve the DevicePolicyManager interface for this administrator to work
+ * with the system.
+ */
+ public DevicePolicyManager getManager(Context context) {
+ if (mManager != null) {
+ return mManager;
+ }
+ mManager = (DevicePolicyManager)context.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ return mManager;
+ }
+
+ /**
+ * Retrieve the ComponentName describing who this device administrator is, for
+ * use in {@link DevicePolicyManager} APIs that require the administrator to
+ * identify itself.
+ */
+ public ComponentName getWho(Context context) {
+ if (mWho != null) {
+ return mWho;
+ }
+ mWho = new ComponentName(context, getClass());
+ return mWho;
+ }
+
+ /**
+ * Called after the administrator is first enabled, as a result of
+ * receiving {@link #ACTION_DEVICE_ADMIN_ENABLED}. At this point you
+ * can use {@link DevicePolicyManager} to set your desired policies.
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onEnabled(Context context, Intent intent) {
+ }
+
+ /**
+ * Called prior to the administrator being disabled, as a result of
+ * receiving {@link #ACTION_DEVICE_ADMIN_DISABLED}. Upon return, you
+ * can no longer use the protected parts of the {@link DevicePolicyManager}
+ * API.
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onDisabled(Context context, Intent intent) {
+ }
+
+ /**
+ * Called after the user has changed their password, as a result of
+ * receiving {@link #ACTION_PASSWORD_CHANGED}. At this point you
+ * can use {@link DevicePolicyManager#getCurrentFailedPasswordAttempts()
+ * DevicePolicyManager.getCurrentFailedPasswordAttempts()}
+ * to retrieve the active password characteristics.
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onPasswordChanged(Context context, Intent intent) {
+ }
+
+ /**
+ * Called after the user has failed at entering their current password, as a result of
+ * receiving {@link #ACTION_PASSWORD_FAILED}. At this point you
+ * can use {@link DevicePolicyManager} to retrieve the number of failed
+ * password attempts.
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onPasswordFailed(Context context, Intent intent) {
+ }
+
+ /**
+ * Called after the user has succeeded at entering their current password,
+ * as a result of receiving {@link #ACTION_PASSWORD_SUCCEEDED}. This will
+ * only be received the first time they succeed after having previously
+ * failed.
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onPasswordSucceeded(Context context, Intent intent) {
+ }
+
+ /**
+ * Intercept standard device administrator broadcasts. Implementations
+ * should not override this method; it is better to implement the
+ * convenience callbacks for each action.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (ACTION_PASSWORD_CHANGED.equals(action)) {
+ onPasswordChanged(context, intent);
+ } else if (ACTION_PASSWORD_FAILED.equals(action)) {
+ onPasswordFailed(context, intent);
+ } else if (ACTION_PASSWORD_SUCCEEDED.equals(action)) {
+ onPasswordSucceeded(context, intent);
+ } else if (ACTION_DEVICE_ADMIN_ENABLED.equals(action)) {
+ onEnabled(context, intent);
+ } else if (ACTION_DEVICE_ADMIN_DISABLED.equals(action)) {
+ onDisabled(context, intent);
+ }
+ }
+}
diff --git a/core/java/android/app/DeviceAdminInfo.java b/core/java/android/app/DeviceAdminInfo.java
new file mode 100644
index 0000000..eac6e46
--- /dev/null
+++ b/core/java/android/app/DeviceAdminInfo.java
@@ -0,0 +1,186 @@
+/*
+ * 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 android.app;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Printer;
+import android.util.Xml;
+
+import java.io.IOException;
+
+/**
+ * This class is used to specify meta information of a device administrator
+ * component.
+ */
+public final class DeviceAdminInfo implements Parcelable {
+ static final String TAG = "DeviceAdminInfo";
+
+ /**
+ * The BroadcastReceiver that implements this device admin component.
+ */
+ final ResolveInfo mReceiver;
+
+ /**
+ * Constructor.
+ *
+ * @param context The Context in which we are parsing the device admin.
+ * @param receiver The ResolveInfo returned from the package manager about
+ * this device admin's component.
+ */
+ public DeviceAdminInfo(Context context, ResolveInfo receiver)
+ throws XmlPullParserException, IOException {
+ mReceiver = receiver;
+ ActivityInfo ai = receiver.activityInfo;
+
+ PackageManager pm = context.getPackageManager();
+
+ XmlResourceParser parser = null;
+ try {
+ parser = ai.loadXmlMetaData(pm, DeviceAdmin.DEVICE_ADMIN_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No "
+ + DeviceAdmin.DEVICE_ADMIN_META_DATA + " meta-data");
+ }
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!"device-admin".equals(nodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with device-admin tag");
+ }
+
+ TypedArray sa = context.getResources().obtainAttributes(attrs,
+ com.android.internal.R.styleable.Wallpaper);
+
+ sa.recycle();
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ DeviceAdminInfo(Parcel source) {
+ mReceiver = ResolveInfo.CREATOR.createFromParcel(source);
+ }
+
+ /**
+ * Return the .apk package that implements this device admin.
+ */
+ public String getPackageName() {
+ return mReceiver.activityInfo.packageName;
+ }
+
+ /**
+ * Return the class name of the receiver component that implements
+ * this device admin.
+ */
+ public String getReceiverName() {
+ return mReceiver.activityInfo.name;
+ }
+
+ /**
+ * Return the raw information about the receiver implementing this
+ * device admin. Do not modify the returned object.
+ */
+ public ActivityInfo getActivityInfo() {
+ return mReceiver.activityInfo;
+ }
+
+ /**
+ * Return the component of the receiver that implements this device admin.
+ */
+ public ComponentName getComponent() {
+ return new ComponentName(mReceiver.activityInfo.packageName,
+ mReceiver.activityInfo.name);
+ }
+
+ /**
+ * Load the user-displayed label for this device admin.
+ *
+ * @param pm Supply a PackageManager used to load the device admin's
+ * resources.
+ */
+ public CharSequence loadLabel(PackageManager pm) {
+ return mReceiver.loadLabel(pm);
+ }
+
+ /**
+ * Load the user-displayed icon for this device admin.
+ *
+ * @param pm Supply a PackageManager used to load the device admin's
+ * resources.
+ */
+ public Drawable loadIcon(PackageManager pm) {
+ return mReceiver.loadIcon(pm);
+ }
+
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "Receiver:");
+ mReceiver.dump(pw, prefix + " ");
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceAdminInfo{" + mReceiver.activityInfo.name + "}";
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ mReceiver.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<DeviceAdminInfo> CREATOR =
+ new Parcelable.Creator<DeviceAdminInfo>() {
+ public DeviceAdminInfo createFromParcel(Parcel source) {
+ return new DeviceAdminInfo(source);
+ }
+
+ public DeviceAdminInfo[] newArray(int size) {
+ return new DeviceAdminInfo[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/app/DevicePolicyManager.java b/core/java/android/app/DevicePolicyManager.java
new file mode 100644
index 0000000..538ba5b
--- /dev/null
+++ b/core/java/android/app/DevicePolicyManager.java
@@ -0,0 +1,457 @@
+/*
+ * 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 android.app;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Public interface for managing policies enforced on a device. Most clients
+ * of this class must have published a {@link DeviceAdmin} that the user
+ * has currently enabled.
+ */
+public class DevicePolicyManager {
+ private static String TAG = "DevicePolicyManager";
+ private static boolean DEBUG = false;
+ private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final IDevicePolicyManager mService;
+
+ /*package*/ DevicePolicyManager(Context context, Handler handler) {
+ mContext = context;
+ mHandler = handler;
+ mService = IDevicePolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
+ }
+
+ /**
+ * Activity action: ask the user to add a new device administrator to the system.
+ * The desired policy is the ComponentName of the policy in the
+ * {@link #EXTRA_DEVICE_ADMIN} extra field. This will invoke a UI to
+ * bring the user through adding the device administrator to the system (or
+ * allowing them to reject it).
+ *
+ * <p>Note: the current platform can only have one device administrator
+ * active at a time. If you make this request while there is already
+ * an active administrator, this new request will be canceled automatically.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ADD_DEVICE_ADMIN
+ = "android.app.action.ADD_DEVICE_ADMIN";
+
+ /**
+ * The ComponentName of the administrator component.
+ *
+ * @see #ACTION_ADD_DEVICE_ADMIN
+ */
+ public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
+
+ /**
+ * Activity action: have the user enter a new password. This activity
+ * should be launched after using {@link #setPasswordMode(ComponentName, int)}
+ * or {@link #setMinimumPasswordLength(ComponentName, int)} to have the
+ * user enter a new password that meets the current requirements. You can
+ * use {@link #isActivePasswordSufficient()} to determine whether you need
+ * to have the user select a new password in order to meet the current
+ * constraints. Upon being resumed from this activity,
+ * you can check the new password characteristics to see if they are
+ * sufficient.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SET_NEW_PASSWORD
+ = "android.app.action.SET_NEW_PASSWORD";
+
+ /**
+ * Return true if the given administrator component is currently
+ * active (enabled) in the system.
+ */
+ public boolean isAdminActive(ComponentName who) {
+ if (mService != null) {
+ try {
+ return who.equals(mService.getActiveAdmin());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Remove a current administration component. This can only be called
+ * by the application that owns the administration component; if you
+ * try to remove someone else's component, a security exception will be
+ * thrown.
+ */
+ public void removeActiveAdmin(ComponentName who) {
+ if (mService != null) {
+ try {
+ mService.removeActiveAdmin(who);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Constant for {@link #setPasswordMode}: the policy has no requirements
+ * for the password. Note that mode constants are ordered so that higher
+ * values are more restrictive.
+ */
+ public static final int PASSWORD_MODE_UNSPECIFIED = 0;
+
+ /**
+ * Constant for {@link #setPasswordMode}: the policy requires some kind
+ * of password, but doesn't care what it is. Note that mode constants
+ * are ordered so that higher values are more restrictive.
+ */
+ public static final int PASSWORD_MODE_SOMETHING = 1000;
+
+ /**
+ * Constant for {@link #setPasswordMode}: the user must have at least a
+ * numeric password. Note that mode constants are ordered so that higher
+ * values are more restrictive.
+ */
+ public static final int PASSWORD_MODE_NUMERIC = 2000;
+
+ /**
+ * Constant for {@link #setPasswordMode}: the user must have at least an
+ * alphanumeric password. Note that mode constants are ordered so that higher
+ * values are more restrictive.
+ */
+ public static final int PASSWORD_MODE_ALPHANUMERIC = 3000;
+
+ /**
+ * Called by an application that is administering the device to set the
+ * password restrictions it is imposing. After setting this, the user
+ * will not be able to enter a new password that is not at least as
+ * restrictive as what has been set. Note that the current password
+ * will remain until the user has set a new one, so the change does not
+ * take place immediately. To prompt the user for a new password, use
+ * {@link #ACTION_SET_NEW_PASSWORD} after setting this value.
+ *
+ * <p>Mode constants are ordered so that higher values are more restrictive;
+ * thus the highest requested mode constant (between the policy set here,
+ * the user's preference, and any other considerations) is the one that
+ * is in effect.
+ *
+ * @param admin Which {@link DeviceAdmin} this request is associated with.
+ * @param mode The new desired mode. One of
+ * {@link #PASSWORD_MODE_UNSPECIFIED}, {@link #PASSWORD_MODE_SOMETHING},
+ * {@link #PASSWORD_MODE_NUMERIC}, or {@link #PASSWORD_MODE_ALPHANUMERIC}.
+ */
+ public void setPasswordMode(ComponentName admin, int mode) {
+ if (mService != null) {
+ try {
+ mService.setPasswordMode(admin, mode);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the current password mode that is in effect due to all
+ * device admins.
+ */
+ public int getPasswordMode() {
+ if (mService != null) {
+ try {
+ return mService.getPasswordMode();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return PASSWORD_MODE_UNSPECIFIED;
+ }
+
+ /**
+ * Called by an application that is administering the device to set the
+ * minimum allowed password length. After setting this, the user
+ * will not be able to enter a new password that is not at least as
+ * restrictive as what has been set. Note that the current password
+ * will remain until the user has set a new one, so the change does not
+ * take place immediately. To prompt the user for a new password, use
+ * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This
+ * constraint is only imposed if the administrator has also requested either
+ * {@link #PASSWORD_MODE_NUMERIC} or {@link #PASSWORD_MODE_ALPHANUMERIC}
+ * with {@link #setPasswordMode}.
+ *
+ * @param admin Which {@link DeviceAdmin} this request is associated with.
+ * @param length The new desired minimum password length. A value of 0
+ * means there is no restriction.
+ */
+ public void setMinimumPasswordLength(ComponentName admin, int length) {
+ if (mService != null) {
+ try {
+ mService.setMinimumPasswordLength(admin, length);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the current minimum password length that is in effect due to all
+ * device admins.
+ */
+ public int getMinimumPasswordLength() {
+ if (mService != null) {
+ try {
+ return mService.getMinimumPasswordLength();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Determine whether the current password the user has set is sufficient
+ * to meet the policy requirements (mode, minimum length) that have been
+ * requested.
+ *
+ * @return Returns true if the password meets the current requirements,
+ * else false.
+ */
+ public boolean isActivePasswordSufficient() {
+ if (mService != null) {
+ try {
+ return mService.isActivePasswordSufficient();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve the number of times the user has failed at entering a
+ * password since that last successful password entry.
+ */
+ public int getCurrentFailedPasswordAttempts() {
+ if (mService != null) {
+ try {
+ return mService.getCurrentFailedPasswordAttempts();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Force a new password on the user. This takes effect immediately. The
+ * given password must meet the current password minimum length constraint
+ * or it will be rejected. The given password will be accepted regardless
+ * of the current password mode, automatically adjusting the password mode
+ * higher if needed. (The string you give here is acceptable for any mode;
+ * if it contains only digits, that is still an acceptable alphanumeric
+ * password.)
+ *
+ * @param password The new password for the user.
+ * @return Returns true if the password was applied, or false if it is
+ * not acceptable for the current constraints.
+ */
+ public boolean resetPassword(String password) {
+ if (mService != null) {
+ try {
+ return mService.resetPassword(password);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by an application that is administering the device to set the
+ * maximum time for user activity until the device will lock. This limits
+ * the length that the user can set. It takes effect immediately.
+ *
+ * @param admin Which {@link DeviceAdmin} this request is associated with.
+ * @param timeMs The new desired maximum time to lock in milliseconds.
+ * A value of 0 means there is no restriction.
+ */
+ public void setMaximumTimeToLock(ComponentName admin, long timeMs) {
+ if (mService != null) {
+ try {
+ mService.setMaximumTimeToLock(admin, timeMs);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the current maximum time to lock that is in effect due to all
+ * device admins. Returns 0 if no maximum is set.
+ */
+ public long getMaximumTimeToLock() {
+ if (mService != null) {
+ try {
+ return mService.getMaximumTimeToLock();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Make the device lock immediately, as if the lock screen timeout has
+ * expired at the point of this call.
+ */
+ public void lockNow() {
+ if (mService != null) {
+ try {
+ mService.lockNow();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Ask the user date be wiped. This will cause the device to reboot,
+ * erasing all user data while next booting up. External storage such
+ * as SD cards will not be erased.
+ *
+ * @param flags Bit mask of additional options: currently must be 0.
+ */
+ public void wipeData(int flags) {
+ if (mService != null) {
+ try {
+ mService.wipeData(flags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setActiveAdmin(ComponentName policyReceiver) {
+ if (mService != null) {
+ try {
+ mService.setActiveAdmin(policyReceiver);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public ComponentName getActiveAdmin() {
+ if (mService != null) {
+ try {
+ return mService.getActiveAdmin();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public DeviceAdminInfo getActiveAdminInfo() {
+ ComponentName cn = getActiveAdmin();
+ if (cn == null) {
+ return null;
+ }
+
+ ActivityInfo ai;
+ try {
+ ai = mContext.getPackageManager().getReceiverInfo(cn,
+ PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Unable to retrieve device policy " + cn, e);
+ return null;
+ }
+
+ ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = ai;
+
+ try {
+ return new DeviceAdminInfo(mContext, ri);
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Unable to parse device policy " + cn, e);
+ return null;
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to parse device policy " + cn, e);
+ return null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setActivePasswordState(int mode, int length) {
+ if (mService != null) {
+ try {
+ mService.setActivePasswordState(mode, length);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void reportFailedPasswordAttempt() {
+ if (mService != null) {
+ try {
+ mService.reportFailedPasswordAttempt();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void reportSuccessfulPasswordAttempt() {
+ if (mService != null) {
+ try {
+ mService.reportSuccessfulPasswordAttempt();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/IDevicePolicyManager.aidl b/core/java/android/app/IDevicePolicyManager.aidl
new file mode 100644
index 0000000..7e38194
--- /dev/null
+++ b/core/java/android/app/IDevicePolicyManager.aidl
@@ -0,0 +1,52 @@
+/*
+**
+** Copyright 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 android.app;
+
+import android.content.ComponentName;
+
+/**
+ * Internal IPC interface to the device policy service.
+ * {@hide}
+ */
+interface IDevicePolicyManager {
+ void setPasswordMode(in ComponentName who, int mode);
+ int getPasswordMode();
+
+ void setMinimumPasswordLength(in ComponentName who, int length);
+ int getMinimumPasswordLength();
+
+ boolean isActivePasswordSufficient();
+ int getCurrentFailedPasswordAttempts();
+
+ boolean resetPassword(String password);
+
+ void setMaximumTimeToLock(in ComponentName who, long timeMs);
+ long getMaximumTimeToLock();
+
+ void lockNow();
+
+ void wipeData(int flags);
+
+ void setActiveAdmin(in ComponentName policyReceiver);
+ ComponentName getActiveAdmin();
+ void removeActiveAdmin(in ComponentName policyReceiver);
+
+ void setActivePasswordState(int mode, int length);
+ void reportFailedPasswordAttempt();
+ void reportSuccessfulPasswordAttempt();
+}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index be5a7d3..4d72f73 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -443,11 +443,7 @@
contentView.setTextViewText(com.android.internal.R.id.text, contentText);
}
if (this.when != 0) {
- Date date = new Date(when);
- CharSequence str =
- DateUtils.isToday(when) ? DateFormat.getTimeFormat(context).format(date)
- : DateFormat.getDateFormat(context).format(date);
- contentView.setTextViewText(com.android.internal.R.id.time, str);
+ contentView.setLong(com.android.internal.R.id.time, "setTime", when);
}
this.contentView = contentView;
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 981145b..d25d670 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -1838,8 +1838,10 @@
*
* TODO: Doing this every time we start global search is inefficient. Will fix that once
* we have settled on the right mechanism for finding the global search activity.
+ *
+ * @hide
*/
- private ComponentName getGlobalSearchActivity() {
+ public ComponentName getGlobalSearchActivity() {
Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH);
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> activities =
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index 1034fab..1612ac9 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.app;
import org.xmlpull.v1.XmlPullParser;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0fafe5d..d76b616 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -250,22 +250,21 @@
public abstract ApplicationInfo getApplicationInfo();
/**
- * {@hide}
- * Return the full path to this context's resource files. This is the ZIP files
- * containing the application's resources.
+ * Return the full path to this context's primary Android package.
+ * The Android package is a ZIP file which contains the application's
+ * primary resources.
*
* <p>Note: this is not generally useful for applications, since they should
* not be directly accessing the file system.
*
- *
* @return String Path to the resources.
*/
public abstract String getPackageResourcePath();
/**
- * {@hide}
- * Return the full path to this context's code and asset files. This is the ZIP files
- * containing the application's code and assets.
+ * Return the full path to this context's primary Android package.
+ * The Android package is a ZIP file which contains application's
+ * primary code and assets.
*
* <p>Note: this is not generally useful for applications, since they should
* not be directly accessing the file system.
@@ -1132,6 +1131,7 @@
* you're running long tasks.
*/
public static final String POWER_SERVICE = "power";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.view.WindowManager} for accessing the system's window
@@ -1141,6 +1141,7 @@
* @see android.view.WindowManager
*/
public static final String WINDOW_SERVICE = "window";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.view.LayoutInflater} for inflating layout resources in this
@@ -1150,6 +1151,7 @@
* @see android.view.LayoutInflater
*/
public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.accounts.AccountManager} for receiving intents at a
@@ -1159,6 +1161,7 @@
* @see android.accounts.AccountManager
*/
public static final String ACCOUNT_SERVICE = "account";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.app.ActivityManager} for interacting with the global
@@ -1168,6 +1171,7 @@
* @see android.app.ActivityManager
*/
public static final String ACTIVITY_SERVICE = "activity";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.app.AlarmManager} for receiving intents at a
@@ -1177,6 +1181,7 @@
* @see android.app.AlarmManager
*/
public static final String ALARM_SERVICE = "alarm";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.app.NotificationManager} for informing the user of
@@ -1186,6 +1191,7 @@
* @see android.app.NotificationManager
*/
public static final String NOTIFICATION_SERVICE = "notification";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.view.accessibility.AccessibilityManager} for giving the user
@@ -1195,6 +1201,7 @@
* @see android.view.accessibility.AccessibilityManager
*/
public static final String ACCESSIBILITY_SERVICE = "accessibility";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.app.NotificationManager} for controlling keyguard.
@@ -1203,6 +1210,7 @@
* @see android.app.KeyguardManager
*/
public static final String KEYGUARD_SERVICE = "keyguard";
+
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.location.LocationManager} for controlling location
@@ -1212,6 +1220,7 @@
* @see android.location.LocationManager
*/
public static final String LOCATION_SERVICE = "location";
+
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.app.SearchManager} for handling searches.
@@ -1220,6 +1229,7 @@
* @see android.app.SearchManager
*/
public static final String SEARCH_SERVICE = "search";
+
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.hardware.SensorManager} for accessing sensors.
@@ -1228,6 +1238,7 @@
* @see android.hardware.SensorManager
*/
public static final String SENSOR_SERVICE = "sensor";
+
/**
* Use with {@link #getSystemService} to retrieve a
* com.android.server.WallpaperService for accessing wallpapers.
@@ -1235,6 +1246,7 @@
* @see #getSystemService
*/
public static final String WALLPAPER_SERVICE = "wallpaper";
+
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.os.Vibrator} for interacting with the vibration hardware.
@@ -1243,6 +1255,7 @@
* @see android.os.Vibrator
*/
public static final String VIBRATOR_SERVICE = "vibrator";
+
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.app.StatusBarManager} for interacting with the status bar.
@@ -1340,6 +1353,15 @@
public static final String DROPBOX_SERVICE = "dropbox";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.DevicePolicyManager} for working with global
+ * device policy management.
+ *
+ * @see #getSystemService
+ */
+ public static final String DEVICE_POLICY_SERVICE = "device_policy";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 3e8a55f..a7ea507 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -252,12 +252,20 @@
public String sourceDir;
/**
- * Full path to the location of the publicly available parts of this package (i.e. the resources
- * and manifest). For non-forward-locked apps this will be the same as {@link #sourceDir).
+ * Full path to the location of the publicly available parts of this
+ * package (i.e. the primary resource package and manifest). For
+ * non-forward-locked apps this will be the same as {@link #sourceDir).
*/
public String publicSourceDir;
/**
+ * Full paths to the locations of extra resource packages this application
+ * uses. This field is only used if there are extra resource packages,
+ * otherwise it is null.
+ */
+ public String[] resourceDirs;
+
+ /**
* Paths to all shared libraries this application is linked against. This
* field is only set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
* PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving
@@ -310,6 +318,7 @@
+ " processName=" + processName);
pw.println(prefix + "sourceDir=" + sourceDir);
pw.println(prefix + "publicSourceDir=" + publicSourceDir);
+ pw.println(prefix + "resourceDirs=" + resourceDirs);
pw.println(prefix + "dataDir=" + dataDir);
if (sharedLibraryFiles != null) {
pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
@@ -360,6 +369,7 @@
flags = orig.flags;
sourceDir = orig.sourceDir;
publicSourceDir = orig.publicSourceDir;
+ resourceDirs = orig.resourceDirs;
sharedLibraryFiles = orig.sharedLibraryFiles;
dataDir = orig.dataDir;
uid = orig.uid;
@@ -390,6 +400,7 @@
dest.writeInt(flags);
dest.writeString(sourceDir);
dest.writeString(publicSourceDir);
+ dest.writeStringArray(resourceDirs);
dest.writeStringArray(sharedLibraryFiles);
dest.writeString(dataDir);
dest.writeInt(uid);
@@ -420,6 +431,7 @@
flags = source.readInt();
sourceDir = source.readString();
publicSourceDir = source.readString();
+ resourceDirs = source.readStringArray();
sharedLibraryFiles = source.readStringArray();
dataDir = source.readString();
uid = source.readInt();
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 5894c4f..23a408b 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -572,6 +572,26 @@
public native final int addAssetPath(String path);
/**
+ * Add multiple sets of assets to the asset manager at once. See
+ * {@link #addAssetPath(String)} for more information. Returns array of
+ * cookies for each added asset with 0 indicating failure, or null if
+ * the input array of paths is null.
+ * {@hide}
+ */
+ public final int[] addAssetPaths(String[] paths) {
+ if (paths == null) {
+ return null;
+ }
+
+ int[] cookies = new int[paths.length];
+ for (int i = 0; i < paths.length; i++) {
+ cookies[i] = addAssetPath(paths[i]);
+ }
+
+ return cookies;
+ }
+
+ /**
* Determine whether the state in this asset manager is up-to-date with
* the files on the filesystem. If false is returned, you need to
* instantiate a new AssetManager class to see the new data.
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index fb44a62..82490bb 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -249,6 +249,8 @@
*/
public static final int MAX_SQL_CACHE_SIZE = 250;
private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance
+ private int mCacheFullWarnings;
+ private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 5;
/** maintain stats about number of cache hits and misses */
private int mNumCacheHits;
@@ -1858,6 +1860,14 @@
return;
}
+ /* don't cache PRAGMA sql statements.
+ * caching them makes sqlite return incorrect results on pragma sql execution!
+ */
+ String prefixSql = sql.substring(0, 6);
+ if (prefixSql.toLowerCase().startsWith("pragma")) {
+ return;
+ }
+
SQLiteCompiledSql compiledSql = null;
synchronized(mCompiledQueries) {
// don't insert the new mapping if a mapping already exists
@@ -1873,13 +1883,30 @@
* JNI method. If entire cache is wiped out, it could cause a big GC activity
* just because a (rogue) process is using the cache incorrectly.
*/
- Log.w(TAG, "Too many sql statements in database cache. Make sure your sql " +
+ Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " +
+ getPath() + "; i.e., NO space for this sql statement in cache: " +
+ sql + ". Make sure your sql " +
"statements are using prepared-sql-statement syntax with '?' for " +
"bindargs, instead of using actual values");
- Set<String> keySet = mCompiledQueries.keySet();
- for (String s : keySet) {
- mCompiledQueries.remove(s);
- break;
+
+ /* increment the number of times this warnings has been printed.
+ * if this warning is printed too many times, clear the whole cache - the app
+ * is doing something weird or incorrect and printing more warnings will only
+ * flood the logfile.
+ */
+ if (++mCacheFullWarnings > MAX_WARNINGS_ON_CACHESIZE_CONDITION) {
+ mCacheFullWarnings = 0;
+ // clear the cache
+ mCompiledQueries.clear();
+ Log.w(TAG, "compiled-sql statement cache cleared for the database " +
+ getPath());
+ } else {
+ // clear just a single entry from cache
+ Set<String> keySet = mCompiledQueries.keySet();
+ for (String s : keySet) {
+ mCompiledQueries.remove(s);
+ break;
+ }
}
}
mCompiledQueries.put(sql, compiledStatement);
@@ -1906,6 +1933,12 @@
* returns null, if not found in the cache.
*/
/* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) {
+ // don't look for PRAGMA sql statements in compiled-sql cache
+ String prefixSql = sql.substring(0, 6);
+ if (prefixSql.toLowerCase().startsWith("pragma")) {
+ return null;
+ }
+
SQLiteCompiledSql compiledStatement = null;
boolean cacheHit;
synchronized(mCompiledQueries) {
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index 0603ca5..714a611 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -56,7 +56,7 @@
if (Config.LOGV)
Log.v("ddm-hello", "Connected!");
- if (true) {
+ if (false) {
/* test spontaneous transmission */
byte[] data = new byte[] { 0, 1, 2, 3, 4, -4, -3, -2, -1, 127 };
Chunk testChunk =
@@ -148,9 +148,7 @@
private Chunk handleFEAT(Chunk request) {
// TODO: query the VM to ensure that support for these features
// is actually compiled in
- final String[] features = {
- "hprof-heap-dump", "method-trace-profiling"
- };
+ final String[] features = Debug.getVmFeatureList();
if (Config.LOGV)
Log.v("ddm-heap", "Got feature list request");
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
new file mode 100644
index 0000000..d30b63d
--- /dev/null
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Callback class for receiving events from an INetworkManagementService
+ *
+ * @hide
+ */
+interface INetworkManagementEventObserver {
+ /**
+ * Interface link status has changed.
+ *
+ * @param iface The interface.
+ * @param link True if link is up.
+ */
+ void interfaceLinkStatusChanged(String iface, boolean link);
+
+ /**
+ * An interface has been added to the system
+ *
+ * @param iface The interface.
+ */
+ void interfaceAdded(String iface);
+
+ /**
+ * An interface has been removed from the system
+ *
+ * @param iface The interface.
+ */
+ void interfaceRemoved(String iface);
+}
diff --git a/core/java/android/net/InterfaceConfiguration.aidl b/core/java/android/net/InterfaceConfiguration.aidl
new file mode 100644
index 0000000..8aa5e34
--- /dev/null
+++ b/core/java/android/net/InterfaceConfiguration.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2008, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable InterfaceConfiguration;
diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java
new file mode 100644
index 0000000..9aa23fe
--- /dev/null
+++ b/core/java/android/net/InterfaceConfiguration.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * A simple object for retrieving / setting an interfaces configuration
+ * @hide
+ */
+public class InterfaceConfiguration implements Parcelable {
+ public String hwAddr;
+ public int ipAddr;
+ public int netmask;
+ public String interfaceFlags;
+
+ public InterfaceConfiguration() {
+ super();
+ }
+
+ public String toString() {
+ StringBuffer str = new StringBuffer();
+
+ str.append("ipddress "); putAddress(str, ipAddr);
+ str.append(" netmask "); putAddress(str, netmask);
+ str.append(" flags ").append(interfaceFlags);
+ str.append(" hwaddr ").append(hwAddr);
+
+ return str.toString();
+ }
+
+ private static void putAddress(StringBuffer buf, int addr) {
+ buf.append(addr & 0xff).append('.').
+ append((addr >>>= 8) & 0xff).append('.').
+ append((addr >>>= 8) & 0xff).append('.').
+ append((addr >>>= 8) & 0xff);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(hwAddr);
+ dest.writeInt(ipAddr);
+ dest.writeInt(netmask);
+ dest.writeString(interfaceFlags);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final Creator<InterfaceConfiguration> CREATOR =
+ new Creator<InterfaceConfiguration>() {
+ public InterfaceConfiguration createFromParcel(Parcel in) {
+ InterfaceConfiguration info = new InterfaceConfiguration();
+ info.hwAddr = in.readString();
+ info.ipAddr = in.readInt();
+ info.netmask = in.readInt();
+ info.interfaceFlags = in.readString();
+ return info;
+ }
+
+ public InterfaceConfiguration[] newArray(int size) {
+ return new InterfaceConfiguration[size];
+ }
+ };
+}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index b33e8be..7043c2e 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -264,6 +264,17 @@
}
/**
+ * Returns an array of strings that identify VM features. This is
+ * used by DDMS to determine what sorts of operations the VM can
+ * perform.
+ *
+ * @hide
+ */
+ public static String[] getVmFeatureList() {
+ return VMDebug.getVmFeatureList();
+ }
+
+ /**
* Change the JDWP port.
*
* @deprecated no longer needed or useful
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
index e73569a..c0c2d03 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/IMountService.aidl
@@ -94,6 +94,11 @@
String mountSecureContainer(String id, String key, int ownerUid);
/*
+ * Unount a secure container.
+ */
+ void unmountSecureContainer(String id);
+
+ /*
* Returns the filesystem path of a mounted secure container.
*/
String getSecureContainerPath(String id);
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
new file mode 100644
index 0000000..e4ec098
--- /dev/null
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -0,0 +1,150 @@
+/* //device/java/android/android/os/INetworkManagementService.aidl
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+import android.net.InterfaceConfiguration;
+import android.net.INetworkManagementEventObserver;
+
+/**
+ * @hide
+ */
+interface INetworkManagementService
+{
+ /**
+ ** GENERAL
+ **/
+
+ /**
+ * Register an observer to receive events
+ */
+ void registerObserver(INetworkManagementEventObserver obs);
+
+ /**
+ * Unregister an observer from receiving events.
+ */
+ void unregisterObserver(INetworkManagementEventObserver obs);
+
+ /**
+ * Returns a list of currently known network interfaces
+ */
+ String[] listInterfaces();
+
+ /**
+ * Retrieves the specified interface config
+ *
+ */
+ InterfaceConfiguration getInterfaceConfig(String iface);
+
+ /**
+ * Sets the configuration of the specified interface
+ */
+ void setInterfaceConfig(String iface, in InterfaceConfiguration cfg);
+
+ /**
+ * Shuts down the service
+ */
+ void shutdown();
+
+ /**
+ ** TETHERING RELATED
+ **/
+
+
+ /**
+ * Returns true if IP forwarding is enabled
+ */
+ boolean getIpForwardingEnabled();
+
+ /**
+ * Enables/Disables IP Forwarding
+ */
+ void setIpForwardingEnabled(boolean enabled);
+
+ /**
+ * Start tethering services with the specified dhcp server range
+ */
+ void startTethering(String dhcpRangeStart, String dhcpRangeEnd);
+
+ /**
+ * Stop currently running tethering services
+ */
+ void stopTethering();
+
+ /**
+ * Returns true if tethering services are started
+ */
+ boolean isTetheringStarted();
+
+ /**
+ * Tethers the specified interface
+ */
+ void tetherInterface(String iface);
+
+ /**
+ * Untethers the specified interface
+ */
+ void untetherInterface(String iface);
+
+ /**
+ * Returns a list of currently tethered interfaces
+ */
+ String[] listTetheredInterfaces();
+
+ /**
+ * Sets the list of DNS forwarders (in order of priority)
+ */
+ void setDnsForwarders(in String[] dns);
+
+ /**
+ * Returns the list of DNS fowarders (in order of priority)
+ */
+ String[] getDnsForwarders();
+
+ /**
+ * Enables Network Address Translation between two interfaces.
+ * The address and netmask of the external interface is used for
+ * the NAT'ed network.
+ */
+ void enableNat(String internalInterface, String externalInterface);
+
+ /**
+ * Disables Network Address Translation between two interfaces.
+ */
+ void disableNat(String internalInterface, String externalInterface);
+
+ /**
+ ** PPPD
+ **/
+
+ /**
+ * Returns the list of currently known TTY devices on the system
+ */
+ String[] listTtys();
+
+ /**
+ * Attaches a PPP server daemon to the specified TTY with the specified
+ * local/remote addresses.
+ */
+ void attachPppd(String tty, String localAddr, String remoteAddr);
+
+ /**
+ * Detaches a PPP server daemon from the specified TTY.
+ */
+ void detachPppd(String tty);
+
+}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 23762ca..b5408ae 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -28,6 +28,7 @@
void setPokeLock(int pokey, IBinder lock, String tag);
int getSupportedWakeLockFlags();
void setStayOnSetting(int val);
+ void setMaximumScreenOffTimeount(int timeMs);
void preventScreenOn(boolean prevent);
boolean isScreenOn();
void reboot(String reason);
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index c2928cb..fe8cfb0 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -522,7 +522,7 @@
protected void handleParams(String params) throws VCardException {
String[] strArray = params.split("=", 2);
if (strArray.length == 2) {
- String paramName = strArray[0].trim();
+ final String paramName = strArray[0].trim().toUpperCase();
String paramValue = strArray[1].trim();
if (paramName.equals("TYPE")) {
handleType(paramValue);
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index a264594..970d520 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -230,10 +230,14 @@
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
-
if (mSeekBar != null) {
- mSeekBar.setProgress(System.getInt(mContext.getContentResolver(),
- System.VOLUME_SETTINGS[mStreamType], 0));
+ int volume = System.getInt(mContext.getContentResolver(),
+ System.VOLUME_SETTINGS[mStreamType], -1);
+ // Works around an atomicity problem with volume updates
+ // TODO: Fix the actual issue, probably in AudioService
+ if (volume >= 0) {
+ mSeekBar.setProgress(volume);
+ }
}
}
};
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index b876f05..d67169f 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -173,9 +173,32 @@
c.startActivity(i);
}
+ /**
+ * Stores a String extra in an {@link Intent} representing the title of a
+ * page to share. When receiving an {@link Intent#ACTION_SEND} from the
+ * Browser, use this to access the title.
+ * @hide
+ */
+ public final static String EXTRA_SHARE_TITLE = "share_title";
+
+ /**
+ * Stores a Bitmap extra in an {@link Intent} representing the screenshot of
+ * a page to share. When receiving an {@link Intent#ACTION_SEND} from the
+ * Browser, use this to access the screenshot.
+ * @hide
+ */
+ public final static String EXTRA_SHARE_SCREENSHOT = "share_screenshot";
+
+ /**
+ * Stores a Bitmap extra in an {@link Intent} representing the favicon of a
+ * page to share. When receiving an {@link Intent#ACTION_SEND} from the
+ * Browser, use this to access the favicon.
+ * @hide
+ */
+ public final static String EXTRA_SHARE_FAVICON = "share_favicon";
+
public static final void sendString(Context c, String s) {
- sendString(c, s,
- c.getText(com.android.internal.R.string.sendText).toString());
+ sendString(c, s, c.getString(com.android.internal.R.string.sendText));
}
/**
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index 509aac5..cbd8a26 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -62,7 +62,7 @@
public static final String EVENT_BEGIN_TIME = "beginTime";
public static final String EVENT_END_TIME = "endTime";
- public static final String AUTHORITY = "calendar";
+ public static final String AUTHORITY = "com.android.calendar";
/**
* The content:// style URL for the top-level calendar authority
@@ -229,7 +229,7 @@
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/calendars");
+ Uri.parse("content://" + AUTHORITY + "/calendars");
/**
* The default sort order for this table
@@ -332,7 +332,7 @@
public static final class Attendees implements BaseColumns,
AttendeesColumns, EventsColumns {
public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/attendees");
+ Uri.parse("content://" + AUTHORITY + "/attendees");
// TODO: fill out this class when we actually start utilizing attendees
// in the calendar application.
@@ -576,7 +576,8 @@
/**
* The content:// style URL for this table
*/
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/event_entities");
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/event_entities");
/**
* The name of the account instance to which this row belongs, which when paired with
@@ -914,10 +915,10 @@
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/events");
+ Uri.parse("content://" + AUTHORITY + "/events");
public static final Uri DELETED_CONTENT_URI =
- Uri.parse("content://calendar/deleted_events");
+ Uri.parse("content://" + AUTHORITY + "/deleted_events");
/**
* The default sort order for this table
@@ -957,9 +958,10 @@
/**
* The content:// style URL for this table
*/
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/instances/when");
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/instances/when");
public static final Uri CONTENT_BY_DAY_URI =
- Uri.parse("content://calendar/instances/whenbyday");
+ Uri.parse("content://" + AUTHORITY + "/instances/whenbyday");
/**
* The default sort order for this table.
@@ -1075,7 +1077,8 @@
}
public static final class EventDays implements EventDaysColumns {
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/instances/groupbyday");
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/instances/groupbyday");
public static final String[] PROJECTION = { STARTDAY, ENDDAY };
public static final String SELECTION = "selected==1";
@@ -1134,7 +1137,7 @@
public static final class Reminders implements BaseColumns, RemindersColumns, EventsColumns {
public static final String TABLE_NAME = "Reminders";
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/reminders");
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/reminders");
}
public interface CalendarAlertsColumns {
@@ -1210,7 +1213,8 @@
public static final class CalendarAlerts implements BaseColumns,
CalendarAlertsColumns, EventsColumns, CalendarsColumns {
public static final String TABLE_NAME = "CalendarAlerts";
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/calendar_alerts");
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/calendar_alerts");
/**
* This URI is for grouping the query results by event_id and begin
@@ -1219,7 +1223,7 @@
* instances of a repeating event will show up multiple times.
*/
public static final Uri CONTENT_URI_BY_INSTANCE =
- Uri.parse("content://calendar/calendar_alerts/by_instance");
+ Uri.parse("content://" + AUTHORITY + "/calendar_alerts/by_instance");
private static final boolean DEBUG = true;
@@ -1404,9 +1408,30 @@
public static final class ExtendedProperties implements BaseColumns,
ExtendedPropertiesColumns, EventsColumns {
public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/extendedproperties");
+ Uri.parse("content://" + AUTHORITY + "/extendedproperties");
// TODO: fill out this class when we actually start utilizing extendedproperties
// in the calendar application.
}
+
+ /**
+ * A table provided for sync adapters to use for storing private sync state data.
+ *
+ * @see SyncStateContract
+ */
+ public static final class SyncState implements SyncStateContract.Columns {
+ /**
+ * This utility class cannot be instantiated
+ */
+ private SyncState() {}
+
+ public static final String CONTENT_DIRECTORY =
+ SyncStateContract.Constants.CONTENT_DIRECTORY;
+
+ /**
+ * The content:// style URI for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.withAppendedPath(Calendar.CONTENT_URI, CONTENT_DIRECTORY);
+ }
}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 93b5b4d..16746d4 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -474,18 +474,44 @@
public static final String DISPLAY_NAME_SOURCE = "display_name_source";
/**
- * The default text shown as the contact's display name. It is based on
- * available data, see {@link #DISPLAY_NAME_SOURCE}.
+ * <p>
+ * The standard text shown as the contact's display name, based on the best
+ * available information for the contact (for example, it might be the email address
+ * if the name is not available).
+ * The information actually used to compute the name is stored in
+ * {@link #DISPLAY_NAME_SOURCE}.
+ * </p>
+ * <p>
+ * A contacts provider is free to choose whatever representation makes most
+ * sense for its target market.
+ * For example in the default Android Open Source Project implementation,
+ * if the display name is
+ * based on the structured name and the structured name follows
+ * the Western full-name style, then this field contains the "given name first"
+ * version of the full name.
+ * <p>
*
* @see ContactsContract.ContactNameColumns#DISPLAY_NAME_ALTERNATIVE
*/
public static final String DISPLAY_NAME_PRIMARY = "display_name";
/**
- * An alternative representation of the display name. If display name is
+ * <p>
+ * An alternative representation of the display name, such as "family name first"
+ * instead of "given name first" for Western names. If an alternative is not
+ * available, the values should be the same as {@link #DISPLAY_NAME_PRIMARY}.
+ * </p>
+ * <p>
+ * A contacts provider is free to provide alternatives as necessary for
+ * its target market.
+ * For example the default Android Open Source Project contacts provider
+ * currently provides an
+ * alternative in a single case: if the display name is
* based on the structured name and the structured name follows
- * the Western full name style, then this field contains the "family name first"
- * version of the full name. Otherwise, it is the same as DISPLAY_NAME_PRIMARY.
+ * the Western full name style, then the field contains the "family name first"
+ * version of the full name.
+ * Other cases may be added later.
+ * </p>
*/
public static final String DISPLAY_NAME_ALTERNATIVE = "display_name_alt";
@@ -1076,6 +1102,33 @@
* <P>Type: INTEGER</P>
*/
public static final String DELETED = "deleted";
+
+ /**
+ * The "name_verified" flag: "1" means that the name fields on this raw
+ * contact can be trusted and therefore should be used for the entire
+ * aggregated contact.
+ * <p>
+ * If an aggregated contact contains more than one raw contact with a
+ * verified name, one of those verified names is chosen at random.
+ * If an aggregated contact contains no verified names, the
+ * name is chosen randomly from the constituent raw contacts.
+ * </p>
+ * <p>
+ * Updating this flag from "0" to "1" automatically resets it to "0" on
+ * all other raw contacts in the same aggregated contact.
+ * </p>
+ * <p>
+ * Sync adapters should only specify a value for this column when
+ * inserting a raw contact and leave it out when doing an update.
+ * </p>
+ * <p>
+ * The default value is "0"
+ * </p>
+ * <p>Type: INTEGER</p>
+ *
+ * @hide
+ */
+ public static final String NAME_VERIFIED = "name_verified";
}
/**
@@ -1239,9 +1292,6 @@
* </dd>
* </dl>
* <h2>Columns</h2>
- * TODO: include {@link #DISPLAY_NAME_PRIMARY}, {@link #DISPLAY_NAME_ALTERNATIVE},
- * {@link #DISPLAY_NAME_SOURCE}, {@link #PHONETIC_NAME}, {@link #PHONETIC_NAME_STYLE},
- * {@link #SORT_KEY_PRIMARY}, {@link #SORT_KEY_ALTERNATIVE}?
*
* <table class="jd-sumtable">
* <tr>
@@ -1650,6 +1700,7 @@
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, CONTACT_ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, STARRED);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, IS_RESTRICTED);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, NAME_VERIFIED);
android.content.Entity contact = new android.content.Entity(cv);
// read data rows until the contact id changes
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index eb863ef..6bf09b5 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -17,6 +17,11 @@
package android.provider;
import android.net.Uri;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+
+import java.io.File;
/**
* The Download Manager
@@ -1065,5 +1070,27 @@
*/
public static final int VISIBILITY_HIDDEN = 2;
+ /**
+ * Using a file name, create a title for a download. Then store it in
+ * the database and return it.
+ *
+ * @param context Context for reaching the {@link ContentResolver} so
+ * the database can be updated with the new title.
+ * @param filename Full path to the file. Used to generate a title.
+ * @param id Id of the download, so the new title can be stored in the
+ * database
+ * @return String Newly created title.
+ * @hide
+ */
+ public static String createTitleFromFilename(Context context,
+ String filename, long id) {
+ if (filename == null) return null;
+ String title = new File(filename).getName();
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_TITLE, title);
+ context.getContentResolver().update(ContentUris.withAppendedId(
+ CONTENT_URI, id), values, null, null);
+ return title;
+ }
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c0aee4c..dc40113 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -436,6 +436,20 @@
public static final String ACTION_MEMORY_CARD_SETTINGS =
"android.settings.MEMORY_CARD_SETTINGS";
+ /**
+ * Activity Action: Show settings for global search.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SEARCH_SETTINGS =
+ "android.search.action.SEARCH_SETTINGS";
+
// End of Intent actions for Settings
private static final String JID_RESOURCE_PREFIX = "android";
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index fe3b149..eb48a0c 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -20,6 +20,8 @@
import com.android.internal.view.BaseIWindow;
import com.android.internal.view.BaseSurfaceHolder;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
import android.app.Service;
import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
@@ -58,9 +60,13 @@
public abstract class WallpaperService extends Service {
/**
* The {@link Intent} that must be declared as handled by the service.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_WALLPAPER} permission so
+ * that other applications can not abuse it.
*/
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
- "android.service.wallpaper.WallpaperService";
+ "android.service.wallpaper.WallpaperService";
/**
* Name under which a WallpaperService component publishes information
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
new file mode 100644
index 0000000..50bfefc
--- /dev/null
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -0,0 +1,445 @@
+/*
+ * 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 android.view;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+/**
+ * Detects transformation gestures involving more than one pointer ("multitouch")
+ * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener}
+ * callback will notify users when a particular gesture event has occurred.
+ * This class should only be used with {@link MotionEvent}s reported via touch.
+ *
+ * To use this class:
+ * <ul>
+ * <li>Create an instance of the {@code ScaleGestureDetector} for your
+ * {@link View}
+ * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ * {@link #onTouchEvent(MotionEvent)}. The methods defined in your
+ * callback will be executed when the events occur.
+ * </ul>
+ * @hide Pending API approval
+ */
+public class ScaleGestureDetector {
+ /**
+ * The listener for receiving notifications when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface. If you only want to listen for a subset it might
+ * be easier to extend {@link SimpleOnScaleGestureListener}.
+ *
+ * An application will receive events in the following order:
+ * <ul>
+ * <li>One {@link OnScaleGestureListener#onScaleBegin()}
+ * <li>Zero or more {@link OnScaleGestureListener#onScale()}
+ * <li>One {@link OnScaleGestureListener#onTransformEnd()}
+ * </ul>
+ */
+ public interface OnScaleGestureListener {
+ /**
+ * Responds to scaling events for a gesture in progress.
+ * Reported by pointer motion.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should consider this event
+ * as handled. If an event was not handled, the detector
+ * will continue to accumulate movement until an event is
+ * handled. This can be useful if an application, for example,
+ * only wants to update scaling factors if the change is
+ * greater than 0.01.
+ */
+ public boolean onScale(ScaleGestureDetector detector);
+
+ /**
+ * Responds to the beginning of a scaling gesture. Reported by
+ * new pointers going down.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should continue recognizing
+ * this gesture. For example, if a gesture is beginning
+ * with a focal point outside of a region where it makes
+ * sense, onScaleBegin() may return false to ignore the
+ * rest of the gesture.
+ */
+ public boolean onScaleBegin(ScaleGestureDetector detector);
+
+ /**
+ * Responds to the end of a scale gesture. Reported by existing
+ * pointers going up. If the end of a gesture would result in a fling,
+ * {@link onTransformFling()} is called instead.
+ *
+ * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
+ * and {@link ScaleGestureDetector#getFocusY()} will return the location
+ * of the pointer remaining on the screen.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ */
+ public void onScaleEnd(ScaleGestureDetector detector);
+ }
+
+ /**
+ * A convenience class to extend when you only want to listen for a subset
+ * of scaling-related events. This implements all methods in
+ * {@link OnScaleGestureListener} but does nothing.
+ * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} and
+ * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} return
+ * {@code true}.
+ */
+ public class SimpleOnScaleGestureListener implements OnScaleGestureListener {
+
+ public boolean onScale(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ // Intentionally empty
+ }
+ }
+
+ private static final float PRESSURE_THRESHOLD = 0.67f;
+
+ private Context mContext;
+ private OnScaleGestureListener mListener;
+ private boolean mGestureInProgress;
+
+ private MotionEvent mPrevEvent;
+ private MotionEvent mCurrEvent;
+
+ private float mFocusX;
+ private float mFocusY;
+ private float mPrevFingerDiffX;
+ private float mPrevFingerDiffY;
+ private float mCurrFingerDiffX;
+ private float mCurrFingerDiffY;
+ private float mCurrLen;
+ private float mPrevLen;
+ private float mScaleFactor;
+ private float mCurrPressure;
+ private float mPrevPressure;
+ private long mTimeDelta;
+
+ private float mEdgeSlop;
+ private float mRightSlopEdge;
+ private float mBottomSlopEdge;
+ private boolean mSloppyGesture;
+
+ public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
+ ViewConfiguration config = ViewConfiguration.get(context);
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ mContext = context;
+ mListener = listener;
+ mEdgeSlop = config.getScaledEdgeSlop();
+ mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
+ mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ final int action = event.getAction();
+ boolean handled = true;
+
+ if (!mGestureInProgress) {
+ if ((action == MotionEvent.ACTION_POINTER_1_DOWN ||
+ action == MotionEvent.ACTION_POINTER_2_DOWN) &&
+ event.getPointerCount() >= 2) {
+ // We have a new multi-finger gesture
+
+ // Be paranoid in case we missed an event
+ reset();
+
+ mPrevEvent = MotionEvent.obtain(event);
+ mTimeDelta = 0;
+
+ setContext(event);
+
+ // Check if we have a sloppy gesture. If so, delay
+ // the beginning of the gesture until we're sure that's
+ // what the user wanted. Sloppy gestures can happen if the
+ // edge of the user's hand is touching the screen, for example.
+ final float edgeSlop = mEdgeSlop;
+ final float rightSlop = mRightSlopEdge;
+ final float bottomSlop = mBottomSlopEdge;
+ final float x0 = event.getRawX();
+ final float y0 = event.getRawY();
+ final float x1 = getRawX(event, 1);
+ final float y1 = getRawY(event, 1);
+
+ boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop ||
+ x1 < edgeSlop || y1 < edgeSlop;
+ boolean p1sloppy = x0 > rightSlop || y0 > bottomSlop ||
+ x1 > rightSlop || y1 > bottomSlop;
+
+ if (p0sloppy) {
+ mFocusX = event.getX(1);
+ mFocusY = event.getY(1);
+ mSloppyGesture = true;
+ } else if (p1sloppy) {
+ mFocusX = event.getX(0);
+ mFocusY = event.getY(0);
+ mSloppyGesture = true;
+ } else {
+ mGestureInProgress = mListener.onScaleBegin(this);
+ }
+ } else if (action == MotionEvent.ACTION_MOVE && mSloppyGesture) {
+ // Initiate sloppy gestures if we've moved outside of the slop area.
+ final float edgeSlop = mEdgeSlop;
+ final float rightSlop = mRightSlopEdge;
+ final float bottomSlop = mBottomSlopEdge;
+ final float x0 = event.getRawX();
+ final float y0 = event.getRawY();
+ final float x1 = getRawX(event, 1);
+ final float y1 = getRawY(event, 1);
+
+ boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop ||
+ x1 < edgeSlop || y1 < edgeSlop;
+ boolean p1sloppy = x0 > rightSlop || y0 > bottomSlop ||
+ x1 > rightSlop || y1 > bottomSlop;
+
+ if (p0sloppy) {
+ mFocusX = event.getX(1);
+ mFocusY = event.getY(1);
+ } else if (p1sloppy) {
+ mFocusX = event.getX(0);
+ mFocusY = event.getY(0);
+ } else {
+ mSloppyGesture = false;
+ mGestureInProgress = mListener.onScaleBegin(this);
+ }
+ }
+ } else {
+ // Transform gesture in progress - attempt to handle it
+ switch (action) {
+ case MotionEvent.ACTION_POINTER_1_UP:
+ case MotionEvent.ACTION_POINTER_2_UP:
+ // Gesture ended
+ setContext(event);
+
+ // Set focus point to the remaining finger
+ int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
+ >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+ mFocusX = event.getX(id);
+ mFocusY = event.getY(id);
+
+ if (!mSloppyGesture) {
+ mListener.onScaleEnd(this);
+ }
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (!mSloppyGesture) {
+ mListener.onScaleEnd(this);
+ }
+
+ reset();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ setContext(event);
+
+ // Only accept the event if our relative pressure is within
+ // a certain limit - this can help filter shaky data as a
+ // finger is lifted.
+ if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
+ final boolean updatePrevious = mListener.onScale(this);
+
+ if (updatePrevious) {
+ mPrevEvent.recycle();
+ mPrevEvent = MotionEvent.obtain(event);
+ }
+ }
+ break;
+ }
+ }
+ return handled;
+ }
+
+ /**
+ * MotionEvent has no getRawX(int) method; simulate it pending future API approval.
+ */
+ private static float getRawX(MotionEvent event, int pointerIndex) {
+ float offset = event.getX() - event.getRawX();
+ return event.getX(pointerIndex) + offset;
+ }
+
+ /**
+ * MotionEvent has no getRawY(int) method; simulate it pending future API approval.
+ */
+ private static float getRawY(MotionEvent event, int pointerIndex) {
+ float offset = event.getY() - event.getRawY();
+ return event.getY(pointerIndex) + offset;
+ }
+
+ private void setContext(MotionEvent curr) {
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ }
+ mCurrEvent = MotionEvent.obtain(curr);
+
+ mCurrLen = -1;
+ mPrevLen = -1;
+ mScaleFactor = -1;
+
+ final MotionEvent prev = mPrevEvent;
+
+ final float px0 = prev.getX(0);
+ final float py0 = prev.getY(0);
+ final float px1 = prev.getX(1);
+ final float py1 = prev.getY(1);
+ final float cx0 = curr.getX(0);
+ final float cy0 = curr.getY(0);
+ final float cx1 = curr.getX(1);
+ final float cy1 = curr.getY(1);
+
+ final float pvx = px1 - px0;
+ final float pvy = py1 - py0;
+ final float cvx = cx1 - cx0;
+ final float cvy = cy1 - cy0;
+ mPrevFingerDiffX = pvx;
+ mPrevFingerDiffY = pvy;
+ mCurrFingerDiffX = cvx;
+ mCurrFingerDiffY = cvy;
+
+ mFocusX = cx0 + cvx * 0.5f;
+ mFocusY = cy0 + cvy * 0.5f;
+ mTimeDelta = curr.getEventTime() - prev.getEventTime();
+ mCurrPressure = curr.getPressure(0) + curr.getPressure(1);
+ mPrevPressure = prev.getPressure(0) + prev.getPressure(1);
+ }
+
+ private void reset() {
+ if (mPrevEvent != null) {
+ mPrevEvent.recycle();
+ mPrevEvent = null;
+ }
+ if (mCurrEvent != null) {
+ mCurrEvent.recycle();
+ mCurrEvent = null;
+ }
+ mSloppyGesture = false;
+ mGestureInProgress = false;
+ }
+
+ /**
+ * Returns {@code true} if a two-finger scale gesture is in progress.
+ * @return {@code true} if a scale gesture is in progress, {@code false} otherwise.
+ */
+ public boolean isInProgress() {
+ return mGestureInProgress;
+ }
+
+ /**
+ * Get the X coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is directly between
+ * the two pointers forming the gesture.
+ * If a gesture is ending, the focal point is the location of the
+ * remaining pointer on the screen.
+ * If {@link isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return X coordinate of the focal point in pixels.
+ */
+ public float getFocusX() {
+ return mFocusX;
+ }
+
+ /**
+ * Get the Y coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is directly between
+ * the two pointers forming the gesture.
+ * If a gesture is ending, the focal point is the location of the
+ * remaining pointer on the screen.
+ * If {@link isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return Y coordinate of the focal point in pixels.
+ */
+ public float getFocusY() {
+ return mFocusY;
+ }
+
+ /**
+ * Return the current distance between the two pointers forming the
+ * gesture in progress.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpan() {
+ if (mCurrLen == -1) {
+ final float cvx = mCurrFingerDiffX;
+ final float cvy = mCurrFingerDiffY;
+ mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy);
+ }
+ return mCurrLen;
+ }
+
+ /**
+ * Return the previous distance between the two pointers forming the
+ * gesture in progress.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpan() {
+ if (mPrevLen == -1) {
+ final float pvx = mPrevFingerDiffX;
+ final float pvy = mPrevFingerDiffY;
+ mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy);
+ }
+ return mPrevLen;
+ }
+
+ /**
+ * Return the scaling factor from the previous scale event to the current
+ * event. This value is defined as
+ * ({@link getCurrentSpan()} / {@link getPreviousSpan()}).
+ *
+ * @return The current scaling factor.
+ */
+ public float getScaleFactor() {
+ if (mScaleFactor == -1) {
+ mScaleFactor = getCurrentSpan() / getPreviousSpan();
+ }
+ return mScaleFactor;
+ }
+
+ /**
+ * Return the time difference in milliseconds between the previous
+ * accepted scaling event and the current scaling event.
+ *
+ * @return Time difference since the last scaling event in milliseconds.
+ */
+ public long getTimeDelta() {
+ return mTimeDelta;
+ }
+
+ /**
+ * Return the event time of the current event being processed.
+ *
+ * @return Current event time in milliseconds.
+ */
+ public long getEventTime() {
+ return mCurrEvent.getEventTime();
+ }
+}
diff --git a/core/java/android/view/TransformGestureDetector.java b/core/java/android/view/TransformGestureDetector.java
deleted file mode 100644
index 196716a..0000000
--- a/core/java/android/view/TransformGestureDetector.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * 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 android.view;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.GestureDetector.SimpleOnGestureListener;
-
-/**
- * Detects transformation gestures involving more than one pointer ("multitouch")
- * using the supplied {@link MotionEvent}s. The {@link OnGestureListener} callback
- * will notify users when a particular gesture event has occurred. This class
- * should only be used with {@link MotionEvent}s reported via touch.
- *
- * To use this class:
- * <ul>
- * <li>Create an instance of the {@code TransformGestureDetector} for your
- * {@link View}
- * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
- * {@link #onTouchEvent(MotionEvent)}. The methods defined in your
- * callback will be executed when the events occur.
- * </ul>
- * @hide Pending API approval
- */
-public class TransformGestureDetector {
- /**
- * The listener for receiving notifications when gestures occur.
- * If you want to listen for all the different gestures then implement
- * this interface. If you only want to listen for a subset it might
- * be easier to extend {@link SimpleOnGestureListener}.
- *
- * An application will receive events in the following order:
- * One onTransformBegin()
- * Zero or more onTransform()
- * One onTransformEnd() or onTransformFling()
- */
- public interface OnTransformGestureListener {
- /**
- * Responds to transformation events for a gesture in progress.
- * Reported by pointer motion.
- *
- * @param detector The detector reporting the event - use this to
- * retrieve extended info about event state.
- * @return true if the event was handled, false otherwise.
- */
- public boolean onTransform(TransformGestureDetector detector);
-
- /**
- * Responds to the beginning of a transformation gesture. Reported by
- * new pointers going down.
- *
- * @param detector The detector reporting the event - use this to
- * retrieve extended info about event state.
- * @return true if the event was handled, false otherwise.
- */
- public boolean onTransformBegin(TransformGestureDetector detector);
-
- /**
- * Responds to the end of a transformation gesture. Reported by existing
- * pointers going up. If the end of a gesture would result in a fling,
- * onTransformFling is called instead.
- *
- * @param detector The detector reporting the event - use this to
- * retrieve extended info about event state.
- * @return true if the event was handled, false otherwise.
- */
- public boolean onTransformEnd(TransformGestureDetector detector);
-
- /**
- * Responds to the end of a transformation gesture that begins a fling.
- * Reported by existing pointers going up. If the end of a gesture
- * would not result in a fling, onTransformEnd is called instead.
- *
- * @param detector The detector reporting the event - use this to
- * retrieve extended info about event state.
- * @return true if the event was handled, false otherwise.
- */
- public boolean onTransformFling(TransformGestureDetector detector);
- }
-
- private static final boolean DEBUG = false;
-
- private static final int INITIAL_EVENT_IGNORES = 2;
-
- private Context mContext;
- private float mTouchSizeScale;
- private OnTransformGestureListener mListener;
- private int mVelocityTimeUnits;
- private MotionEvent mInitialEvent;
-
- private MotionEvent mPrevEvent;
- private MotionEvent mCurrEvent;
- private VelocityTracker mVelocityTracker;
-
- private float mCenterX;
- private float mCenterY;
- private float mTransX;
- private float mTransY;
- private float mPrevFingerDiffX;
- private float mPrevFingerDiffY;
- private float mCurrFingerDiffX;
- private float mCurrFingerDiffY;
- private float mRotateDegrees;
- private float mCurrLen;
- private float mPrevLen;
- private float mScaleFactor;
-
- // Units in pixels. Current value is pulled out of thin air for debugging only.
- private float mPointerJumpLimit = 30;
-
- private int mEventIgnoreCount;
-
- public TransformGestureDetector(Context context, OnTransformGestureListener listener,
- int velocityTimeUnits) {
- mContext = context;
- mListener = listener;
- mTouchSizeScale = context.getResources().getDisplayMetrics().widthPixels/3;
- mVelocityTimeUnits = velocityTimeUnits;
- mEventIgnoreCount = INITIAL_EVENT_IGNORES;
- }
-
- public TransformGestureDetector(Context context, OnTransformGestureListener listener) {
- this(context, listener, 1000);
- }
-
- public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getAction();
- boolean handled = true;
-
- if (mInitialEvent == null) {
- // No transform gesture in progress
- if ((action == MotionEvent.ACTION_POINTER_1_DOWN ||
- action == MotionEvent.ACTION_POINTER_2_DOWN) &&
- event.getPointerCount() >= 2) {
- // We have a new multi-finger gesture
- mInitialEvent = MotionEvent.obtain(event);
- mPrevEvent = MotionEvent.obtain(event);
- mVelocityTracker = VelocityTracker.obtain();
- handled = mListener.onTransformBegin(this);
- }
- } else {
- // Transform gesture in progress - attempt to handle it
- switch (action) {
- case MotionEvent.ACTION_POINTER_1_UP:
- case MotionEvent.ACTION_POINTER_2_UP:
- // Gesture ended
- handled = mListener.onTransformEnd(this);
-
- reset();
- break;
-
- case MotionEvent.ACTION_CANCEL:
- handled = mListener.onTransformEnd(this);
-
- reset();
- break;
-
- case MotionEvent.ACTION_MOVE:
- setContext(event);
-
- // Our first few events can be crazy from some touchscreens - drop them.
- if (mEventIgnoreCount == 0) {
- mVelocityTracker.addMovement(event);
- handled = mListener.onTransform(this);
- } else {
- mEventIgnoreCount--;
- }
-
- mPrevEvent.recycle();
- mPrevEvent = MotionEvent.obtain(event);
- break;
- }
- }
- return handled;
- }
-
- private void setContext(MotionEvent curr) {
- mCurrEvent = MotionEvent.obtain(curr);
-
- mRotateDegrees = -1;
- mCurrLen = -1;
- mPrevLen = -1;
- mScaleFactor = -1;
-
- final MotionEvent prev = mPrevEvent;
-
- float px0 = prev.getX(0);
- float py0 = prev.getY(0);
- float px1 = prev.getX(1);
- float py1 = prev.getY(1);
- float cx0 = curr.getX(0);
- float cy0 = curr.getY(0);
- float cx1 = curr.getX(1);
- float cy1 = curr.getY(1);
-
- // Some touchscreens do weird things with pointer values where points are
- // too close along one axis. Try to detect this here and smooth things out.
- // The main indicator is that we get the X or Y value from the other pointer.
- final float dx0 = cx0 - px0;
- final float dy0 = cy0 - py0;
- final float dx1 = cx1 - px1;
- final float dy1 = cy1 - py1;
-
- if (cx0 == cx1) {
- if (Math.abs(dx0) > mPointerJumpLimit) {
- cx0 = px0;
- } else if (Math.abs(dx1) > mPointerJumpLimit) {
- cx1 = px1;
- }
- } else if (cy0 == cy1) {
- if (Math.abs(dy0) > mPointerJumpLimit) {
- cy0 = py0;
- } else if (Math.abs(dy1) > mPointerJumpLimit) {
- cy1 = py1;
- }
- }
-
- final float pvx = px1 - px0;
- final float pvy = py1 - py0;
- final float cvx = cx1 - cx0;
- final float cvy = cy1 - cy0;
- mPrevFingerDiffX = pvx;
- mPrevFingerDiffY = pvy;
- mCurrFingerDiffX = cvx;
- mCurrFingerDiffY = cvy;
-
- final float pmidx = px0 + pvx * 0.5f;
- final float pmidy = py0 + pvy * 0.5f;
- final float cmidx = cx0 + cvx * 0.5f;
- final float cmidy = cy0 + cvy * 0.5f;
-
- mCenterX = cmidx;
- mCenterY = cmidy;
- mTransX = cmidx - pmidx;
- mTransY = cmidy - pmidy;
- }
-
- private void reset() {
- if (mInitialEvent != null) {
- mInitialEvent.recycle();
- mInitialEvent = null;
- }
- if (mPrevEvent != null) {
- mPrevEvent.recycle();
- mPrevEvent = null;
- }
- if (mCurrEvent != null) {
- mCurrEvent.recycle();
- mCurrEvent = null;
- }
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- mEventIgnoreCount = INITIAL_EVENT_IGNORES;
- }
-
- public float getCenterX() {
- return mCenterX;
- }
-
- public float getCenterY() {
- return mCenterY;
- }
-
- public float getTranslateX() {
- return mTransX;
- }
-
- public float getTranslateY() {
- return mTransY;
- }
-
- public float getCurrentSpan() {
- if (mCurrLen == -1) {
- final float cvx = mCurrFingerDiffX;
- final float cvy = mCurrFingerDiffY;
- mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy);
- }
- return mCurrLen;
- }
-
- public float getPreviousSpan() {
- if (mPrevLen == -1) {
- final float pvx = mPrevFingerDiffX;
- final float pvy = mPrevFingerDiffY;
- mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy);
- }
- return mPrevLen;
- }
-
- public float getScaleFactor() {
- if (mScaleFactor == -1) {
- mScaleFactor = getCurrentSpan() / getPreviousSpan();
- }
- return mScaleFactor;
- }
-
- public float getRotation() {
- throw new UnsupportedOperationException();
- }
-}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 3ebe1c2..616485e 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -90,6 +90,15 @@
public static final boolean TRACE_RECYCLER = false;
/**
+ * Enables or disables motion events tracing. Any invoker of
+ * {@link #trace(View, MotionEvent, MotionEventTraceType)} should first check
+ * that this value is set to true as not to affect performance.
+ *
+ * @hide
+ */
+ public static final boolean TRACE_MOTION_EVENTS = false;
+
+ /**
* The system property of dynamic switch for capturing view information
* when it is set, we dump interested fields and methods for the view on focus
*/
@@ -367,7 +376,6 @@
BIND_VIEW,
RECYCLE_FROM_ACTIVE_HEAP,
RECYCLE_FROM_SCRAP_HEAP,
- MOVE_TO_ACTIVE_HEAP,
MOVE_TO_SCRAP_HEAP,
MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
}
@@ -385,6 +393,21 @@
private static String sRecyclerTracePrefix;
/**
+ * Defines the type of motion events trace to output to the motion events traces file.
+ *
+ * @hide
+ */
+ public enum MotionEventTraceType {
+ DISPATCH,
+ ON_INTERCEPT,
+ ON_TOUCH
+ }
+
+ private static BufferedWriter sMotionEventTraces;
+ private static ViewRoot sMotionEventRoot;
+ private static String sMotionEventTracePrefix;
+
+ /**
* Returns the number of instanciated Views.
*
* @return The number of Views instanciated in the current process.
@@ -675,6 +698,146 @@
sHierarhcyRoot = null;
}
+ /**
+ * Outputs a trace to the currently opened traces file. The trace contains the class name
+ * and instance's hashcode of the specified view as well as the supplied trace type.
+ *
+ * @param view the view to trace
+ * @param event the event of the trace
+ * @param type the type of the trace
+ *
+ * @hide
+ */
+ public static void trace(View view, MotionEvent event, MotionEventTraceType type) {
+ if (sMotionEventTraces == null) {
+ return;
+ }
+
+ try {
+ sMotionEventTraces.write(type.name());
+ sMotionEventTraces.write(' ');
+ sMotionEventTraces.write(event.getAction());
+ sMotionEventTraces.write(' ');
+ sMotionEventTraces.write(view.getClass().getName());
+ sMotionEventTraces.write('@');
+ sMotionEventTraces.write(Integer.toHexString(view.hashCode()));
+ sHierarchyTraces.newLine();
+ } catch (IOException e) {
+ Log.w("View", "Error while dumping trace of event " + event + " for view " + view);
+ }
+ }
+
+ /**
+ * Starts tracing the motion events for the hierarchy of the specificy view.
+ * The trace is identified by a prefix, used to build the traces files names:
+ * <code>/EXTERNAL/motion-events/PREFIX.traces</code> and
+ * <code>/EXTERNAL/motion-events/PREFIX.tree</code>.
+ *
+ * Only one view hierarchy can be traced at the same time. After calling this method, any
+ * other invocation will result in a <code>IllegalStateException</code> unless
+ * {@link #stopMotionEventTracing()} is invoked before.
+ *
+ * Calling this method creates the file <code>/EXTERNAL/motion-events/PREFIX.traces</code>
+ * containing all the traces (or method calls) relative to the specified view's hierarchy.
+ *
+ * This method will return immediately if TRACE_HIERARCHY is false.
+ *
+ * @param prefix the traces files name prefix
+ * @param view the view whose hierarchy must be traced
+ *
+ * @see #stopMotionEventTracing()
+ * @see #trace(View, MotionEvent, android.view.ViewDebug.MotionEventTraceType)
+ *
+ * @hide
+ */
+ public static void startMotionEventTracing(String prefix, View view) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (!TRACE_MOTION_EVENTS) {
+ return;
+ }
+
+ if (sMotionEventRoot != null) {
+ throw new IllegalStateException("You must call stopMotionEventTracing() before running" +
+ " a new trace!");
+ }
+
+ File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "motion-events/");
+ //noinspection ResultOfMethodCallIgnored
+ hierarchyDump.mkdirs();
+
+ hierarchyDump = new File(hierarchyDump, prefix + ".traces");
+ sMotionEventTracePrefix = prefix;
+
+ try {
+ sMotionEventTraces = new BufferedWriter(new FileWriter(hierarchyDump), 32 * 1024);
+ } catch (IOException e) {
+ Log.e("View", "Could not dump view hierarchy");
+ return;
+ }
+
+ sMotionEventRoot = (ViewRoot) view.getRootView().getParent();
+ }
+
+ /**
+ * Stops the current motion events tracing. This method closes the file
+ * <code>/EXTERNAL/motion-events/PREFIX.traces</code>.
+ *
+ * Calling this method creates the file <code>/EXTERNAL/motion-events/PREFIX.tree</code>
+ * containing the view hierarchy of the view supplied to
+ * {@link #startMotionEventTracing(String, View)}.
+ *
+ * This method will return immediately if TRACE_HIERARCHY is false.
+ *
+ * @see #startMotionEventTracing(String, View)
+ * @see #trace(View, MotionEvent, android.view.ViewDebug.MotionEventTraceType)
+ *
+ * @hide
+ */
+ public static void stopMotionEventTracing() {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (!TRACE_MOTION_EVENTS) {
+ return;
+ }
+
+ if (sMotionEventRoot == null || sMotionEventTraces == null) {
+ throw new IllegalStateException("You must call startMotionEventTracing() before" +
+ " stopMotionEventTracing()!");
+ }
+
+ try {
+ sMotionEventTraces.close();
+ } catch (IOException e) {
+ Log.e("View", "Could not write view traces");
+ }
+ sMotionEventTraces = null;
+
+ File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "motion-events/");
+ //noinspection ResultOfMethodCallIgnored
+ hierarchyDump.mkdirs();
+ hierarchyDump = new File(hierarchyDump, sMotionEventTracePrefix + ".tree");
+
+ BufferedWriter out;
+ try {
+ out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
+ } catch (IOException e) {
+ Log.e("View", "Could not dump view hierarchy");
+ return;
+ }
+
+ View view = sMotionEventRoot.getView();
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ dumpViewHierarchy(group, out, 0);
+ try {
+ out.close();
+ } catch (IOException e) {
+ Log.e("View", "Could not dump view hierarchy");
+ }
+ }
+
+ sHierarhcyRoot = null;
+ }
+
static void dispatchCommand(View view, String command, String parameters,
OutputStream clientStream) throws IOException {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 763f273..49c2d0e 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2268,7 +2268,7 @@
addInArray(child, index);
child.mParent = this;
- child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN;
+ child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK & ~DRAWING_CACHE_VALID) | DRAWN;
if (child.hasFocus()) {
requestChildFocus(child, child.findFocus());
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index a5e0e94..2ddf5f8 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -16,6 +16,8 @@
package android.view.inputmethod;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.ResultReceiver;
@@ -54,9 +56,12 @@
/**
* This is the interface name that a service implementing an input
* method should say that it supports -- that is, this is the action it
- * uses for its intent filter. (Note: this name is used because this
- * interface should be moved to the view package.)
+ * uses for its intent filter.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission so
+ * that other applications can not abuse it.
*/
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.view.InputMethod";
/**
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index d5bb572..35f1ac6 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -412,6 +412,7 @@
*/
public void setSupportZoom(boolean support) {
mSupportZoom = support;
+ mWebView.updateMultiTouchSupport(mContext);
}
/**
@@ -426,6 +427,7 @@
*/
public void setBuiltInZoomControls(boolean enabled) {
mBuiltInZoomControls = enabled;
+ mWebView.updateMultiTouchSupport(mContext);
}
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index db5641c..56650a6 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -21,6 +21,7 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
+import android.content.pm.PackageManager;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -48,6 +49,7 @@
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
@@ -55,6 +57,8 @@
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AlphaAnimation;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebTextView.AutoCompleteAdapter;
import android.webkit.WebViewCore.EventHub;
@@ -360,6 +364,7 @@
private static final int TOUCH_DOUBLE_TAP_MODE = 6;
private static final int TOUCH_DONE_MODE = 7;
private static final int TOUCH_SELECT_MODE = 8;
+ private static final int TOUCH_PINCH_DRAG = 9;
// Whether to forward the touch events to WebCore
private boolean mForwardTouchEvents = false;
@@ -458,6 +463,18 @@
private static final int MOTIONLESS_TRUE = 2;
private int mHeldMotionless;
+ // whether support multi-touch
+ private static boolean mSupportMultiTouch;
+ // use the framework's ScaleGestureDetector to handle multi-touch
+ private ScaleGestureDetector mScaleDetector;
+ // minimum scale change during multi-touch zoom
+ private static float PREVIEW_SCALE_INCREMENT = 0.01f;
+
+ // the anchor point in the document space where VIEW_SIZE_CHANGED should
+ // apply to
+ private int mAnchorX;
+ private int mAnchorY;
+
/**
* Private message ids
*/
@@ -467,7 +484,7 @@
private static final int SWITCH_TO_LONGPRESS = 4;
private static final int RELEASE_SINGLE_TAP = 5;
private static final int REQUEST_FORM_DATA = 6;
- private static final int RESUME_WEBCORE_UPDATE = 7;
+ private static final int RESUME_WEBCORE_PRIORITY = 7;
private static final int DRAG_HELD_MOTIONLESS = 8;
private static final int AWAKEN_SCROLL_BARS = 9;
@@ -486,7 +503,7 @@
static final int MOVE_OUT_OF_PLUGIN = 19;
static final int CLEAR_TEXT_ENTRY = 20;
static final int UPDATE_TEXT_SELECTION_MSG_ID = 21;
-
+ static final int SHOW_RECT_MSG_ID = 22;
static final int LONG_PRESS_CENTER = 23;
static final int PREVENT_TOUCH_ID = 24;
static final int WEBCORE_NEED_TOUCH_EVENTS = 25;
@@ -508,7 +525,7 @@
"SWITCH_TO_LONGPRESS", // = 4;
"RELEASE_SINGLE_TAP", // = 5;
"REQUEST_FORM_DATA", // = 6;
- "RESUME_WEBCORE_UPDATE", // = 7;
+ "RESUME_WEBCORE_PRIORITY", // = 7;
"DRAG_HELD_MOTIONLESS", // = 8;
"AWAKEN_SCROLL_BARS", // = 9;
"SCROLL_TO_MSG_ID", // = 10;
@@ -523,7 +540,7 @@
"MOVE_OUT_OF_PLUGIN", // = 19;
"CLEAR_TEXT_ENTRY", // = 20;
"UPDATE_TEXT_SELECTION_MSG_ID", // = 21;
- "22", // = 22;
+ "SHOW_RECT_MSG_ID", // = 22;
"LONG_PRESS_CENTER", // = 23;
"PREVENT_TOUCH_ID", // = 24;
"WEBCORE_NEED_TOUCH_EVENTS", // = 25;
@@ -568,13 +585,13 @@
// ideally mZoomOverviewWidth should be mContentWidth. But sites like espn,
// engadget always have wider mContentWidth no matter what viewport size is.
int mZoomOverviewWidth = DEFAULT_VIEWPORT_WIDTH;
- float mLastScale;
+ float mTextWrapScale;
// default scale. Depending on the display density.
static int DEFAULT_SCALE_PERCENT;
private float mDefaultScale;
- // set to true temporarily while the zoom control is being dragged
+ // set to true temporarily during ScaleGesture triggered zoom
private boolean mPreviewZoomOnly = false;
// computed scale and inverse, from mZoomWidth.
@@ -801,6 +818,20 @@
params;
frameParams.gravity = Gravity.RIGHT;
}
+ updateMultiTouchSupport(context);
+ }
+
+ void updateMultiTouchSupport(Context context) {
+ WebSettings settings = getSettings();
+ mSupportMultiTouch = context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
+ && settings.supportZoom() && settings.getBuiltInZoomControls();
+ if (mSupportMultiTouch && (mScaleDetector == null)) {
+ mScaleDetector = new ScaleGestureDetector(context,
+ new ScaleDetectorListener());
+ } else if (!mSupportMultiTouch && (mScaleDetector != null)) {
+ mScaleDetector = null;
+ }
}
private void updateZoomButtonsEnabled() {
@@ -840,6 +871,7 @@
mDefaultScale = density;
mActualScale = density;
mInvActualScale = 1 / density;
+ mTextWrapScale = density;
DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
@@ -860,7 +892,7 @@
mDefaultScale = density;
mMaxZoomScale *= scaleFactor;
mMinZoomScale *= scaleFactor;
- setNewZoomScale(mActualScale * scaleFactor, false);
+ setNewZoomScale(mActualScale * scaleFactor, true, false);
}
}
@@ -1227,9 +1259,8 @@
b.putInt("scrollX", mScrollX);
b.putInt("scrollY", mScrollY);
b.putFloat("scale", mActualScale);
- if (mInZoomOverview) {
- b.putFloat("lastScale", mLastScale);
- }
+ b.putFloat("textwrapScale", mTextWrapScale);
+ b.putBoolean("overview", mInZoomOverview);
return true;
}
return false;
@@ -1274,13 +1305,8 @@
// onSizeChanged() is called, the rest will be set
// correctly
mActualScale = scale;
- float lastScale = b.getFloat("lastScale", -1.0f);
- if (lastScale > 0) {
- mInZoomOverview = true;
- mLastScale = lastScale;
- } else {
- mInZoomOverview = false;
- }
+ mTextWrapScale = b.getFloat("textwrapScale", scale);
+ mInZoomOverview = b.getBoolean("overview");
invalidate();
return true;
}
@@ -2010,12 +2036,18 @@
contentSizeChanged(updateLayout);
}
- private void setNewZoomScale(float scale, boolean force) {
+ private void setNewZoomScale(float scale, boolean updateTextWrapScale,
+ boolean force) {
if (scale < mMinZoomScale) {
scale = mMinZoomScale;
} else if (scale > mMaxZoomScale) {
scale = mMaxZoomScale;
}
+ if (updateTextWrapScale) {
+ mTextWrapScale = scale;
+ // reset mLastHeightSent to force VIEW_SIZE_CHANGED sent to WebKit
+ mLastHeightSent = 0;
+ }
if (scale != mActualScale || force) {
if (mDrawHistory) {
// If history Picture is drawn, don't update scroll. They will
@@ -2025,9 +2057,7 @@
}
mActualScale = scale;
mInvActualScale = 1 / scale;
- if (!mPreviewZoomOnly) {
- sendViewSizeZoom();
- }
+ sendViewSizeZoom();
} else {
// update our scroll so we don't appear to jump
// i.e. keep the center of the doc in the center of the view
@@ -2055,10 +2085,9 @@
mScrollX = pinLocX(Math.round(sx));
mScrollY = pinLocY(Math.round(sy));
- if (!mPreviewZoomOnly) {
- sendViewSizeZoom();
- sendOurVisibleRect();
- }
+ // update webkit
+ sendViewSizeZoom();
+ sendOurVisibleRect();
}
}
}
@@ -2068,6 +2097,8 @@
private Rect mLastGlobalRect;
private Rect sendOurVisibleRect() {
+ if (mPreviewZoomOnly) return mLastVisibleRectSent;
+
Rect rect = new Rect();
calcOurContentVisibleRect(rect);
// Rect.equals() checks for null input.
@@ -2121,6 +2152,8 @@
int mWidth;
int mHeight;
int mTextWrapWidth;
+ int mAnchorX;
+ int mAnchorY;
float mScale;
boolean mIgnoreHeight;
}
@@ -2132,6 +2165,8 @@
* @return true if new values were sent
*/
private boolean sendViewSizeZoom() {
+ if (mPreviewZoomOnly) return false;
+
int viewWidth = getViewWidth();
int newWidth = Math.round(viewWidth * mInvActualScale);
int newHeight = Math.round(getViewHeight() * mInvActualScale);
@@ -2151,16 +2186,15 @@
ViewSizeData data = new ViewSizeData();
data.mWidth = newWidth;
data.mHeight = newHeight;
- // while in zoom overview mode, the text are wrapped to the screen
- // width matching mLastScale. So that we don't trigger re-flow while
- // toggling between overview mode and normal mode.
- data.mTextWrapWidth = mInZoomOverview ? Math.round(viewWidth
- / mLastScale) : newWidth;
+ data.mTextWrapWidth = Math.round(viewWidth / mTextWrapScale);;
data.mScale = mActualScale;
data.mIgnoreHeight = mZoomScale != 0 && !mHeightCanMeasure;
+ data.mAnchorX = mAnchorX;
+ data.mAnchorY = mAnchorY;
mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
mLastWidthSent = newWidth;
mLastHeightSent = newHeight;
+ mAnchorX = mAnchorY = 0;
return true;
}
return false;
@@ -3098,13 +3132,13 @@
canvas.scale(mActualScale, mActualScale);
}
- mWebViewCore.drawContentPicture(canvas, color, animateZoom,
- animateScroll);
+ mWebViewCore.drawContentPicture(canvas, color,
+ (animateZoom || mPreviewZoomOnly), animateScroll);
drawLayers(canvas);
if (mNativeClass == 0) return;
- if (mShiftIsPressed && !animateZoom) {
+ if (mShiftIsPressed && !(animateZoom || mPreviewZoomOnly)) {
if (mTouchSelection || mExtendSelection) {
nativeDrawSelectionRegion(canvas);
}
@@ -3206,6 +3240,13 @@
mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
}
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ InputConnection connection = super.onCreateInputConnection(outAttrs);
+ outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN;
+ return connection;
+ }
+
/**
* Called in response to a message from webkit telling us that the soft
* keyboard should be launched.
@@ -3218,10 +3259,14 @@
rebuildWebTextView();
if (!inEditingMode()) return;
imm.showSoftInput(mWebTextView, 0);
- if (mInZoomOverview) {
- // if in zoom overview mode, call doDoubleTap() to bring it back
- // to normal mode so that user can enter text.
- doDoubleTap();
+ // bring it back to the default scale so that user can enter text
+ if (mActualScale < mDefaultScale) {
+ mInZoomOverview = false;
+ mZoomCenterX = mLastTouchX;
+ mZoomCenterY = mLastTouchY;
+ // do not change text wrap scale so that there is no reflow
+ setNewZoomScale(mDefaultScale, false, false);
+ didUpdateTextViewBounds(true);
}
}
else { // used by plugins
@@ -3863,6 +3908,25 @@
return changed;
}
+ private static class PostScale implements Runnable {
+ final WebView mWebView;
+ final boolean mUpdateTextWrap;
+
+ public PostScale(WebView webView, boolean updateTextWrap) {
+ mWebView = webView;
+ mUpdateTextWrap = updateTextWrap;
+ }
+
+ public void run() {
+ if (mWebView.mWebViewCore != null) {
+ // we always force, in case our height changed, in which case we
+ // still want to send the notification over to webkit.
+ mWebView.setNewZoomScale(mWebView.mActualScale,
+ mUpdateTextWrap, true);
+ }
+ }
+ }
+
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
@@ -3870,6 +3934,8 @@
if (mZoomScale == 0) { // unless we're already zooming
mZoomCenterX = getViewWidth() * .5f;
mZoomCenterY = getViewHeight() * .5f;
+ mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+ mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
}
// adjust the max viewport width depending on the view dimensions. This
@@ -3902,15 +3968,9 @@
// requestLayout() is blocked during layout. As setNewZoomScale() will
// call its child View to reposition itself through ViewManager's
// scaleAll(), we need to post a Runnable to ensure requestLayout().
- post(new Runnable() {
- public void run() {
- // we always force, in case our height changed, in which case we
- // still want to send the notification over to webkit
- if (mWebViewCore != null) {
- setNewZoomScale(mActualScale, true);
- }
- }
- });
+ // <b/>
+ // only update the text wrap scale if width changed.
+ post(new PostScale(this, w != ow));
}
@Override
@@ -4118,6 +4178,76 @@
private DragTracker mDragTracker;
private DragTrackerHandler mDragTrackerHandler;
+ private class ScaleDetectorListener implements
+ ScaleGestureDetector.OnScaleGestureListener {
+
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ // cancel the single touch handling
+ cancelTouch();
+ if (mZoomButtonsController.isVisible()) {
+ mZoomButtonsController.setVisible(false);
+ }
+ // reset the zoom overview mode so that the page won't auto grow
+ mInZoomOverview = false;
+ // If it is in password mode, turn it off so it does not draw
+ // misplaced.
+ if (inEditingMode() && nativeFocusCandidateIsPassword()) {
+ mWebTextView.setInPassword(false);
+ }
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ if (mPreviewZoomOnly) {
+ mPreviewZoomOnly = false;
+ mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+ mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+ // don't reflow when zoom in; when zoom out, do reflow if the
+ // new scale is almost minimum scale;
+ boolean reflowNow = (mActualScale - mMinZoomScale <= 0.01f)
+ || ((mActualScale <= 0.8 * mTextWrapScale));
+ // force zoom after mPreviewZoomOnly is set to false so that the
+ // new view size will be passed to the WebKit
+ setNewZoomScale(mActualScale, reflowNow, true);
+ // call invalidate() to draw without zoom filter
+ invalidate();
+ }
+ // adjust the edit text view if needed
+ if (inEditingMode() && didUpdateTextViewBounds(false)
+ && nativeFocusCandidateIsPassword()) {
+ // If it is a password field, start drawing the
+ // WebTextView once again.
+ mWebTextView.setInPassword(true);
+ }
+ // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as it
+ // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it
+ // may trigger the unwanted fling.
+ mTouchMode = TOUCH_PINCH_DRAG;
+ startTouch(detector.getFocusX(), detector.getFocusY(),
+ mLastTouchTime);
+ }
+
+ public boolean onScale(ScaleGestureDetector detector) {
+ float scale = (float) (Math.round(detector.getScaleFactor()
+ * mActualScale * 100) / 100.0);
+ if (Math.abs(scale - mActualScale) >= PREVIEW_SCALE_INCREMENT) {
+ mPreviewZoomOnly = true;
+ // limit the scale change per step
+ if (scale > mActualScale) {
+ scale = Math.min(scale, mActualScale * 1.25f);
+ } else {
+ scale = Math.max(scale, mActualScale * 0.8f);
+ }
+ mZoomCenterX = detector.getFocusX();
+ mZoomCenterY = detector.getFocusY();
+ setNewZoomScale(scale, false, false);
+ invalidate();
+ return true;
+ }
+ return false;
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
@@ -4129,11 +4259,45 @@
+ mTouchMode);
}
- int action = ev.getAction();
- float x = ev.getX();
- float y = ev.getY();
+ int action;
+ float x, y;
long eventTime = ev.getEventTime();
+ // FIXME: we may consider to give WebKit an option to handle multi-touch
+ // events later.
+ if (mSupportMultiTouch && ev.getPointerCount() > 1) {
+ if (mMinZoomScale < mMaxZoomScale) {
+ mScaleDetector.onTouchEvent(ev);
+ if (mScaleDetector.isInProgress()) {
+ mLastTouchTime = eventTime;
+ return true;
+ }
+ x = mScaleDetector.getFocusX();
+ y = mScaleDetector.getFocusY();
+ action = ev.getAction() & MotionEvent.ACTION_MASK;
+ if (action == MotionEvent.ACTION_POINTER_DOWN) {
+ cancelTouch();
+ action = MotionEvent.ACTION_DOWN;
+ } else if (action == MotionEvent.ACTION_POINTER_UP) {
+ // set mLastTouchX/Y to the remaining point
+ mLastTouchX = x;
+ mLastTouchY = y;
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ // negative x or y indicate it is on the edge, skip it.
+ if (x < 0 || y < 0) {
+ return true;
+ }
+ }
+ } else {
+ // if the page disallow zoom, skip multi-pointer action
+ return true;
+ }
+ } else {
+ action = ev.getAction();
+ x = ev.getX();
+ y = ev.getY();
+ }
+
// Due to the touch screen edge effect, a touch closer to the edge
// always snapped to the edge. As getViewWidth() can be different from
// getWidth() due to the scrollbar, adjusting the point to match
@@ -4170,7 +4334,7 @@
// fling's velocity
mScroller.abortAnimation();
mTouchMode = TOUCH_DRAG_START_MODE;
- mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
+ mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
} else if (mShiftIsPressed) {
mSelectX = mScrollX + (int) x;
mSelectY = mScrollY + (int) y;
@@ -4192,6 +4356,7 @@
// continue, mTouchMode should be still TOUCH_INIT_MODE
}
} else {
+ mPreviewZoomOnly = false;
mTouchMode = TOUCH_INIT_MODE;
mPreventDrag = mForwardTouchEvents ? PREVENT_DRAG_MAYBE_YES
: PREVENT_DRAG_NO;
@@ -4210,16 +4375,7 @@
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT);
}
- // Remember where the motion event started
- mLastTouchX = x;
- mLastTouchY = y;
- mLastTouchTime = eventTime;
- mVelocityTracker = VelocityTracker.obtain();
- mSnapScrollMode = SNAP_NONE;
- if (mDragTracker != null) {
- mDragTrackerHandler = new DragTrackerHandler(x, y,
- mDragTracker);
- }
+ startTouch(x, y, eventTime);
break;
}
case MotionEvent.ACTION_MOVE: {
@@ -4282,7 +4438,7 @@
deltaX = 0;
deltaY = 0;
- WebViewCore.pauseUpdate(mWebViewCore);
+ WebViewCore.reducePriority(mWebViewCore);
if (!mDragFromTextInput) {
nativeHideCursor();
}
@@ -4445,7 +4601,7 @@
|| computeVerticalScrollExtent() < computeVerticalScrollRange())) {
// we will not rewrite drag code here, but we
// will try fling if it applies.
- WebViewCore.pauseUpdate(mWebViewCore);
+ WebViewCore.reducePriority(mWebViewCore);
// fall through to TOUCH_DRAG_MODE
} else {
break;
@@ -4485,7 +4641,7 @@
break;
}
mLastVelocity = 0;
- WebViewCore.resumeUpdate(mWebViewCore);
+ WebViewCore.resumePriority(mWebViewCore);
break;
case TOUCH_DRAG_START_MODE:
case TOUCH_DONE_MODE:
@@ -4502,33 +4658,49 @@
break;
}
case MotionEvent.ACTION_CANCEL: {
- if (mDragTrackerHandler != null) {
- mDragTrackerHandler.stopDrag();
- mDragTrackerHandler = null;
- }
- // we also use mVelocityTracker == null to tell us that we are
- // not "moving around", so we can take the slower/prettier
- // mode in the drawing code
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- if (mTouchMode == TOUCH_DRAG_MODE) {
- WebViewCore.resumeUpdate(mWebViewCore);
- }
- mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
- mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
- mHeldMotionless = MOTIONLESS_TRUE;
- mTouchMode = TOUCH_DONE_MODE;
- nativeHideCursor();
+ cancelTouch();
break;
}
}
return true;
}
+ private void startTouch(float x, float y, long eventTime) {
+ // Remember where the motion event started
+ mLastTouchX = x;
+ mLastTouchY = y;
+ mLastTouchTime = eventTime;
+ mVelocityTracker = VelocityTracker.obtain();
+ mSnapScrollMode = SNAP_NONE;
+ if (mDragTracker != null) {
+ mDragTrackerHandler = new DragTrackerHandler(x, y, mDragTracker);
+ }
+ }
+
+ private void cancelTouch() {
+ if (mDragTrackerHandler != null) {
+ mDragTrackerHandler.stopDrag();
+ mDragTrackerHandler = null;
+ }
+ // we also use mVelocityTracker == null to tell us that we are
+ // not "moving around", so we can take the slower/prettier
+ // mode in the drawing code
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ if (mTouchMode == TOUCH_DRAG_MODE) {
+ WebViewCore.resumePriority(mWebViewCore);
+ }
+ mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
+ mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_TRUE;
+ mTouchMode = TOUCH_DONE_MODE;
+ nativeHideCursor();
+ }
+
private long mTrackballFirstTime = 0;
private long mTrackballLastTime = 0;
private float mTrackballRemainsX = 0.0f;
@@ -4844,7 +5016,7 @@
vy = vy * 3 / 4;
}
if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
- WebViewCore.resumeUpdate(mWebViewCore);
+ WebViewCore.resumePriority(mWebViewCore);
return;
}
float currentVelocity = mScroller.getCurrVelocity();
@@ -4878,7 +5050,7 @@
// want to calculate how long the animation is going to run to precisely
// resume the webcore update.
final int time = mScroller.getDuration();
- mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time);
+ mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_PRIORITY, time);
awakenScrollBars(time);
invalidate();
}
@@ -4893,7 +5065,7 @@
scale = mDefaultScale;
}
- setNewZoomScale(scale, false);
+ setNewZoomScale(scale, true, false);
if (oldScale != mActualScale) {
// use mZoomPickerScale to see zoom preview first
@@ -4901,9 +5073,6 @@
mInvInitialZoomScale = 1.0f / oldScale;
mInvFinalZoomScale = 1.0f / mActualScale;
mZoomScale = mActualScale;
- if (!mInZoomOverview) {
- mLastScale = scale;
- }
invalidate();
return true;
} else {
@@ -5001,18 +5170,13 @@
public boolean zoomIn() {
// TODO: alternatively we can disallow this during draw history mode
switchOutDrawHistory();
+ mInZoomOverview = false;
// Center zooming to the center of the screen.
- if (mInZoomOverview) {
- // if in overview mode, bring it back to normal mode
- mLastTouchX = getViewWidth() * .5f;
- mLastTouchY = getViewHeight() * .5f;
- doDoubleTap();
- return true;
- } else {
- mZoomCenterX = getViewWidth() * .5f;
- mZoomCenterY = getViewHeight() * .5f;
- return zoomWithPreview(mActualScale * 1.25f);
- }
+ mZoomCenterX = getViewWidth() * .5f;
+ mZoomCenterY = getViewHeight() * .5f;
+ mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+ mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+ return zoomWithPreview(mActualScale * 1.25f);
}
/**
@@ -5022,20 +5186,12 @@
public boolean zoomOut() {
// TODO: alternatively we can disallow this during draw history mode
switchOutDrawHistory();
- float scale = mActualScale * 0.8f;
- if (scale < (mMinZoomScale + 0.1f)
- && mWebViewCore.getSettings().getUseWideViewPort()
- && mZoomOverviewWidth > Math.ceil(getViewWidth()
- * mInvActualScale)) {
- // when zoom out to min scale, switch to overview mode
- doDoubleTap();
- return true;
- } else {
- // Center zooming to the center of the screen.
- mZoomCenterX = getViewWidth() * .5f;
- mZoomCenterY = getViewHeight() * .5f;
- return zoomWithPreview(scale);
- }
+ // Center zooming to the center of the screen.
+ mZoomCenterX = getViewWidth() * .5f;
+ mZoomCenterY = getViewHeight() * .5f;
+ mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+ mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+ return zoomWithPreview(mActualScale * 0.8f);
}
private void updateSelection() {
@@ -5079,7 +5235,7 @@
mLastTouchTime = eventTime;
if (!mScroller.isFinished()) {
abortAnimation();
- mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
+ mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
}
mSnapScrollMode = SNAP_NONE;
mVelocityTracker = VelocityTracker.obtain();
@@ -5159,15 +5315,21 @@
}
}
+ // Rule for double tap:
+ // 1. if the current scale is not same as the text wrap scale and layout
+ // algorithm is NARROW_COLUMNS, fit to column;
+ // 2. if the current state is not overview mode, change to overview mode;
+ // 3. if the current state is overview mode, change to default scale.
private void doDoubleTap() {
if (mWebViewCore.getSettings().getUseWideViewPort() == false) {
return;
}
mZoomCenterX = mLastTouchX;
mZoomCenterY = mLastTouchY;
- mInZoomOverview = !mInZoomOverview;
- // remove the zoom control after double tap
+ mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+ mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
WebSettings settings = getSettings();
+ // remove the zoom control after double tap
if (settings.getBuiltInZoomControls()) {
if (mZoomButtonsController.isVisible()) {
mZoomButtonsController.setVisible(false);
@@ -5181,28 +5343,45 @@
}
}
settings.setDoubleTapToastCount(0);
- if (mInZoomOverview) {
+ boolean zoomToDefault = false;
+ if ((settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS)
+ && (Math.abs(mActualScale - mTextWrapScale) >= 0.01f)) {
+ setNewZoomScale(mActualScale, true, true);
+ float overviewScale = (float) getViewWidth() / mZoomOverviewWidth;
+ if (Math.abs(mActualScale - overviewScale) < 0.01f) {
+ mInZoomOverview = true;
+ }
+ } else if (!mInZoomOverview) {
float newScale = (float) getViewWidth() / mZoomOverviewWidth;
- if (Math.abs(mActualScale - newScale) < 0.01f) {
- // reset mInZoomOverview to false if scale doesn't change
- mInZoomOverview = false;
- } else {
+ if (Math.abs(mActualScale - newScale) >= 0.01f) {
+ mInZoomOverview = true;
// Force the titlebar fully reveal in overview mode
if (mScrollY < getTitleHeight()) mScrollY = 0;
zoomWithPreview(newScale);
+ } else if (Math.abs(mActualScale - mDefaultScale) >= 0.01f) {
+ zoomToDefault = true;
}
} else {
- // mLastTouchX and mLastTouchY are the point in the current viewport
- int contentX = viewToContentX((int) mLastTouchX + mScrollX);
- int contentY = viewToContentY((int) mLastTouchY + mScrollY);
- int left = nativeGetBlockLeftEdge(contentX, contentY, mActualScale);
+ zoomToDefault = true;
+ }
+ if (zoomToDefault) {
+ mInZoomOverview = false;
+ int left = nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale);
if (left != NO_LEFTEDGE) {
- // add a 5pt padding to the left edge. Re-calculate the zoom
- // center so that the new scroll x will be on the left edge.
- mZoomCenterX = left < 5 ? 0 : (left - 5) * mLastScale
- * mActualScale / (mLastScale - mActualScale);
+ // add a 5pt padding to the left edge.
+ int viewLeft = contentToViewX(left < 5 ? 0 : (left - 5))
+ - mScrollX;
+ // Re-calculate the zoom center so that the new scroll x will be
+ // on the left edge.
+ if (viewLeft > 0) {
+ mZoomCenterX = viewLeft * mDefaultScale
+ / (mDefaultScale - mActualScale);
+ } else {
+ scrollBy(viewLeft, 0);
+ mZoomCenterX = 0;
+ }
}
- zoomWithPreview(mLastScale);
+ zoomWithPreview(mDefaultScale);
}
}
@@ -5494,9 +5673,6 @@
boolean hasRestoreState = restoreState != null;
if (hasRestoreState) {
mInZoomOverview = false;
- mLastScale = mInitialScaleInPercent > 0
- ? mInitialScaleInPercent / 100.0f
- : restoreState.mTextWrapScale;
if (restoreState.mMinScale == 0) {
if (restoreState.mMobileSite) {
if (draw.mMinPrefWidth >
@@ -5504,6 +5680,8 @@
mMinZoomScale = (float) viewWidth
/ draw.mMinPrefWidth;
mMinZoomScaleFixed = false;
+ mInZoomOverview = useWideViewport &&
+ settings.getLoadWithOverviewMode();
} else {
mMinZoomScale = restoreState.mDefaultScale;
mMinZoomScaleFixed = true;
@@ -5521,17 +5699,29 @@
} else {
mMaxZoomScale = restoreState.mMaxScale;
}
- setNewZoomScale(mLastScale, false);
+ if (mInitialScaleInPercent > 0) {
+ setNewZoomScale(mInitialScaleInPercent / 100.0f,
+ mInitialScaleInPercent != mTextWrapScale * 100,
+ false);
+ } else if (restoreState.mViewScale > 0) {
+ mTextWrapScale = restoreState.mTextWrapScale;
+ setNewZoomScale(restoreState.mViewScale, false,
+ false);
+ } else {
+ mInZoomOverview = useWideViewport
+ && settings.getLoadWithOverviewMode();
+ float scale;
+ if (mInZoomOverview) {
+ scale = (float) viewWidth
+ / DEFAULT_VIEWPORT_WIDTH;
+ } else {
+ scale = restoreState.mTextWrapScale;
+ }
+ setNewZoomScale(scale, Math.abs(scale
+ - mTextWrapScale) >= 0.01f, false);
+ }
setContentScrollTo(restoreState.mScrollX,
restoreState.mScrollY);
- if (useWideViewport
- && settings.getLoadWithOverviewMode()) {
- if (restoreState.mViewScale == 0
- || (restoreState.mMobileSite
- && mMinZoomScale < restoreState.mDefaultScale)) {
- mInZoomOverview = true;
- }
- }
// As we are on a new page, remove the WebTextView. This
// is necessary for page loads driven by webkit, and in
// particular when the user was on a password field, so
@@ -5561,11 +5751,14 @@
mPictureListener.onNewPicture(WebView.this, capturePicture());
}
if (useWideViewport) {
- // limit mZoomOverviewWidth to sMaxViewportWidth so that
- // if the page doesn't behave well, the WebView won't go
- // insane.
+ // limit mZoomOverviewWidth upper bound to
+ // sMaxViewportWidth so that if the page doesn't behave
+ // well, the WebView won't go insane. limit the lower
+ // bound to match the default scale for mobile sites.
mZoomOverviewWidth = Math.min(sMaxViewportWidth, Math
- .max(draw.mMinPrefWidth, draw.mViewPoint.x));
+ .max((int) (viewWidth / mDefaultScale), Math
+ .max(draw.mMinPrefWidth,
+ draw.mViewPoint.x)));
}
if (!mMinZoomScaleFixed) {
mMinZoomScale = (float) viewWidth / mZoomOverviewWidth;
@@ -5576,7 +5769,8 @@
if (Math.abs((viewWidth * mInvActualScale)
- mZoomOverviewWidth) > 1) {
setNewZoomScale((float) viewWidth
- / mZoomOverviewWidth, false);
+ / mZoomOverviewWidth, Math.abs(mActualScale
+ - mTextWrapScale) < 0.01f, false);
}
}
if (draw.mFocusSizeChanged && inEditingMode()) {
@@ -5701,8 +5895,8 @@
mWebTextView.setAdapterCustom(adapter);
}
break;
- case RESUME_WEBCORE_UPDATE:
- WebViewCore.resumeUpdate(mWebViewCore);
+ case RESUME_WEBCORE_PRIORITY:
+ WebViewCore.resumePriority(mWebViewCore);
break;
case LONG_PRESS_CENTER:
@@ -5780,7 +5974,7 @@
doMotionUp(msg.arg1, msg.arg2);
break;
- case SHOW_FULLSCREEN:
+ case SHOW_FULLSCREEN: {
WebViewCore.PluginFullScreenData data
= (WebViewCore.PluginFullScreenData) msg.obj;
if (data.mNpp != 0 && data.mView != null) {
@@ -5828,15 +6022,19 @@
if (width > viewWidth || height > viewHeight) {
mZoomCenterX = viewWidth * .5f;
mZoomCenterY = viewHeight * .5f;
+ // do not change text wrap scale so that there is no
+ // reflow
setNewZoomScale(mActualScale
/ Math.max((float) width / viewWidth,
- (float) height / viewHeight), false);
+ (float) height / viewHeight), false,
+ false);
}
// Now update the bound
mFullScreenHolder.updateBound(contentToViewX(data.mDocX)
- mScrollX, contentToViewY(data.mDocY) - mScrollY,
contentToViewDimension(data.mDocWidth),
contentToViewDimension(data.mDocHeight));
+ }
break;
case HIDE_FULLSCREEN:
@@ -5853,6 +6051,44 @@
}
break;
+ case SHOW_RECT_MSG_ID: {
+ WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj;
+ int x = mScrollX;
+ int left = contentToViewDimension(data.mLeft);
+ int width = contentToViewDimension(data.mWidth);
+ int maxWidth = contentToViewDimension(data.mContentWidth);
+ int viewWidth = getViewWidth();
+ if (width < viewWidth) {
+ // center align
+ x += left + width / 2 - mScrollX - viewWidth / 2;
+ } else {
+ x += (int) (left + data.mXPercentInDoc * width
+ - mScrollX - data.mXPercentInView * viewWidth);
+ }
+ // use the passing content width to cap x as the current
+ // mContentWidth may not be updated yet
+ x = Math.max(0,
+ (Math.min(maxWidth, x + viewWidth)) - viewWidth);
+ int y = mScrollY;
+ int top = contentToViewDimension(data.mTop);
+ int height = contentToViewDimension(data.mHeight);
+ int maxHeight = contentToViewDimension(data.mContentHeight);
+ int viewHeight = getViewHeight();
+ if (height < viewHeight) {
+ // middle align
+ y += top + height / 2 - mScrollY - viewHeight / 2;
+ } else {
+ y += (int) (top + data.mYPercentInDoc * height
+ - mScrollY - data.mYPercentInView * viewHeight);
+ }
+ // use the passing content height to cap y as the current
+ // mContentHeight may not be updated yet
+ y = Math.max(0,
+ (Math.min(maxHeight, y + viewHeight) - viewHeight));
+ scrollTo(x, y);
+ }
+ break;
+
default:
super.handleMessage(msg);
break;
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index d509bb4..6700d71 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -482,8 +482,8 @@
should this be called nativeSetViewPortSize?
*/
private native void nativeSetSize(int width, int height, int screenWidth,
- float scale, int realScreenWidth, int screenHeight,
- boolean ignoreHeight);
+ float scale, int realScreenWidth, int screenHeight, int anchorX,
+ int anchorY, boolean ignoreHeight);
private native int nativeGetContentMinPrefWidth();
@@ -1033,6 +1033,7 @@
(WebView.ViewSizeData) msg.obj;
viewSizeChanged(data.mWidth, data.mHeight,
data.mTextWrapWidth, data.mScale,
+ data.mAnchorX, data.mAnchorY,
data.mIgnoreHeight);
break;
}
@@ -1584,7 +1585,7 @@
// notify webkit that our virtual view size changed size (after inv-zoom)
private void viewSizeChanged(int w, int h, int textwrapWidth, float scale,
- boolean ignoreHeight) {
+ int anchorX, int anchorY, boolean ignoreHeight) {
if (DebugFlags.WEB_VIEW_CORE) {
Log.v(LOGTAG, "viewSizeChanged w=" + w + "; h=" + h
+ "; textwrapWidth=" + textwrapWidth + "; scale=" + scale);
@@ -1616,12 +1617,14 @@
Math.max(WebView.DEFAULT_VIEWPORT_WIDTH,
nativeGetContentMinPrefWidth())));
}
- } else {
+ } else if (mViewportWidth > 0) {
width = Math.max(w, mViewportWidth);
+ } else {
+ width = textwrapWidth;
}
}
nativeSetSize(width, width == w ? h : Math.round((float) width * h / w),
- textwrapWidth, scale, w, h, ignoreHeight);
+ textwrapWidth, scale, w, h, anchorX, anchorY, ignoreHeight);
// Remember the current width and height
boolean needInvalidate = (mCurrentViewWidth == 0);
mCurrentViewWidth = w;
@@ -1778,7 +1781,7 @@
return result;
}
- static void pauseUpdate(WebViewCore core) {
+ static void reducePriority(WebViewCore core) {
// remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
@@ -1786,7 +1789,7 @@
.obtainMessage(WebCoreThread.REDUCE_PRIORITY));
}
- static void resumeUpdate(WebViewCore core) {
+ static void resumePriority(WebViewCore core) {
// remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
@@ -2070,14 +2073,12 @@
mRestoreState.mScrollY = mRestoredY;
mRestoreState.mMobileSite = (0 == mViewportWidth);
if (mRestoredScale > 0) {
+ mRestoreState.mViewScale = mRestoredScale / 100.0f;
if (mRestoredScreenWidthScale > 0) {
mRestoreState.mTextWrapScale =
mRestoredScreenWidthScale / 100.0f;
- // 0 will trigger WebView to turn on zoom overview mode
- mRestoreState.mViewScale = 0;
} else {
- mRestoreState.mViewScale = mRestoreState.mTextWrapScale =
- mRestoredScale / 100.0f;
+ mRestoreState.mTextWrapScale = mRestoreState.mViewScale;
}
} else {
if (mViewportInitialScale > 0) {
@@ -2110,6 +2111,7 @@
data.mTextWrapWidth = data.mWidth;
data.mScale = -1.0f;
data.mIgnoreHeight = false;
+ data.mAnchorX = data.mAnchorY = 0;
// send VIEW_SIZE_CHANGED to the front of the queue so that we can
// avoid pushing the wrong picture to the WebView side. If there is
// a VIEW_SIZE_CHANGED in the queue, probably from WebView side,
@@ -2145,6 +2147,7 @@
data.mTextWrapWidth = Math.round(webViewWidth
/ mRestoreState.mTextWrapScale);
data.mIgnoreHeight = false;
+ data.mAnchorX = data.mAnchorY = 0;
// send VIEW_SIZE_CHANGED to the front of the queue so that we
// can avoid pushing the wrong picture to the WebView side.
mEventHub.removeMessages(EventHub.VIEW_SIZE_CHANGED);
@@ -2364,6 +2367,40 @@
childView.removeView();
}
+ // called by JNI
+ static class ShowRectData {
+ int mLeft;
+ int mTop;
+ int mWidth;
+ int mHeight;
+ int mContentWidth;
+ int mContentHeight;
+ float mXPercentInDoc;
+ float mXPercentInView;
+ float mYPercentInDoc;
+ float mYPercentInView;
+ }
+
+ private void showRect(int left, int top, int width, int height,
+ int contentWidth, int contentHeight, float xPercentInDoc,
+ float xPercentInView, float yPercentInDoc, float yPercentInView) {
+ if (mWebView != null) {
+ ShowRectData data = new ShowRectData();
+ data.mLeft = left;
+ data.mTop = top;
+ data.mWidth = width;
+ data.mHeight = height;
+ data.mContentWidth = contentWidth;
+ data.mContentHeight = contentHeight;
+ data.mXPercentInDoc = xPercentInDoc;
+ data.mXPercentInView = xPercentInView;
+ data.mYPercentInDoc = yPercentInDoc;
+ data.mYPercentInView = yPercentInView;
+ Message.obtain(mWebView.mPrivateHandler, WebView.SHOW_RECT_MSG_ID,
+ data).sendToTarget();
+ }
+ }
+
private native void nativePause();
private native void nativeResume();
private native void nativeFreeMemory();
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index bb9a672..d53a442 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -1331,20 +1331,26 @@
final int maxHeight = mPopup.getMaxAvailableHeight(
getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
+ // getMaxAvailableHeight() subtracts the padding, so we put it back,
+ // to get the available height for the whole window
+ int padding = 0;
+ Drawable background = mPopup.getBackground();
+ if (background != null) {
+ background.getPadding(mTempRect);
+ padding = mTempRect.top + mTempRect.bottom;
+ }
+
if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
- // getMaxAvailableHeight() subtracts the padding, so we put it back,
- // to get the available height for the whole window
- int padding = 0;
- Drawable background = mPopup.getBackground();
- if (background != null) {
- background.getPadding(mTempRect);
- padding = mTempRect.top + mTempRect.bottom;
- }
return maxHeight + padding;
}
- return mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
- 0, ListView.NO_POSITION, maxHeight - otherHeights, 2) + otherHeights;
+ final int listContent = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
+ 0, ListView.NO_POSITION, maxHeight - otherHeights, 2);
+ // add padding only if the list has items in it, that way we don't show
+ // the popup if it is not needed
+ if (listContent > 0) otherHeights += padding;
+
+ return listContent + otherHeights;
}
private View getHintView(Context context) {
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index b657e8e..299ed8a 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -25,9 +25,9 @@
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.LayoutInflater;
+import android.widget.NumberPicker;
+import android.widget.NumberPicker.OnChangedListener;
-import com.android.common.widget.NumberPicker;
-import com.android.common.widget.NumberPicker.OnChangedListener;
import com.android.internal.R;
import java.text.DateFormatSymbols;
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
new file mode 100644
index 0000000..9067e26
--- /dev/null
+++ b/core/java/android/widget/DateTimeView.java
@@ -0,0 +1,258 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.text.format.Time;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.widget.TextView;
+import android.widget.RemoteViews.RemoteView;
+
+import com.android.internal.R;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+//
+// TODO
+// - listen for the next threshold time to update the view.
+// - listen for date format pref changed
+// - put the AM/PM in a smaller font
+//
+
+/**
+ * Displays a given time in a convenient human-readable foramt.
+ *
+ * @hide
+ */
+@RemoteView
+public class DateTimeView extends TextView {
+ private static final String TAG = "DateTimeView";
+
+ private static final long TWELVE_HOURS_IN_MINUTES = 12 * 60;
+ private static final long TWENTY_FOUR_HOURS_IN_MILLIS = 24 * 60 * 60 * 1000;
+
+ private static final int SHOW_TIME = 0;
+ private static final int SHOW_MONTH_DAY_YEAR = 1;
+
+ Date mTime;
+ long mTimeMillis;
+
+ int mLastDisplay = -1;
+ DateFormat mLastFormat;
+
+ private boolean mAttachedToWindow;
+ private long mUpdateTimeMillis;
+
+ public DateTimeView(Context context) {
+ super(context);
+ }
+
+ public DateTimeView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onDetachedFromWindow();
+ registerReceivers();
+ mAttachedToWindow = true;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ unregisterReceivers();
+ mAttachedToWindow = false;
+ }
+
+ @android.view.RemotableViewMethod
+ public void setTime(long time) {
+ Time t = new Time();
+ t.set(time);
+ t.second = 0;
+ mTimeMillis = t.toMillis(false);
+ mTime = new Date(t.year-1900, t.month, t.monthDay, t.hour, t.minute, 0);
+ update();
+ }
+
+ void update() {
+ if (mTime == null) {
+ return;
+ }
+
+ long start = System.nanoTime();
+
+ int display;
+ Date time = mTime;
+
+ Time t = new Time();
+ t.set(mTimeMillis);
+ t.second = 0;
+
+ t.hour -= 12;
+ long twelveHoursBefore = t.toMillis(false);
+ t.hour += 12;
+ long twelveHoursAfter = t.toMillis(false);
+ t.hour = 0;
+ t.minute = 0;
+ long midnightBefore = t.toMillis(false);
+ t.monthDay++;
+ long midnightAfter = t.toMillis(false);
+
+ long nowMillis = System.currentTimeMillis();
+ t.set(nowMillis);
+ t.second = 0;
+ nowMillis = t.normalize(false);
+
+ // Choose the display mode
+ choose_display: {
+ if ((nowMillis >= midnightBefore && nowMillis < midnightAfter)
+ || (nowMillis >= twelveHoursBefore && nowMillis < twelveHoursAfter)) {
+ display = SHOW_TIME;
+ break choose_display;
+ }
+ // Else, show month day and year.
+ display = SHOW_MONTH_DAY_YEAR;
+ break choose_display;
+ }
+
+ // Choose the format
+ DateFormat format;
+ if (display == mLastDisplay && mLastFormat != null) {
+ // use cached format
+ format = mLastFormat;
+ } else {
+ switch (display) {
+ case SHOW_TIME:
+ format = getTimeFormat();
+ break;
+ case SHOW_MONTH_DAY_YEAR:
+ format = getDateFormat();
+ break;
+ default:
+ throw new RuntimeException("unknown display value: " + display);
+ }
+ mLastFormat = format;
+ }
+
+ // Set the text
+ String text = format.format(mTime);
+ setText(text);
+
+ // Schedule the next update
+ if (display == SHOW_TIME) {
+ // Currently showing the time, update at the later of twelve hours after or midnight.
+ mUpdateTimeMillis = twelveHoursAfter > midnightAfter ? twelveHoursAfter : midnightAfter;
+ } else {
+ // Currently showing the date
+ if (mTimeMillis < nowMillis) {
+ // If the time is in the past, don't schedule an update
+ mUpdateTimeMillis = 0;
+ } else {
+ // If hte time is in the future, schedule one at the earlier of twelve hours
+ // before or midnight before.
+ mUpdateTimeMillis = twelveHoursBefore < midnightBefore
+ ? twelveHoursBefore : midnightBefore;
+ }
+ }
+ if (false) {
+ Log.d(TAG, "update needed for '" + time + "' at '" + new Date(mUpdateTimeMillis)
+ + "' - text=" + text);
+ }
+
+ long finish = System.nanoTime();
+ }
+
+ private DateFormat getTimeFormat() {
+ int res;
+ Context context = getContext();
+ if (android.text.format.DateFormat.is24HourFormat(context)) {
+ res = R.string.twenty_four_hour_time_format;
+ } else {
+ res = R.string.twelve_hour_time_format;
+ }
+ String format = context.getString(res);
+ return new SimpleDateFormat(format);
+ }
+
+ private DateFormat getDateFormat() {
+ String format = Settings.System.getString(getContext().getContentResolver(),
+ Settings.System.DATE_FORMAT);
+ if ("".equals(format)) {
+ return DateFormat.getDateInstance(DateFormat.SHORT);
+ } else {
+ return new SimpleDateFormat(format);
+ }
+ }
+
+ private void registerReceivers() {
+ Context context = getContext();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_TICK);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ context.registerReceiver(mBroadcastReceiver, filter);
+
+ Uri uri = Settings.System.getUriFor(Settings.System.DATE_FORMAT);
+ context.getContentResolver().registerContentObserver(uri, true, mContentObserver);
+ }
+
+ private void unregisterReceivers() {
+ Context context = getContext();
+ context.unregisterReceiver(mBroadcastReceiver);
+ context.getContentResolver().unregisterContentObserver(mContentObserver);
+ }
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_TIME_TICK.equals(action)) {
+ if (System.currentTimeMillis() < mUpdateTimeMillis) {
+ // The update() function takes a few milliseconds to run because of
+ // all of the time conversions it needs to do, so we can't do that
+ // every minute.
+ return;
+ }
+ }
+ // ACTION_TIME_CHANGED can also signal a change of 12/24 hr. format.
+ mLastFormat = null;
+ update();
+ }
+ };
+
+ private ContentObserver mContentObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mLastFormat = null;
+ update();
+ }
+ };
+}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 37372c5..b4e2790 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -50,6 +50,7 @@
* Whether the children of this layout are baseline aligned. Only applicable
* if {@link #mOrientation} is horizontal.
*/
+ @ViewDebug.ExportedProperty
private boolean mBaselineAligned = true;
/**
@@ -59,6 +60,7 @@
* Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned
* with whether the children of this layout are baseline aligned.
*/
+ @ViewDebug.ExportedProperty
private int mBaselineAlignedChildIndex = -1;
/**
@@ -66,14 +68,35 @@
* We'll calculate the baseline of this layout as we measure vertically; for
* horizontal linear layouts, the offset of 0 is appropriate.
*/
+ @ViewDebug.ExportedProperty
private int mBaselineChildTop = 0;
+ @ViewDebug.ExportedProperty
private int mOrientation;
+ @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.IntToString(from = -1, to = "NONE"),
+ @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
+ @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
+ @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
+ @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
+ @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
+ @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
+ @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
+ @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
+ @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"),
+ @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"),
+ @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
+ })
private int mGravity = Gravity.LEFT | Gravity.TOP;
+ @ViewDebug.ExportedProperty
private int mTotalLength;
+ @ViewDebug.ExportedProperty
private float mWeightSum;
+ @ViewDebug.ExportedProperty
+ private boolean mUseLargestChild;
+
private int[] mMaxAscent;
private int[] mMaxDescent;
@@ -82,7 +105,7 @@
private static final int INDEX_CENTER_VERTICAL = 0;
private static final int INDEX_TOP = 1;
private static final int INDEX_BOTTOM = 2;
- private static final int INDEX_FILL = 3;
+ private static final int INDEX_FILL = 3;
public LinearLayout(Context context) {
super(context);
@@ -114,6 +137,9 @@
mBaselineAlignedChildIndex =
a.getInt(com.android.internal.R.styleable.LinearLayout_baselineAlignedChildIndex, -1);
+ // TODO: Better name, add Java APIs, make it public
+ mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_useLargestChild, false);
+
a.recycle();
}
@@ -308,6 +334,9 @@
boolean matchWidth = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
+ final boolean useLargestChild = mUseLargestChild;
+
+ int largestChildHeight = Integer.MIN_VALUE;
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
@@ -333,30 +362,35 @@
// there is any leftover space.
mTotalLength += lp.topMargin + lp.bottomMargin;
} else {
- int oldHeight = Integer.MIN_VALUE;
+ int oldHeight = Integer.MIN_VALUE;
- if (lp.height == 0 && lp.weight > 0) {
+ if (lp.height == 0 && lp.weight > 0) {
// heightMode is either UNSPECIFIED OR AT_MOST, and this child
// wanted to stretch to fill available space. Translate that to
// WRAP_CONTENT so that it does not end up with a height of 0
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
- }
+ }
- // Determine how big this child would like to. If this or
- // previous children have given a weight, then we allow it to
- // use all available space (and we will shrink things later
- // if needed).
- measureChildBeforeLayout(
+ // Determine how big this child would like to. If this or
+ // previous children have given a weight, then we allow it to
+ // use all available space (and we will shrink things later
+ // if needed).
+ measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
- if (oldHeight != Integer.MIN_VALUE) {
+ if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
- }
+ }
- mTotalLength += child.getMeasuredHeight() + lp.topMargin +
+ final int childHeight = child.getMeasuredHeight();
+ mTotalLength += childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child);
+
+ if (useLargestChild) {
+ largestChildHeight = Math.max(childHeight, largestChildHeight);
+ }
}
/**
@@ -406,7 +440,30 @@
i += getChildrenSkipCount(child, i);
}
-
+
+ if (useLargestChild && heightMode == MeasureSpec.AT_MOST) {
+ mTotalLength = 0;
+
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null) {
+ mTotalLength += measureNullChild(i);
+ continue;
+ }
+
+ if (child.getVisibility() == GONE) {
+ i += getChildrenSkipCount(child, i);
+ continue;
+ }
+
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+ child.getLayoutParams();
+ mTotalLength += largestChildHeight + lp.topMargin+ lp.bottomMargin +
+ getNextLocationOffset(child);
+ }
+ }
+
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
@@ -567,6 +624,9 @@
maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;
final boolean baselineAligned = mBaselineAligned;
+ final boolean useLargestChild = mUseLargestChild;
+
+ int largestChildWidth = Integer.MIN_VALUE;
// See how wide everyone is. Also remember max height.
for (int i = 0; i < count; ++i) {
@@ -582,7 +642,8 @@
continue;
}
- final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+ child.getLayoutParams();
totalWeight += lp.weight;
@@ -623,9 +684,14 @@
if (oldWidth != Integer.MIN_VALUE) {
lp.width = oldWidth;
}
-
- mTotalLength += child.getMeasuredWidth() + lp.leftMargin +
- lp.rightMargin + getNextLocationOffset(child);
+
+ final int childWidth = child.getMeasuredWidth();
+ mTotalLength += childWidth + lp.leftMargin + lp.rightMargin +
+ getNextLocationOffset(child);
+
+ if (useLargestChild) {
+ largestChildWidth = Math.max(childWidth, largestChildWidth);
+ }
}
boolean matchHeightLocally = false;
@@ -688,6 +754,29 @@
maxHeight = Math.max(maxHeight, ascent + descent);
}
+ if (useLargestChild && widthMode == MeasureSpec.AT_MOST) {
+ mTotalLength = 0;
+
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null) {
+ mTotalLength += measureNullChild(i);
+ continue;
+ }
+
+ if (child.getVisibility() == GONE) {
+ i += getChildrenSkipCount(child, i);
+ continue;
+ }
+
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+ child.getLayoutParams();
+ mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
+ getNextLocationOffset(child);
+ }
+ }
+
// Add in our padding
mTotalLength += mPaddingLeft + mPaddingRight;
@@ -933,7 +1022,7 @@
final int paddingLeft = mPaddingLeft;
int childTop = mPaddingTop;
- int childLeft = paddingLeft;
+ int childLeft;
// Where right end of child should go
final int width = mRight - mLeft;
@@ -1018,7 +1107,7 @@
void layoutHorizontal() {
final int paddingTop = mPaddingTop;
- int childTop = paddingTop;
+ int childTop;
int childLeft = mPaddingLeft;
// Where bottom of child should go
@@ -1083,7 +1172,7 @@
break;
case Gravity.CENTER_VERTICAL:
- // Removed support for baselign alignment when layout_gravity or
+ // Removed support for baseline alignment when layout_gravity or
// gravity == center_vertical. See bug #1038483.
// Keep the code around if we need to re-enable this feature
// if (childBaseline != -1) {
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index c63774a..401e7ff 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -2781,10 +2781,10 @@
while (first.getBottom() < listTop) {
AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
- removeViewInLayout(first);
+ detachViewFromParent(first);
recycleBin.addScrapView(first);
} else {
- detachViewFromParent(first);
+ removeViewInLayout(first);
}
first = getChildAt(0);
mFirstPosition++;
@@ -2812,10 +2812,10 @@
while (last.getTop() > listBottom) {
AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
- removeViewInLayout(last);
+ detachViewFromParent(last);
recycleBin.addScrapView(last);
} else {
- detachViewFromParent(last);
+ removeViewInLayout(last);
}
last = getChildAt(--lastIndex);
}
diff --git a/common/java/com/android/common/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
similarity index 65%
rename from common/java/com/android/common/widget/NumberPicker.java
rename to core/java/android/widget/NumberPicker.java
index 64b436f..2d36bc8 100644
--- a/common/java/com/android/common/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.common.widget;
+package android.widget;
+import android.annotation.Widget;
import android.content.Context;
import android.os.Handler;
import android.text.InputFilter;
@@ -34,13 +35,29 @@
import com.android.internal.R;
-public class NumberPicker extends LinearLayout implements OnClickListener,
- OnFocusChangeListener, OnLongClickListener {
+/**
+ * A view for selecting a number
+ *
+ * For a dialog using this view, see {@link android.app.TimePickerDialog}.
+ */
+@Widget
+public class NumberPicker extends LinearLayout {
+ /**
+ * The callback interface used to indicate the number value has been adjusted.
+ */
public interface OnChangedListener {
+ /**
+ * @param picker The NumberPicker associated with this listener.
+ * @param oldVal The previous value.
+ * @param newVal The new value.
+ */
void onChanged(NumberPicker picker, int oldVal, int newVal);
}
+ /**
+ * Interface used to format the number into a string for presentation
+ */
public interface Formatter {
String toString(int value);
}
@@ -81,10 +98,26 @@
private final InputFilter mNumberInputFilter;
private String[] mDisplayedValues;
- protected int mStart;
- protected int mEnd;
- protected int mCurrent;
- protected int mPrevious;
+
+ /**
+ * Lower value of the range of numbers allowed for the NumberPicker
+ */
+ private int mStart;
+
+ /**
+ * Upper value of the range of numbers allowed for the NumberPicker
+ */
+ private int mEnd;
+
+ /**
+ * Current value of this NumberPicker
+ */
+ private int mCurrent;
+
+ /**
+ * Previous value of this NumberPicker.
+ */
+ private int mPrevious;
private OnChangedListener mListener;
private Formatter mFormatter;
private long mSpeed = 300;
@@ -92,35 +125,89 @@
private boolean mIncrement;
private boolean mDecrement;
+ /**
+ * Create a new number picker
+ * @param context the application environment
+ */
public NumberPicker(Context context) {
this(context, null);
}
+ /**
+ * Create a new number picker
+ * @param context the application environment
+ * @param attrs a collection of attributes
+ */
public NumberPicker(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- @SuppressWarnings({"UnusedDeclaration"})
- public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
setOrientation(VERTICAL);
LayoutInflater inflater =
(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.number_picker, this, true);
mHandler = new Handler();
+
+ OnClickListener clickListener = new OnClickListener() {
+ public void onClick(View v) {
+ validateInput(mText);
+ if (!mText.hasFocus()) mText.requestFocus();
+
+ // now perform the increment/decrement
+ if (R.id.increment == v.getId()) {
+ changeCurrent(mCurrent + 1);
+ } else if (R.id.decrement == v.getId()) {
+ changeCurrent(mCurrent - 1);
+ }
+ }
+ };
+
+ OnFocusChangeListener focusListener = new OnFocusChangeListener() {
+ public void onFocusChange(View v, boolean hasFocus) {
+
+ /* When focus is lost check that the text field
+ * has valid values.
+ */
+ if (!hasFocus) {
+ validateInput(v);
+ }
+ }
+ };
+
+ OnLongClickListener longClickListener = new OnLongClickListener() {
+ /**
+ * We start the long click here but rely on the {@link NumberPickerButton}
+ * to inform us when the long click has ended.
+ */
+ public boolean onLongClick(View v) {
+ /* The text view may still have focus so clear it's focus which will
+ * trigger the on focus changed and any typed values to be pulled.
+ */
+ mText.clearFocus();
+
+ if (R.id.increment == v.getId()) {
+ mIncrement = true;
+ mHandler.post(mRunnable);
+ } else if (R.id.decrement == v.getId()) {
+ mDecrement = true;
+ mHandler.post(mRunnable);
+ }
+ return true;
+ }
+ };
+
InputFilter inputFilter = new NumberPickerInputFilter();
mNumberInputFilter = new NumberRangeKeyListener();
mIncrementButton = (NumberPickerButton) findViewById(R.id.increment);
- mIncrementButton.setOnClickListener(this);
- mIncrementButton.setOnLongClickListener(this);
+ mIncrementButton.setOnClickListener(clickListener);
+ mIncrementButton.setOnLongClickListener(longClickListener);
mIncrementButton.setNumberPicker(this);
+
mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement);
- mDecrementButton.setOnClickListener(this);
- mDecrementButton.setOnLongClickListener(this);
+ mDecrementButton.setOnClickListener(clickListener);
+ mDecrementButton.setOnLongClickListener(longClickListener);
mDecrementButton.setNumberPicker(this);
mText = (EditText) findViewById(R.id.timepicker_input);
- mText.setOnFocusChangeListener(this);
+ mText.setOnFocusChangeListener(focusListener);
mText.setFilters(new InputFilter[] {inputFilter});
mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
@@ -129,6 +216,12 @@
}
}
+ /**
+ * Set the enabled state of this view. The interpretation of the enabled
+ * state varies by subclass.
+ *
+ * @param enabled True if this view is enabled, false otherwise.
+ */
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
@@ -137,10 +230,19 @@
mText.setEnabled(enabled);
}
+ /**
+ * Set the callback that indicates the number has been adjusted by the user.
+ * @param listener the callback, should not be null.
+ */
public void setOnChangeListener(OnChangedListener listener) {
mListener = listener;
}
+ /**
+ * Set the formatter that will be used to format the number for presentation
+ * @param formatter the formatter object. If formatter is null, String.valueOf()
+ * will be used
+ */
public void setFormatter(Formatter formatter) {
mFormatter = formatter;
}
@@ -153,10 +255,7 @@
* @param end the end of the range (inclusive)
*/
public void setRange(int start, int end) {
- mStart = start;
- mEnd = end;
- mCurrent = start;
- updateView();
+ setRange(start, end, null/*displayedValues*/);
}
/**
@@ -176,39 +275,49 @@
updateView();
}
+ /**
+ * Set the current value for the number picker.
+ *
+ * @param current the current value the start of the range (inclusive)
+ * @throws IllegalArgumentException when current is not within the range
+ * of of the number picker
+ */
public void setCurrent(int current) {
+ if (current < mStart || current > mEnd) {
+ throw new IllegalArgumentException(
+ "current should be >= start and <= end");
+ }
mCurrent = current;
updateView();
}
/**
- * The speed (in milliseconds) at which the numbers will scroll
- * when the the +/- buttons are longpressed. Default is 300ms.
+ * Sets the speed at which the numbers will scroll when the +/-
+ * buttons are longpressed
+ *
+ * @param speed The speed (in milliseconds) at which the numbers will scroll
+ * default 300ms
*/
public void setSpeed(long speed) {
mSpeed = speed;
}
- public void onClick(View v) {
- validateInput(mText);
- if (!mText.hasFocus()) mText.requestFocus();
-
- // now perform the increment/decrement
- if (R.id.increment == v.getId()) {
- changeCurrent(mCurrent + 1);
- } else if (R.id.decrement == v.getId()) {
- changeCurrent(mCurrent - 1);
- }
- }
-
private String formatNumber(int value) {
return (mFormatter != null)
? mFormatter.toString(value)
: String.valueOf(value);
}
+ /**
+ * Sets the current value of this NumberPicker, and sets mPrevious to the previous
+ * value. If current is greater than mEnd less than mStart, the value of mCurrent
+ * is wrapped around.
+ *
+ * Subclasses can override this to change the wrapping behavior
+ *
+ * @param current the new value of the NumberPicker
+ */
protected void changeCurrent(int current) {
-
// Wrap around the values if we go past the start or end
if (current > mEnd) {
current = mStart;
@@ -221,14 +330,23 @@
updateView();
}
- protected void notifyChange() {
+ /**
+ * Notifies the listener, if registered, of a change of the value of this
+ * NumberPicker.
+ */
+ private void notifyChange() {
if (mListener != null) {
mListener.onChanged(this, mPrevious, mCurrent);
}
}
- protected void updateView() {
-
+ /**
+ * Updates the view of this NumberPicker. If displayValues were specified
+ * in {@link #setRange}, the string corresponding to the index specified by
+ * the current value will be returned. Otherwise, the formatter specified
+ * in {@link setFormatter} will be used to format the number.
+ */
+ private void updateView() {
/* If we don't have displayed values then use the
* current number else find the correct value in the
* displayed values for the current number.
@@ -253,16 +371,6 @@
updateView();
}
- public void onFocusChange(View v, boolean hasFocus) {
-
- /* When focus is lost check that the text field
- * has valid values.
- */
- if (!hasFocus) {
- validateInput(v);
- }
- }
-
private void validateInput(View v) {
String str = String.valueOf(((TextView) v).getText());
if ("".equals(str)) {
@@ -277,30 +385,15 @@
}
/**
- * We start the long click here but rely on the {@link NumberPickerButton}
- * to inform us when the long click has ended.
+ * @hide
*/
- public boolean onLongClick(View v) {
-
- /* The text view may still have focus so clear it's focus which will
- * trigger the on focus changed and any typed values to be pulled.
- */
- mText.clearFocus();
-
- if (R.id.increment == v.getId()) {
- mIncrement = true;
- mHandler.post(mRunnable);
- } else if (R.id.decrement == v.getId()) {
- mDecrement = true;
- mHandler.post(mRunnable);
- }
- return true;
- }
-
public void cancelIncrement() {
mIncrement = false;
}
+ /**
+ * @hide
+ */
public void cancelDecrement() {
mDecrement = false;
}
@@ -404,9 +497,26 @@
}
/**
+ * Returns the current value of the NumberPicker
* @return the current value.
*/
public int getCurrent() {
return mCurrent;
}
+
+ /**
+ * Returns the upper value of the range of the NumberPicker
+ * @return the uppper number of the range.
+ */
+ protected int getEndRange() {
+ return mEnd;
+ }
+
+ /**
+ * Returns the lower value of the range of the NumberPicker
+ * @return the lower number of the range.
+ */
+ protected int getBeginRange() {
+ return mStart;
+ }
}
diff --git a/common/java/com/android/common/widget/NumberPickerButton.java b/core/java/android/widget/NumberPickerButton.java
similarity index 91%
rename from common/java/com/android/common/widget/NumberPickerButton.java
rename to core/java/android/widget/NumberPickerButton.java
index f6b6d5d..1c8579c 100644
--- a/common/java/com/android/common/widget/NumberPickerButton.java
+++ b/core/java/android/widget/NumberPickerButton.java
@@ -14,20 +14,22 @@
* limitations under the License.
*/
-package com.android.common.widget;
+package android.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.ImageButton;
+import android.widget.NumberPicker;
import com.android.internal.R;
/**
- * This class exists purely to cancel long click events.
+ * This class exists purely to cancel long click events, that got
+ * started in NumberPicker
*/
-public class NumberPickerButton extends ImageButton {
+class NumberPickerButton extends ImageButton {
private NumberPicker mNumberPicker;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 12e8e29..7ba0fa1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6301,7 +6301,8 @@
if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
+ " before=" + before + " after=" + after + ": " + buffer);
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()
+ && !isPasswordInputType(mInputType)) {
mBeforeText = buffer.toString();
}
@@ -6972,9 +6973,7 @@
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean isPassword =
- (mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) ==
- (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
+ final boolean isPassword = isPasswordInputType(mInputType);
if (!isPassword) {
CharSequence text = getText();
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index b87e278..caed308 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -23,9 +23,9 @@
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.NumberPicker;
import com.android.internal.R;
-import com.android.common.widget.NumberPicker;
import java.text.DateFormatSymbols;
import java.util.Calendar;
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index bea009c0..3df419a 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -546,6 +546,11 @@
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
+ if (event.getPointerCount() > 1) {
+ // ZoomButtonsController doesn't handle mutitouch. Give up control.
+ return false;
+ }
+
if (mReleaseTouchListenerOnUp) {
// The controls were dismissed but we need to throw away all events until the up
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 404c513..b677b1e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -66,6 +66,13 @@
/** when preloading, GC after allocating this many bytes */
private static final int PRELOAD_GC_THRESHOLD = 50000;
+ /** throw on missing preload, only if this looks like a developer */
+ private static final boolean THROW_ON_MISSING_PRELOAD =
+ "1".equals(SystemProperties.get("persist.service.adb.enable"));
+
+ public static final String USAGE_STRING =
+ " <\"true\"|\"false\" for startSystemServer>";
+
private static LocalServerSocket sServerSocket;
/**
@@ -322,8 +329,8 @@
}
}
- if (missingClasses != null &&
- "1".equals(SystemProperties.get("persist.service.adb.enable"))) {
+ if (THROW_ON_MISSING_PRELOAD &&
+ missingClasses != null) {
throw new IllegalStateException(
"Missing class(es) for preloading, update preloaded-classes ["
+ missingClasses + "]");
@@ -597,12 +604,13 @@
// If requested, start system server directly from Zygote
if (argv.length != 2) {
- throw new RuntimeException(
- "ZygoteInit.main expects two arguments");
+ throw new RuntimeException(argv[0] + USAGE_STRING);
}
if (argv[1].equals("true")) {
startSystemServer();
+ } else if (!argv[1].equals("false")) {
+ throw new RuntimeException(argv[0] + USAGE_STRING);
}
Log.i(TAG, "Accepting command socket connections");
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 3c5a753..aafe453 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -16,7 +16,10 @@
package com.android.internal.widget;
+import android.app.DevicePolicyManager;
+import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.Context;
import android.os.SystemClock;
import android.provider.Settings;
import android.security.MessageDigest;
@@ -29,6 +32,7 @@
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
@@ -76,9 +80,9 @@
* pin = digit-only password
* password = alphanumeric password
*/
- public static final int MODE_PATTERN = 0;
- public static final int MODE_PIN = 1;
- public static final int MODE_PASSWORD = 2;
+ public static final int MODE_PATTERN = DevicePolicyManager.PASSWORD_MODE_SOMETHING;
+ public static final int MODE_PIN = DevicePolicyManager.PASSWORD_MODE_NUMERIC;
+ public static final int MODE_PASSWORD = DevicePolicyManager.PASSWORD_MODE_ALPHANUMERIC;
/**
* The minimum number of dots the user must include in a wrong pattern
@@ -91,17 +95,22 @@
private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
private final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
+ private final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
+ private final Context mContext;
private final ContentResolver mContentResolver;
-
+ private DevicePolicyManager mDevicePolicyManager;
private static String sLockPatternFilename;
private static String sLockPasswordFilename;
/**
* @param contentResolver Used to look up and save settings.
*/
- public LockPatternUtils(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
+ public LockPatternUtils(Context context) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mDevicePolicyManager =
+ (DevicePolicyManager)context.getSystemService(Context.DEVICE_POLICY_SERVICE);
// Initialize the location of gesture lock file
if (sLockPatternFilename == null) {
sLockPatternFilename = android.os.Environment.getDataDirectory()
@@ -112,6 +121,63 @@
}
+ public boolean isDevicePolicyActive() {
+ ComponentName admin = mDevicePolicyManager.getActiveAdmin();
+ return admin != null ? mDevicePolicyManager.isAdminActive(admin) : false;
+ }
+
+ public int getRequestedMinimumPasswordLength() {
+ return mDevicePolicyManager.getMinimumPasswordLength();
+ }
+
+ /**
+ * Gets the device policy password mode. If the mode is non-specific, returns
+ * MODE_PATTERN which allows the user to choose anything.
+ *
+ * @return
+ */
+ public int getRequestedPasswordMode() {
+ int policyMode = mDevicePolicyManager.getPasswordMode();
+ switch (policyMode) {
+ case DevicePolicyManager.PASSWORD_MODE_ALPHANUMERIC:
+ return MODE_PASSWORD;
+ case DevicePolicyManager.PASSWORD_MODE_NUMERIC:
+ return MODE_PIN;
+ case DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED:
+ return MODE_PATTERN;
+ }
+ return MODE_PATTERN;
+ }
+
+ /**
+ * Returns the actual password mode, as set by keyguard after updating the password.
+ *
+ * @return
+ */
+ public void reportFailedPasswordAttempt() {
+ mDevicePolicyManager.reportFailedPasswordAttempt();
+ }
+
+ public void reportSuccessfulPasswordAttempt() {
+ mDevicePolicyManager.reportSuccessfulPasswordAttempt();
+ }
+
+ public void setActivePasswordState(int mode, int length) {
+ int policyMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED;
+ switch (mode) {
+ case MODE_PATTERN:
+ policyMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED;
+ break;
+ case MODE_PIN:
+ policyMode = DevicePolicyManager.PASSWORD_MODE_NUMERIC;
+ break;
+ case MODE_PASSWORD:
+ policyMode = DevicePolicyManager.PASSWORD_MODE_ALPHANUMERIC;
+ break;
+ }
+ mDevicePolicyManager.setActivePasswordState(policyMode, length);
+ }
+
/**
* Check to see if a pattern matches the saved pattern. If no pattern exists,
* always returns true.
@@ -154,7 +220,7 @@
return true;
}
// Compare the hash from the file with the entered password's hash
- return Arrays.equals(stored, LockPatternUtils.passwordToHash(password));
+ return Arrays.equals(stored, passwordToHash(password));
} catch (FileNotFoundException fnfe) {
return true;
} catch (IOException ioe) {
@@ -209,6 +275,16 @@
}
/**
+ * Clear any lock pattern or password.
+ */
+ public void clearLock() {
+ saveLockPassword(null, LockPatternUtils.MODE_PATTERN);
+ setLockPatternEnabled(false);
+ saveLockPattern(null);
+ setLong(PASSWORD_TYPE_KEY, MODE_PATTERN);
+ }
+
+ /**
* Save a lock pattern.
* @param pattern The new pattern to save.
*/
@@ -225,8 +301,14 @@
raf.write(hash, 0, hash.length);
}
raf.close();
- setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
- setLong(PASSWORD_TYPE_KEY, MODE_PATTERN);
+ if (pattern != null) {
+ setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
+ setLong(PASSWORD_TYPE_KEY, MODE_PATTERN);
+ DevicePolicyManager dpm = (DevicePolicyManager)mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ dpm.setActivePasswordState(
+ DevicePolicyManager.PASSWORD_MODE_SOMETHING, pattern.size());
+ }
} catch (FileNotFoundException fnfe) {
// Cant do much, unless we want to fail over to using the settings provider
Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
@@ -240,10 +322,9 @@
* Save a lock password.
* @param password The password to save
*/
- public void saveLockPassword(String password) {
+ public void saveLockPassword(String password, int mode) {
// Compute the hash
- boolean numericHint = password != null ? TextUtils.isDigitsOnly(password) : false;
- final byte[] hash = LockPatternUtils.passwordToHash(password);
+ final byte[] hash = passwordToHash(password);
try {
// Write the hash to file
RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rw");
@@ -254,7 +335,16 @@
raf.write(hash, 0, hash.length);
}
raf.close();
- setLong(PASSWORD_TYPE_KEY, numericHint ? MODE_PIN : MODE_PASSWORD);
+ if (password != null) {
+ int textMode = TextUtils.isDigitsOnly(password) ? MODE_PIN : MODE_PASSWORD;
+ if (textMode > mode) {
+ mode = textMode;
+ }
+ setLong(PASSWORD_TYPE_KEY, mode);
+ DevicePolicyManager dpm = (DevicePolicyManager)mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ dpm.setActivePasswordState(mode, password.length());
+ }
} catch (FileNotFoundException fnfe) {
// Cant do much, unless we want to fail over to using the settings provider
Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename);
@@ -330,6 +420,21 @@
}
}
+ private String getSalt() {
+ long salt = getLong(LOCK_PASSWORD_SALT_KEY, 0);
+ if (salt == 0) {
+ try {
+ salt = SecureRandom.getInstance("SHA1PRNG").nextLong();
+ setLong(LOCK_PASSWORD_SALT_KEY, salt);
+ Log.v(TAG, "Initialized lock password salt");
+ } catch (NoSuchAlgorithmException e) {
+ // Throw an exception rather than storing a password we'll never be able to recover
+ throw new IllegalStateException("Couldn't get SecureRandom number", e);
+ }
+ }
+ return Long.toHexString(salt);
+ }
+
/*
* Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
* Not the most secure, but it is at least a second level of protection. First level is that
@@ -337,15 +442,14 @@
* @param password the gesture pattern.
* @return the hash of the pattern in a byte array.
*/
- public static byte[] passwordToHash(String password) {
+ public byte[] passwordToHash(String password) {
if (password == null) {
return null;
}
String algo = null;
byte[] hashed = null;
try {
- long salt = 0x2374868151054924L; // TODO: make this unique to device
- byte[] saltedPassword = (password + Long.toString(salt)).getBytes();
+ byte[] saltedPassword = (password + getSalt()).getBytes();
byte[] sha1 = MessageDigest.getInstance(algo = "SHA-1").digest(saltedPassword);
byte[] md5 = MessageDigest.getInstance(algo = "MD5").digest(saltedPassword);
hashed = (toHex(sha1) + toHex(md5)).getBytes();
@@ -476,6 +580,7 @@
}
private boolean getBoolean(String systemSettingKey) {
+ // STOPSHIP: these need to be moved to secure settings!
return 1 ==
android.provider.Settings.System.getInt(
mContentResolver,
@@ -483,6 +588,7 @@
}
private void setBoolean(String systemSettingKey, boolean enabled) {
+ // STOPSHIP: these need to be moved to secure settings!
android.provider.Settings.System.putInt(
mContentResolver,
systemSettingKey,
@@ -490,10 +596,12 @@
}
private long getLong(String systemSettingKey, long def) {
+ // STOPSHIP: these need to be moved to secure settings!
return android.provider.Settings.System.getLong(mContentResolver, systemSettingKey, def);
}
private void setLong(String systemSettingKey, long value) {
+ // STOPSHIP: these need to be moved to secure settings!
android.provider.Settings.System.putLong(mContentResolver, systemSettingKey, value);
}
diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
index adafbb4..eb6d1a6 100644
--- a/core/java/com/android/internal/widget/SlidingTab.java
+++ b/core/java/com/android/internal/widget/SlidingTab.java
@@ -145,20 +145,6 @@
void onGrabbedStateChange(View v, int grabbedState);
}
- // TODO: For debugging; remove after glitches debugged.
- @Override
- protected void dispatchDraw(Canvas canvas) {
- int orientation = getResources().getConfiguration().orientation;
- if (mOrientation == HORIZONTAL && orientation != Configuration.ORIENTATION_PORTRAIT
- || mOrientation == VERTICAL && orientation != Configuration.ORIENTATION_LANDSCAPE) {
- // UBER HACK ALERT. This is a workaround for a configuration race condition between
- // orientation changed notification and the resize notification. This just prevents
- // us from drawing under this circumstance, though the view will still be wrong.
- return;
- }
- super.dispatchDraw(canvas);
- }
-
/**
* Simple container class for all things pertinent to a slider.
* A slider consists of 3 Views:
@@ -433,7 +419,7 @@
/**
* Start animating the slider. Note we need two animations since an Animator
* keeps internal state of the invalidation region which is just the view being animated.
- *
+ *
* @param anim1
* @param anim2
*/
@@ -671,7 +657,7 @@
resetView();
}
anim.setAnimationListener(mAnimationDoneListener);
-
+
/* Animation can be the same for these since the animation just holds */
mLeftSlider.startAnimation(anim, anim);
mRightSlider.startAnimation(anim, anim);
diff --git a/core/java/com/android/internal/widget/WeightedLinearLayout.java b/core/java/com/android/internal/widget/WeightedLinearLayout.java
new file mode 100644
index 0000000..b90204e
--- /dev/null
+++ b/core/java/com/android/internal/widget/WeightedLinearLayout.java
@@ -0,0 +1,84 @@
+/*
+ * 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.internal.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.widget.LinearLayout;
+
+import static android.view.View.MeasureSpec.*;
+import static com.android.internal.R.*;
+
+/**
+ * A special layout when measured in AT_MOST will take up a given percentage of
+ * the available space.
+ */
+public class WeightedLinearLayout extends LinearLayout {
+ private float mMajorWeight;
+ private float mMinorWeight;
+
+ public WeightedLinearLayout(Context context) {
+ super(context);
+ }
+
+ public WeightedLinearLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a =
+ context.obtainStyledAttributes(attrs, styleable.WeightedLinearLayout);
+
+ mMajorWeight = a.getFloat(styleable.WeightedLinearLayout_majorWeight, 0.0f);
+ mMinorWeight = a.getFloat(styleable.WeightedLinearLayout_minorWeight, 0.0f);
+
+ a.recycle();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+
+ final int widthMode = getMode(widthMeasureSpec);
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int width = getMeasuredWidth();
+ int height = getMeasuredHeight();
+ boolean measure = false;
+
+ final int widthSize = getSize(widthMeasureSpec);
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, EXACTLY);
+
+ final float widthWeight = isPortrait ? mMinorWeight : mMajorWeight;
+ if (widthMode == AT_MOST && widthWeight > 0.0f) {
+ if (width < (widthSize * widthWeight)) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * widthWeight),
+ EXACTLY);
+ measure = true;
+ }
+ }
+
+ // TODO: Support height?
+
+ if (measure) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+}
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index d2e9454..4e1ae62 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -382,7 +382,7 @@
dbus_connection_unregister_object_path(nat->conn, agent_path);
dbus_bus_remove_match(nat->conn,
- "type='signal',interface='org.bluez.audio.Sink'",
+ "type='signal',interface='org.bluez.AudioSink'",
&err);
if (dbus_error_is_set(&err)) {
LOG_AND_FREE_DBUS_ERROR(&err);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c49a86a..d81476a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -927,21 +927,27 @@
android:description="@string/permdesc_readInputState"
android:protectionLevel="signature" />
- <!-- Must be required by input method services, to ensure that only the
- system can bind to them. -->
+ <!-- Must be required by an {@link android.inputmethodservice.InputMethodService},
+ to ensure that only the system can bind to it. -->
<permission android:name="android.permission.BIND_INPUT_METHOD"
android:label="@string/permlab_bindInputMethod"
android:description="@string/permdesc_bindInputMethod"
android:protectionLevel="signature" />
- <!-- Must be required by wallpaper services, to ensure that only the
- system can bind to them.
- @hide Live Wallpaper -->
+ <!-- Must be required by a {@link android.service.wallpaper.WallpaperService},
+ to ensure that only the system can bind to it. -->
<permission android:name="android.permission.BIND_WALLPAPER"
android:label="@string/permlab_bindWallpaper"
android:description="@string/permdesc_bindWallpaper"
android:protectionLevel="signatureOrSystem" />
+ <!-- Must be required by device administration receiver, to ensure that only the
+ system can interact with it. -->
+ <permission android:name="android.permission.BIND_DEVICE_ADMIN"
+ android:label="@string/permlab_bindDeviceAdmin"
+ android:description="@string/permdesc_bindDeviceAdmin"
+ android:protectionLevel="signature" />
+
<!-- Allows low-level access to setting the orientation (actually
rotation) of the screen. Not for use by normal applications. -->
<permission android:name="android.permission.SET_ORIENTATION"
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index 409dcd3..231e11c 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -18,7 +18,8 @@
*/
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.WeightedLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/parentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -27,7 +28,8 @@
android:paddingBottom="3dip"
android:paddingLeft="3dip"
android:paddingRight="1dip"
- >
+ android:majorWeight="0.5"
+ android:minorWeight="0.8">
<LinearLayout android:id="@+id/topPanel"
android:layout_width="match_parent"
@@ -109,7 +111,8 @@
android:orientation="horizontal"
android:paddingTop="4dip"
android:paddingLeft="2dip"
- android:paddingRight="2dip" >
+ android:paddingRight="2dip"
+ android:useLargestChild="true">
<LinearLayout android:id="@+id/leftSpacer"
android:layout_weight="0.25"
android:layout_width="0dip"
@@ -142,4 +145,4 @@
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
-</LinearLayout>
+</com.android.internal.widget.WeightedLinearLayout>
diff --git a/core/res/res/layout/date_picker.xml b/core/res/res/layout/date_picker.xml
index 56d5494..4fd46b3 100644
--- a/core/res/res/layout/date_picker.xml
+++ b/core/res/res/layout/date_picker.xml
@@ -29,7 +29,7 @@
android:layout_height="wrap_content">
<!-- Month -->
- <com.android.common.widget.NumberPicker
+ <NumberPicker
android:id="@+id/month"
android:layout_width="80dip"
android:layout_height="wrap_content"
@@ -40,7 +40,7 @@
/>
<!-- Day -->
- <com.android.common.widget.NumberPicker
+ <NumberPicker
android:id="@+id/day"
android:layout_width="80dip"
android:layout_height="wrap_content"
@@ -51,7 +51,7 @@
/>
<!-- Year -->
- <com.android.common.widget.NumberPicker
+ <NumberPicker
android:id="@+id/year"
android:layout_width="95dip"
android:layout_height="wrap_content"
diff --git a/core/res/res/layout/number_picker.xml b/core/res/res/layout/number_picker.xml
index 44dca33..9241708 100644
--- a/core/res/res/layout/number_picker.xml
+++ b/core/res/res/layout/number_picker.xml
@@ -19,7 +19,7 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <com.android.common.widget.NumberPickerButton android:id="@+id/increment"
+ <NumberPickerButton android:id="@+id/increment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/timepicker_up_btn" />
@@ -34,7 +34,7 @@
android:textSize="30sp"
android:background="@drawable/timepicker_input" />
- <com.android.common.widget.NumberPickerButton android:id="@+id/decrement"
+ <NumberPickerButton android:id="@+id/decrement"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/timepicker_down_btn" />
diff --git a/core/res/res/layout/status_bar_latest_event_content.xml b/core/res/res/layout/status_bar_latest_event_content.xml
index 2f7036f..c3aa041 100644
--- a/core/res/res/layout/status_bar_latest_event_content.xml
+++ b/core/res/res/layout/status_bar_latest_event_content.xml
@@ -45,7 +45,7 @@
android:textSize="14sp"
android:paddingLeft="4dp"
/>
- <TextView android:id="@+id/time"
+ <android.widget.DateTimeView android:id="@+id/time"
android:layout_marginLeft="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/core/res/res/layout/time_picker.xml b/core/res/res/layout/time_picker.xml
index b9f42ce..6124ea8 100644
--- a/core/res/res/layout/time_picker.xml
+++ b/core/res/res/layout/time_picker.xml
@@ -26,7 +26,7 @@
android:layout_height="wrap_content">
<!-- hour -->
- <com.android.common.widget.NumberPicker
+ <NumberPicker
android:id="@+id/hour"
android:layout_width="70dip"
android:layout_height="wrap_content"
@@ -35,7 +35,7 @@
/>
<!-- minute -->
- <com.android.common.widget.NumberPicker
+ <NumberPicker
android:id="@+id/minute"
android:layout_width="70dip"
android:layout_height="wrap_content"
diff --git a/core/res/res/values-land/donottranslate.xml b/core/res/res/values-land/donottranslate.xml
new file mode 100644
index 0000000..75a7b06
--- /dev/null
+++ b/core/res/res/values-land/donottranslate.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- @hide DO NOT TRANSLATE. Workaround for resource race condition in lockscreen -->
+ <bool name="lockscreen_isPortrait">false</bool>
+</resources>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 53bb586..3dbfa25 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -131,7 +131,8 @@
the first component from this list which is found to be installed is set as the
preferred activity. -->
<string-array name="default_web_search_providers">
- <item>com.android.quicksearchbox/com.android.googlesearch.GoogleSearch</item>
+ <item>com.google.android.googlequicksearchbox/.google.GoogleSearch</item>
+ <item>com.android.quicksearchbox/.google.GoogleSearch</item>
<item>com.google.android.providers.enhancedgooglesearch/.Launcher</item>
<item>com.android.googlesearch/.GoogleSearch</item>
<item>com.android.websearch/.Search.1</item>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8dadd88..287e3a0 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1700,6 +1700,10 @@
space by giving it a layout_weight of 0.5 and setting the weightSum
to 1.0. -->
<attr name="weightSum" format="float" />
+ <!-- When set to true, all children with a weight will be considered having
+ the minimum size of the largest child. If false, all children are
+ measured normally. -->
+ <attr name="useLargestChild" format="boolean" />
</declare-styleable>
<declare-styleable name="ListView">
<!-- Reference to an array resource that will populate the ListView. For static content,
@@ -2330,6 +2334,11 @@
<attr name="orientation" />
</declare-styleable>
+ <!-- @hide -->
+ <declare-styleable name="WeightedLinearLayout">
+ <attr name="majorWeight" format="float" />
+ <attr name="minorWeight" format="float" />
+ </declare-styleable>
<!-- ========================= -->
<!-- Drawable class attributes -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index cc54ba3..9a8af3f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -164,6 +164,15 @@
<!-- Close low battery warning when battery level reaches this value -->
<integer name="config_lowBatteryCloseWarningLevel">20</integer>
+ <!-- Default color for notification LED. -->
+ <color name="config_defaultNotificationColor">#ff00ff00</color>
+
+ <!-- Default LED on time for notification LED in milliseconds. -->
+ <integer name="config_defaultNotificationLedOn">500</integer>
+
+ <!-- Default LED off time for notification LED in milliseconds. -->
+ <integer name="config_defaultNotificationLedOff">2000</integer>
+
<!-- Allow the menu hard key to be disabled in LockScreen on some devices -->
<bool name="config_disableMenuKeyInLockScreen">false</bool>
diff --git a/core/res/res/values/donottranslate.xml b/core/res/res/values/donottranslate.xml
index 6def3bf..78d4d36d 100644
--- a/core/res/res/values/donottranslate.xml
+++ b/core/res/res/values/donottranslate.xml
@@ -20,4 +20,6 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Default text encoding for WebSettings. -->
<string name="default_text_encoding">Latin-1</string>
+ <!-- @hide DO NOT TRANSLATE. Workaround for resource race condition in lockscreen. -->
+ <bool name="lockscreen_isPortrait">true</bool>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 539db83..265dacd 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -617,6 +617,12 @@
interface of a wallpaper. Should never be needed for normal applications.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_bindDeviceAdmin">interact with a device admin</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_bindDeviceAdmin">Allows the holder to send intents to
+ a device administrator. Should never be needed for normal applications.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_setOrientation">change screen orientation</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_setOrientation">Allows an application to change
diff --git a/docs/html/guide/developing/debug-tasks.jd b/docs/html/guide/developing/debug-tasks.jd
index 3279741..975f6998 100644
--- a/docs/html/guide/developing/debug-tasks.jd
+++ b/docs/html/guide/developing/debug-tasks.jd
@@ -6,7 +6,8 @@
<h2>In this document</h2>
<ol>
<li><a href="#tools">Tools</a></li>
- <li><a href="#additionaldebugging">Debug and Test Settings</a></li>
+ <li><a href="#additionaldebugging">Debug with Dev Tools</a></li>
+ <li><a href="#DebuggingWebPages">Debugging Web Pages</a></li>
<li><a href="#toptips">Top Debugging Tips</a></li>
<li><a href="#ide-debug-port">Configuring Your IDE to Attach to the Debugging Port</a></li>
</ol>
@@ -17,178 +18,242 @@
<h2 id="tools">Tools</h2>
-<p>The Android SDK includes a fairly extensive set of tools to help you debug your programs: </p>
-<ul>
- <li><a href="{@docRoot}guide/developing/tools/ddms.html"><strong>DDMS</strong></a> - A graphical program that
+
+<p>The Android SDK includes a set of tools to help you debug and profile
+your applications. Here are some tools that you'll use most often:</p>
+
+<dl>
+ <dt><strong><a href="{@docRoot}guide/developing/tools/adb.html">Android Debug Bridge
+(ADB)</a></strong></dt>
+ <dd>Provides various device management capabilities, including
+ moving and syncing files to the emulator, forwarding ports, and running a UNIX
+ shell on the emulator.</dd>
+ <dt><strong><a href="{@docRoot}guide/developing/tools/ddms.html">Dalvik Debug Monitor Server
+(DDMS)</a></strong></dt>
+ <dd>A graphical program that
supports port forwarding (so you can set up breakpoints in your code in your
IDE), screen captures on the emulator, thread and stack information,
- and many other features. You can also run logcat to retrieve your Log messages.
- See the linked topic for more information. </li>
- <li><strong><a href="{@docRoot}guide/developing/tools/ddms.html#logcat">logcat</a></strong> - Dumps a log of system
- messages. The messages include a stack trace when the emulator throws an error,
- as well as Log messages. To run logcat, see the linked topic.
-
- <pre>...
-I/MemoryDealer( 763): MemoryDealer (this=0x54bda0): Creating 2621440 bytes heap at 0x438db000
-<span style="background-color:#CCCCCC; border-bottom:medium">I/Logger( 1858): getView() requesting item number 0
-I/Logger( 1858): getView() requesting item number 1
-I/Logger( 1858): getView() requesting item number 2</span>
-D/ActivityManager( 763): Stopping: HistoryRecord{409dbb20 com.android.home.AllApps}
-...</pre>
+ and many other features. You can also run logcat to retrieve your Log messages.</dd>
+ </dd>
+ <dt><strong><a href="{@docRoot}guide/developing/tools/traceview.html">Traceview</a></strong></dt>
+ <dd>A graphical viewer that displays trace file data for method calls and times saved by
+ your application, which can help you profile the performance of your application.</dd>
+ <dt><strong><a href="{@docRoot}guide/developing/tools/ddms.html#logcat">logcat</a></strong></dt>
+ <dd>Dumps a log of system
+ messages. The messages include a stack trace when the emulator throws an error,
+ as well as {@link android.util.Log} messages you've written from your application. To run
+ logcat, execute <code>adb logcat</code> or, from DDMS, select <strong>Device > Run
+ logcat</strong>.
+ <p>{@link android.util.Log} is a logging
+ class you can use to print out messages to the logcat. You can read messages
+ in real time if you run logcat on DDMS (covered next). Common logging methods include:
+ {@link android.util.Log#v(String,String)} (verbose), {@link
+ android.util.Log#d(String,String)} (debug), {@link android.util.Log#i(String,String)}
+ (information), {@link android.util.Log#w(String,String)} (warning) and {@link
+ android.util.Log#e(String,String)} (error). For example:</p>
+<pre class="no-pretty-print">
+Log.i("MyActivity", "MyClass.getView() — get item number " + position);
+</pre>
+ <p>The logcat will then output something like:</p>
+<pre class="no-pretty-print">
+I/MyActivity( 1557): MyClass.getView() — get item number 1
+</pre>
+ <p>Logcat is also the place to look when debugging a web page in the Android browser. All
+browser bugs will be output to logcat with the {@code WebCore} tag.
+</dl>
- </li>
- <li><p><strong>{@link android.util.Log Android Log}</strong>- A logging
- class to print out messages to a log file on the emulator. You can read messages
- in real time if you run logcat on DDMS (covered next). Add a few logging
- method calls to your code.</p>
- <p>To use the <code>Log</code> class, you just call <code>Log.v()</code>
- (verbose), <code>Log.d()</code> (debug), <code>Log.i()</code> (information),
- <code>Log.w()</code> (warning) or <code>Log.e</code> (error) depending
- on the importance you wish to assign the log message.</p>
- <code>Log.i("MyActivity", "MyClass.getView()
- — Requesting item number " + position)</code>
- <p>You can use logcat to read these messages</p></li>
- <li><strong><a href="{@docRoot}guide/developing/tools/traceview.html">Traceview</a> </strong>- Android can save
- a log of method calls and times to a logging file that you can view in a
- graphical reader called Traceview. See the linked topic for more information. </li>
-</ul>
-<ul>
- <li><a href="{@docRoot}guide/developing/eclipse-adt.html"><strong>Eclipse plugin</strong></a> - The ADT Plugin
- for Eclipse integrates a number of these tools (ADB, DDMS, logcat output,
- and other functionality). See the linked topic for more information. </li>
- <li><strong>Debug and Test Device Settings</strong> - Android exposes several settings
- that expose useful information such as CPU usage and frame rate. See <a href="#additionaldebugging">Debug
- and Test Settings on the Emulator</a> below. </li>
-</ul>
-<p>Also, see the <a href="{@docRoot}resources/faq/troubleshooting.html">Troubleshooting</a> section
- of the doc to figure out why your application isn't appearing on the emulator,
- or why it's not starting. </p>
+<p>For more information about all the development tools provided with the Android SDK, see the <a
+href="{@docRoot}guide/developing/tools/index.html">Tools</a> document.</p>
+<p>In addition to the above tools, you may also find the following useful for debugging:
+<dl>
+ <dt><a href="{@docRoot}guide/developing/eclipse-adt.html"><strong>Eclipse ADT
+plugin</strong></a></dt>
+ <dd>The ADT Plugin for Eclipse integrates a number of the Android development tools (ADB, DDMS,
+logcat output, and other functionality), so that you won't work with them directly but will utilize
+them through the Eclipse IDE.</dd>
+ <dt><strong>Developer Settings in the Dev Tools app</strong></dt>
+ <dd>The Dev Tools application included in the emulator system image exposes several settings
+ that provide useful information such as CPU usage and frame rate. See <a
+href="#additionaldebugging">Debugging and Testing with Dev Tools</a> below.</dd>
+</dl>
-<h2 id="additionaldebugging">Debug and Test Settings</h2>
+<h2 id="additionaldebugging">Debugging and Testing with Dev Tools</h2>
-<p>With the <strong>Dev Tools</strong> application, you can turn on a number of settings that will make it easier to test
- and debug your applications. To get to the development settings page on the emulator, launch the
- <strong>Dev Tools</strong> application and open <strong>Development Settings</strong>.
- This will open the development settings page with the following options (among
- others):</p>
-<ul>
- <li><strong>Debug app</strong> Selects the application that
- will be debugged. You do not need to set this to attach a debugger, but setting
- this value has two effects:
+<p>With the Dev Tools application, you can turn on a number of settings that will
+make it easier to test and debug your applications. The Dev Tools application is automatically
+installed on all system images included with the SDK. The source code for the Dev Tools application
+is also provided in the SDK samples so that you may build it and then install the application on any
+development device.</p>
+
+<p>To get to the development settings page on the emulator, launch the Dev Tools application and
+select Development Settings. This will open the Development Settings page with the
+following options (among others):</p>
+
+<dl>
+ <dt><strong>Debug app</strong></dt>
+ <dd>Lets you select the application to debug. You do not need to set this to attach a debugger,
+ but setting this value has two effects:
<ul>
- <li>It will prevent Android from throwing an error if you pause on
+ <li>It will prevent Android from throwing an error if you pause on
a breakpoint for a long time while debugging.</li>
<li>It will enable you to select the <em>Wait for Debugger</em> option
to pause application startup until your debugger attaches (described
next). </li>
</ul>
- </li>
- <li><strong>Wait for debugger </strong>
- Blocks the selected application from loading until a debugger attaches. This
+ </dd>
+ <dt><strong>Wait for debugger</strong></dt>
+ <dd>Blocks the selected application from loading until a debugger attaches. This
way you can set a breakpoint in onCreate(), which is important to debug
the startup process of an Activity. When you change this option, any
currently running instances of the selected application will be killed.
In order to check this box, you must have selected a debug application
as described in the previous option. You can do the same thing by adding
- {@link android.os.Debug#waitForDebugger()} to your code. </li>
- <li><strong>Immediately destroy activities</strong> Tells the
+ {@link android.os.Debug#waitForDebugger()} to your code.</dd>
+ <dt><strong>Show screen updates</strong></dt>
+ <dd>Flashes a momentary pink rectangle on any screen sections that are being
+ redrawn. This is very useful for discovering unnecessary screen drawing.</dd>
+ <dt><strong>Immediately destroy activities</strong></dt>
+ <dd>Tells the
system to destroy an activity as soon as it is stopped (as if Android had to
reclaim memory). This is very useful for testing the {@link android.app.Activity#onSaveInstanceState}
/ {@link android.app.Activity#onCreate(android.os.Bundle)} code path, which would
otherwise be difficult to force. Choosing this option will probably reveal
- a number of problems in your application due to not saving state.</li>
- <li><strong>Show screen updates</strong>
- Flashes a momentary pink rectangle on any screen sections that are being
- redrawn. This is very useful for discovering unnecessary screen drawing. </li>
- <li><strong>Show CPU usage</strong> Displays CPU meters at the
+ a number of problems in your application due to not saving state.</dd>
+ <dt><strong>Show CPU usage</strong></dt>
+ <dd>Displays CPU meters at the
top of the screen, showing how much the CPU is being used. The top red bar
shows overall CPU usage, and the green bar underneath it shows the CPU time
spent in compositing the screen. <em>Note: You cannot turn this feature off
- once it is on, without restarting the emulator.</em> </li>
- <li><strong>Show background</strong> Displays a background pattern
+ once it is on, without restarting the emulator.</em> </dd>
+ <dt><strong>Show background</strong></dt>
+ <dd>Displays a background pattern
when no activity screens are visible. This typically does not happen, but
- can happen during debugging. </li>
-</ul>
+ can happen during debugging.</dd>
+</dl>
+
<p>These settings will be remembered across emulator restarts. </p>
+<h2 id="DebuggingWebPages">Debugging Web Pages</h2>
+
+<p>If you're developing a web application for Android devices, you can debug your JavaScript on
+Android using the Console APIs, which will output messages to logcat. If you're familiar
+debugging web pages with Firefox's FireBug or WebKit's Web Inspector, then you're probably familiar
+with the Console APIs. The Android Browser (and {@link android.webkit.WebChromeClient}) supports
+most of the same APIs.</p>
+
+<p>When you call a function from the Console APIs (in the DOM's {@code window.console} object),
+you will see the output in logcat as a warning. For example, if your web page
+executes the following JavaScript:</p>
+<pre class="no-pretty-print">
+console.log("Hello World");
+</pre>
+<p>Then the logcat output from the Android Browser will look like this:</p>
+<pre class="no-pretty-print">
+W/browser ( 202): Console: Hello World :0
+</pre>
+
+<p class="note"><strong>Note:</strong> All Console messages from the Android
+Browser are tagged with the name "browser" on Android platforms running API Level 7 or higher and
+tagged with the name "WebCore" for platforms running API Level 6 or lower.</p>
+
+<p>Not all of the Console APIs available in Firefox or other WebKit browsers are implemented
+on Android. Mostly, you need to depend on basic text logging provided by
+functions like {@code console.log(String)}, {@code console.info(String)}, {@code
+console.warn(String)}, and {@code console.error(String)}. Although other Console functions may not
+be implemented, they will not raise run-time errors, but will simply not behave as you might
+expect.</p>
+
+<p>If you've implemented a custom {@link android.webkit.WebView} in your application, then in order
+to receive messages that are sent through the Console APIs, you must provide a {@link
+android.webkit.WebChromeClient} that implements the {@link
+android.webkit.WebChromeClient#onConsoleMessage(String,int,String) onConsoleMessage()} callback
+method. For example, assuming that the {@code myWebView} field references the {@link
+android.webkit.WebView} in your application, you can log debug messages like this:</p>
+<pre>
+myWebView.setWebChromeClient(new WebChromeClient() {
+ public void onConsoleMessage(String message, int lineNumber, String sourceID) {
+ Log.d("MyApplication", message);
+ }
+});
+</pre>
+<p>The {@link android.webkit.WebChromeClient#onConsoleMessage(String,int,String)
+onConsoleMessage()} method will be called each time one of the Console methods is called from
+within your {@link android.webkit.WebView}.</p>
+<p>When the "Hello World" log is executed through your {@link android.webkit.WebView}, it will
+now look like this:</p>
+<pre class="no-pretty-print">
+D/MyApplication ( 430): Hello World
+</pre>
+
+<p class="note"><strong>Note:</strong> The {@link
+android.webkit.WebChromeClient#onConsoleMessage(String,int,String) onConsoleMessage()} callback
+method was added with API Level 7. If you are targetting platforms running API Level 6 or lower,
+then your Console messages will automatically be sent to logcat with the "WebCore" logging tag.</p>
+
+
+
+
<h2 id="toptips">Top Debugging Tips</h2>
-<!--
-<ul>
- <li><a href="#stackdump">Quick stack dump</a></li>
- <li><a href="#displayinfo">Displaying useful info on the emulator screen </a></li>
- <li><a href="#dumpstate">Getting system state information from the emulator (dumpstate)</a></li>
- <li><a href="#dumpsys">Getting application state information from the emulator (dumpsys)</a></li>
- <li><a href="#radioinfo">Getting wireless connectivity information</a></li>
- <li><a href="#loggingdata">Logging Trace Data</a></li>
- <li><a href="#logradio">Logging Radio Data </a></li>
- <li><a href="#adb">Running adb</a></li>
- <li><a href="#screencaps">Getting screen captures from the emulator</a></li>
- <li><a href="#debughelpers">Using debug helper classes</a></li>
-</ul>
--->
+
<dl>
-<dt>Quick stack dump <a name="stackdump" id="stackdump"></a></dt>
+<dt><strong>Dump the stack trace</strong></dt>
<dd>To obtain a stack dump from emulator, you can log
in with <code>adb shell</code>, use "ps" to find the process you
want, and then "kill -3 ". The stack trace appears in the log file.
</dd>
-<dt>Displaying useful info on the emulator screen<a name="displayinfo" id="displayinfo"></a></dt>
+<dt><strong>Display useful info on the emulator screen</strong></dt>
<dd>The device can display useful information such as CPU usage or highlights
around redrawn areas. Turn these features on and off in the developer settings
window as described in <a href="#additionaldebugging">Setting debug and test
configurations on the emulator</a>.
</dd>
-<dt>Getting system state information from the emulator (dumpstate)<a name="dumpstate" id="dumpstate"></a> </dt>
+<dt><strong>Get system state information from the emulator (dumpstate)</strong></dt>
<dd>You can access dumpstate information from the Dalvik Debug Monitor Service
tool. See <a href="{@docRoot}guide/developing/tools/adb.html#dumpsys">dumpsys and
dumpstate</a> on the adb topic page.</dd>
-<dt>Getting application state information from the emulator (dumpsys)<a name="dumpsys" id="dumpsys"></a></dt>
+<dt><strong>Get application state information from the emulator (dumpsys)</strong></dt>
<dd>You can access dumpsys information from the Dalvik Debug Monitor Service
tool. See <a href="{@docRoot}guide/developing/tools/adb.html#dumpsys">dumpsys and
dumpstate</a> on the adb topic page.</dd>
-<dt>Getting wireless connectivity information <a name="radioinfo" id="radioinfo"></a></dt>
+<dt><strong>Get wireless connectivity information</strong></dt>
<dd>You can get information about wireless connectivity using the Dalvik Debug
Monitor Service tool. From the <strong>Device</strong> menu, select "Dump
radio state".</dd>
-<dt>Logging Trace Data<a name="loggingdata" id="loggingdata"></a></dt>
+<dt><strong>Log trace data</strong></dt>
<dd>You can log method calls and other tracing data in an activity by calling
-android.os.Debug.startMethodTracing(). See <a
+{@link android.os.Debug#startMethodTracing(String) startMethodTracing()}. See <a
href="{@docRoot}guide/developing/tools/traceview.html">Running the Traceview Debugging
Program</a> for details. </dd>
-<dt>Logging Radio Data<a name="logradio" id="logradio"></a></dt>
+<dt><strong>Log radio data</strong></dt>
<dd>By default, radio information is not logged to the system (it is a lot of
data). However, you can enable radio logging using the following commands:
-<pre>
+<pre class="no-pretty-print">
adb shell
logcat -b radio
</pre>
</dd>
-<dt>Running adb<a name="adb" id="adb"></a></dt>
-<dd>Android ships with a tool called adb that provides various capabilities, including
-moving and syncing files to the emulator, forwarding ports, and running a UNIX
-shell on the emulator. See <a href="{@docRoot}guide/developing/tools/adb.html">Using adb</a> for details.</dd>
+<dt><strong>Capture screenshots</strong></dt>
+<dd>The Dalvik Debug Monitor Server (DDMS) can capture screenshots from the emulator. Select
+<strong>Device > Screen capture</strong>.</dd>
-<dt>Getting screen captures from the emulator<a name="screencaps" id="screencaps"></a></dt>
-<dd> Dalvik Debug Monitor Server (DDMS) can capture screenshots from the emulator.</dd>
-
-
-<a name="debughelpers"></a>
-
-<dt>Using debugging helper classes</dt>
-
+<dt><strong>Use debugging helper classes</strong></dt>
<dd>Android provides debug helper classes such as {@link android.util.Log
util.Log} and {@link android.os.Debug} for your convenience. </dd>
</dl>
+<p>Also see the <a href="{@docRoot}resources/faq/troubleshooting.html">Troubleshooting</a> document
+for answers to some common developing and debugging issues.</p>
+
+
<h2 id="ide-debug-port">Configuring Your IDE to Attach to the Debugging Port</h2>
<p>DDMS will assign a specific debugging port to every virtual machine that it
diff --git a/docs/html/resources/community-groups.jd b/docs/html/resources/community-groups.jd
index 61fbcc8..a36a425 100644
--- a/docs/html/resources/community-groups.jd
+++ b/docs/html/resources/community-groups.jd
@@ -1,43 +1,65 @@
community=true
-page.title=Android Developer Groups
+page.title=Developer Forums
@jd:body
-<p>Welcome to the Android developers community! We're glad you're here and invite you to participate in these discussions. Before posting, please read the <a href="http://source.android.com/discuss/android-discussion-groups-charter">Groups Charter</a> that covers the community guidelines.</p>
+<p>Welcome to the Android developers community! We're glad you're here and invite you to participate in discussions with other Android application developers on topics that interest you.</p>
-<p class="note"><strong>Note:</strong> If you are seeking discussion about Android source code (not application development),
-then please refer to the <a href="http://source.android.com/discuss">Open Source Project Mailing lists</a>.</p>
+<p>The lists on this page are primarily for discussion about Android application development. If you are seeking discussion about Android source code (not application development), then please refer to the <a href="http://source.android.com/discuss">Open Source Project Mailing lists</a>.</p>
<p style="margin-bottom:.5em"><strong>Contents</strong></p>
<ol class="toc">
- <li><a href="#BeforeYouPost">Before you post</a></li>
- <li><a href="#ApplicationDeveloperLists">Application developer mailing lists</a></li>
- <li><a href="#UsingEmail">Using email with the mailing lists</a></li>
+ <li><a href="#StackOverflow">Stack Overflow</a> <span class="new">new!</span></li>
+ <li><a href="#MailingLists">Mailing lists</a><ol>
+ <li><a href="#BeforeYouPost">Before you post</a></li>
+ <li><a href="#UsingEmail">Using email with the mailing lists</a></li>
+ <li><a href="#ApplicationDeveloperLists">Application developer mailing lists</a></li>
+ </ol></li>
+ <li><a href="#MarketHelp">Android Market Help Forum</a></li>
</ol>
-<h2 id="BeforeYouPost">Before you post</h2>
+
+<h2 id="StackOverflow">Stack Overflow</h2>
+
+<p><a href="http://stackoverflow.com">Stack Overflow</a> is a collaboratively edited question and answer site for programmers. It's a great place to ask technical questions about developing and maintaining Android applications. The site is especially useful for asking questions with definite answers, but can also be used for discussing best practices.</p>
+
+<p>On the site, questions and answers relating to Android use the <a href="http://stackoverflow.com/questions/tagged/android">'android' tag</a>. You can look for Android topics by adding '<code>[android]</code>' to your search query, or by visiting the tag page at:</p>
+
+<p style="margin-left: 2em"><a href="http://stackoverflow.com/questions/tagged/android">http://stackoverflow.com/questions/tagged/android</a></p>
+
+<p>If you want to ask a question on Stack Overflow, you can use <a href="http://stackoverflow.com/questions/ask">this form</a>. Before submitting the form, make sure to add the 'android' tag so that other Android developers will be able to find your question. As always, before submitting a new question, take a look at the existing topics to see whether another developer has already asked or answered the question.</p>
+
+<p>If you are getting started with Android development, Stack Overflow may be a great location to ask questions about general Java programming or setting up the Eclipse development environment. Simply tag your questions with the <a href="http://stackoverflow.com/questions/tagged/java">Java</a> or <a href="http://stackoverflow.com/questions/tagged/eclipse">Eclipse</a> tags in these cases.</p>
+
+
+<h2 id="MailingLists">Mailing lists</h2>
+
+<p>There are a number of mailing lists, powered by <a href="http://groups.google.com">Google Groups</a>, available for discussing Android application development.</p>
+
+
+<h3 id="BeforeYouPost">Before you post</h3>
<p>Before writing a post, please try the following:</p>
<ol>
-<li><a href="{@docRoot}resources/faq/index.html">Read the FAQs</a> The most common questions about developing Android applications are addressed in this frequently updated list.</li>
-<li><strong>Type in keywords of your questions in the main Android site's search bar</strong> (such as the one above). This search encompasses all previous discussions, across all groups, as well as the full contents of the site, documentation, and blogs. Chances are good that somebody has run into the same issue before.</li>
-<li><b>Search the mailing list archives</b> to see whether your questions have already been discussed.
+
+<li>Look through the support information available in the 'More' section of this tab. You may find the answer to your question in the <a href="{@docRoot}resources/faq/commontasks.html">Common Tasks</a>, <a href="{@docRoot}resources/faq/troubleshooting.html">Troubleshooting Tips</a>, or <a href="{@docRoot}resources/faq/index.html">FAQs</a> sections.</li>
+<li>Type in keywords of your questions in the main Android site's search bar (such as the one above). This search encompasses all previous discussions, across all groups, as well as the full contents of the site, documentation, and blogs. Chances are good that somebody has run into the same issue before.</li>
</li>
</ol>
<p>If you can't find your answer, then we encourage you to address the community.
As you write your post, please do the following:
<ol>
-<li><b>Read
-the <a href="http://sites.google.com/a/android.com/opensource/discuss/android-discussion-groups-charter">mailing list charter</a></b> that covers the community guidelines.
+<li><strong>Read
+the <a href="http://source.android.com/discuss/android-discussion-groups-charter">mailing list charter</a></strong> that covers the community guidelines.
</li>
-<li><b>Select the most appropriate mailing list for your question</b>. There are several different lists for
+<li><strong>Select the most appropriate mailing list for your question</strong>. There are several different lists for
developers, described below.</li>
<li>
- <b>Be very clear</b> about your question
+ <strong>Be very clear</strong> about your question
in the subject -- it helps everyone, both those trying to answer your
question as well as those who may be looking for information in the
future.</li>
-<li><b>Give plenty of details</b> in your post to
+<li><strong>Give plenty of details</strong> in your post to
help others understand your problem. Code or log snippets, as well as
pointers to screenshots, may also be helpful. For a great guide to
phrasing your questions, read <a href="http://www.catb.org/%7Eesr/faqs/smart-questions.html">How To Ask Questions The Smart Way</a>.
@@ -45,77 +67,48 @@
</ol>
-<h2 id="ApplicationDeveloperLists">Application developer mailing lists</h2>
-<ul>
-<li><b>Android beginners</b> - You're new to Android application development. You want to figure out how to get started with the Android SDK and the basic Android APIs? Start here. This list is open to any discussion around beginner-type questions for developers using the SDK; this is a great way to get up and running with your new application on the Android platform. Ask about getting your development environment set up, get help with the first steps of Android development (your first User Interface, your first permission, your first file on the Android filesystem, your first app on the Android Market...). Be sure to check the archives first before asking new questions. Please avoid advanced subjects, which belong on android-developers, and user questions, which will get a better reception on android-discuss.
-<ul>
-<li>Subscribe using Google Groups: <a href="http://groups.google.com/group/android-beginners">android-beginners</a></li>
-<li>Subscribe via email: <a href="mailto:android-beginners-subscribe@googlegroups.com">android-beginners-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android developers</b> - You're now an experienced Android application developer. You've grasped the basics of Android app development, you're comfortable using the SDK, now you want to move to advanced topics. Get help here with troubleshooting applications, advice on implementation, and strategies for improving your application's performance and user experience. This is the not the right place to discuss user issues (use android-discuss for that) or beginner questions with the Android SDK (use android-beginners for that).
-<ul>
-<li>Subscribe using Google Groups: <a href="http://groups.google.com/group/android-developers">android-developers</a></li>
-<li>Subscribe via email: <a href="mailto:android-developers-subscribe@googlegroups.com">android-developers-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android discuss</b> - The "water cooler" of Android discussion. You can discuss just about anything Android-related here, ideas for the Android platform, announcements about your applications, discussions about Android devices, community resources... As long as your discussion is related to Android, it's on-topic here. However, if you have a discussion here that could belong on another list, you are probably not reaching all of your target audience here and may want to consider shifting to a more targeted list.
-<ul>
-<li>Subscribe using Google Groups: <a href="http://groups.google.com/group/android-discuss">android-discuss</a></li>
-<li>Subscribe via email: <a href="mailto:android-discuss-subscribe@googlegroups.com">android-discuss-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android ndk</b> - A place for discussing the Android NDK and topics related to using native code in Android applications.
-<ul>
-<li>Subscribe using Google Groups: <a href="http://groups.google.com/group/android-ndk">android-ndk</a></li>
-<li>Subscribe via email: <a href="mailto:android-ndk-subscribe@googlegroups.com">android-ndk-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android security discuss</b> - A place for open discussion on secure development, emerging security concerns, and best practices for and by android developers. Please don't disclose vulnerabilities directly on this list, you'd be putting all Android users at risk.
-<ul>
-<li>Subscribe using Google Groups: <a href="http://groups.google.com/group/android-security-discuss">android-security-discuss</a></li>
-<li>Subscribe via email: <a href="mailto:android-security-discuss@googlegroups.com">android-security-discuss@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android security announce</b> - A low-volume group for security-related announcements by the Android Security Team.
-<ul>
-<li>Subscribe using Google Groups: <a href="http://groups.google.com/group/android-security-announce">android-security-announce</a></li>
-<li>Subscribe via email: <a href="mailto:android-security-announce-subscribe@googlegroups.com">android-security-announce-subscribe@googlegroups.com</a></li>
-</ul>
-</li>
-
-<li><b>Android Market Help Forum</b> - A web-based discussion forum where you can ask questions or report issues relating to Android Market.
-<ul>
-<li>URL: <a href="http://www.google.com/support/forum/p/Android+Market?hl=en">http://www.google.com/support/forum/p/Android+Market?hl=en</a></li>
-</ul>
-</li>
-
-</ul>
-
-
-
-<h2 id="UsingEmail">Using email with the mailing lists</h2>
+<h3 id="UsingEmail">Using email with the mailing lists</h3>
<p>Instead of using the <a href="http://groups.google.com/">Google Groups</a> site, you can use your email client of choice to participate in the mailing lists.</p>
<p>To subscribe to a group without using the Google Groups site, use the link under "subscribe via email" in the lists above.</p>
<p>To set up how you receive mailing list postings by email:</p>
<ol><li>Sign into the group via the Google Groups site. For example, for the android-framework group you would visit <a href="http://groups.google.com/group/android-framework">http://groups.google.com/group/android-framework</a>.</li>
-<li>Click "Edit
-my membership" on the right side.</li>
-<li>Under "How do
-you want to read this group?" select one of the email options. </li>
+<li>Click "Edit my membership" on the right side.</li>
+<li>Under "How do you want to read this group?" select one of the email options.</li>
</ol>
+<h3 id="ApplicationDeveloperLists">Application developer mailing lists</h3>
+<ul>
+<li><strong><a href="http://groups.google.com/group/android-developers">android-developers</a></strong>
+(<a href="mailto:android-developers-subscribe@googlegroups.com">subscribe via email</a>)<br>
+You're now an experienced Android application developer. You've grasped the basics of Android app development, you're comfortable using the SDK, now you want to move to advanced topics. Get help here with troubleshooting applications, advice on implementation, and strategies for improving your application's performance and user experience. This is the not the right place to discuss user issues (use android-discuss for that) or beginner questions with the Android SDK (use android-beginners for that).
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-discuss">android-discuss</a></strong>
+(<a href="mailto:android-discuss-subscribe@googlegroups.com">subscribe via email</a>)<br>
+The "water cooler" of Android discussion. You can discuss just about anything Android-related here, ideas for the Android platform, announcements about your applications, discussions about Android devices, community resources... As long as your discussion is related to Android, it's on-topic here. However, if you have a discussion here that could belong on another list, you are probably not reaching all of your target audience here and may want to consider shifting to a more targeted list.
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-ndk">android-ndk</a></strong>
+(<a href="mailto:android-ndk-subscribe@googlegroups.com">subscribe via email</a>)<br>
+A place for discussing the Android NDK and topics related to using native code in Android applications.
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-security-discuss">android-security-discuss</a></strong>
+(<a href="mailto:android-security-discuss-subscribe@googlegroups.com">subscribe via email</a>)<br>
+A place for open discussion on secure development, emerging security concerns, and best practices for and by android developers. Please don't disclose vulnerabilities directly on this list, you'd be putting all Android users at risk.
+</li>
+
+<li><strong><a href="http://groups.google.com/group/android-security-announce">android-security-announce</a></strong>
+(<a href="mailto:android-security-announce-subscribe@googlegroups.com">subscribe via email</a>)<br>
+A low-volume group for security-related announcements by the Android Security Team.
+</li>
+</ul>
+<h2 id="MarketHelp">Android Market Help Forum</h2>
+<p>The <a href="http://www.google.com/support/forum/p/Android+Market">Android Market Help Forum</a> is a web-based discussion forum where you can ask questions or report issues relating to Android Market.</p>
-
-
-</div>
+<p style="margin-left: 2em"><a href="http://www.google.com/support/forum/p/Android+Market">http://www.google.com/support/forum/p/Android+Market</a></p>
diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs
index f5c573e..e337e38 100644
--- a/docs/html/resources/resources_toc.cs
+++ b/docs/html/resources/resources_toc.cs
@@ -11,7 +11,7 @@
</h2>
<ul>
<li><a href="<?cs var:toroot ?>resources/community-groups.html">
- <span class="en">Android Developer Groups</span>
+ <span class="en">Developer Forums</span>
</a></li>
<li><a href="<?cs var:toroot ?>resources/community-more.html">
<span class="en">IRC, Twitter</span>
diff --git a/docs/html/resources/tutorials/views/hello-autocomplete.jd b/docs/html/resources/tutorials/views/hello-autocomplete.jd
index fba1ad8..e26683d 100644
--- a/docs/html/resources/tutorials/views/hello-autocomplete.jd
+++ b/docs/html/resources/tutorials/views/hello-autocomplete.jd
@@ -1,56 +1,82 @@
-page.title=Hello, AutoCompleteTextView
+page.title=Auto Complete
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>{@link android.widget.AutoCompleteTextView} is an implementation of the EditText widget that will provide
-auto-complete suggestions as the user types. The suggestions are extracted from a collection of strings.</p>
+<p>To create a text entry widget that provides auto-complete suggestions, use
+the {@link android.widget.AutoCompleteTextView} widget. Suggestions are received from a
+collection of strings associated with the widget through an {@link
+android.widget.ArrayAdapter}.</p>
+
+<p>In this tutorial, you will create a {@link android.widget.AutoCompleteTextView} widget that
+provides suggestions for a country name.</p>
<ol>
- <li>Start a new project/Activity called HelloAutoComplete.</li>
- <li>Open the layout file.
- Make it like so:
+ <li>Start a new project named <em>HelloAutoComplete</em>.</li>
+ <li>Create an XML file named <code>list_item.xml</code> and save it inside the
+<code>res/layout/</code> folder. Edit the file to look like this:
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="10dp"
+ android:textSize="16sp"
+ android:textColor="#000">
+</TextView>
+</pre>
+ <p>This file defines a simple {@link android.widget.TextView} that will be used for each
+item that appears in the list of suggestions.</p>
+ </li>
+ <li>Open the <code>res/layout/main.xml</code> file and insert the following:
<pre>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
- android:layout_height="wrap_content">
-
+ android:layout_height="wrap_content"
+ android:padding="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Country" />
-
- <AutoCompleteTextView android:id="@+id/edit"
+ <AutoCompleteTextView android:id="@+id/autocomplete_country"
android:layout_width="fill_parent"
- android:layout_height="wrap_content"/>
-
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"/>
</LinearLayout>
</pre>
+ <p>The {@link android.widget.TextView} is a label that introduces the {@link
+android.widget.AutoCompleteTextView} widget.
</li>
-<li>Open HelloAutoComplete.java and insert the following as the <code>onCreate</code> method:
+<li>Open <code>HelloAutoComplete.java</code> and insert the following code for the {@link
+android.app.Activity#onCreate(Bundle) onCreate()} method:
<pre>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
- AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.edit);
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
- android.R.layout.simple_dropdown_item_1line, COUNTRIES);
+ AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.autocomplete_country);
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.list_item, COUNTRIES);
textView.setAdapter(adapter);
}
</pre>
- <p>Here, we create an AutoComplteteTextView from our layout. We then
- create an {@link android.widget.ArrayAdapter} that binds a <code>simple_dropdown_item_1line</code>
- layout item to each entry in the <code>COUNTRIES</code> array (which we'll add next).
- The last part sets the ArrayAdapter to associate with our AutoCompleteTextView.</p>
+
+<p>After the content view is set to the <code>main.xml</code> layout, the {@link
+android.widget.AutoCompleteTextView} widget is captured from the layout with {@link
+android.app.Activity#findViewById(int)}. A new {@link
+android.widget.ArrayAdapter} is then initialized to bind the <code>list_item.xml</code> layout
+to each list item in the <code>COUNTRIES</code> string array (defined in the next step).
+Finally, {@link android.widget.AutoCompleteTextView#setAdapter(T) setAdapter()} is called to
+associate the {@link android.widget.ArrayAdapter} with the
+{@link android.widget.AutoCompleteTextView} widget so that the string array will populate
+the list of suggestions.</p>
</li>
-<li>After the <code>onCreate()</code> method, add the String array:
+<li>Inside the <code>HelloAutoComplete</code> class, add the string array:
<pre>
static final String[] COUNTRIES = new String[] {
"Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
@@ -96,19 +122,50 @@
"Yemen", "Yugoslavia", "Zambia", "Zimbabwe"
};
</pre>
- <p>This is the list of suggestions that will be offered as the user types into the
- AutoCompleteTextView.</p>
+<p>This is the list of suggestions that will be provided in a drop-down list when the user types into
+the {@link android.widget.AutoCompleteTextView} widget.</p>
</li>
-<li>Now run it.</li>
+<li>Run the application.</li>
</ol>
<p>As you type, you should see something like this:</p>
<img src="images/hello-autocomplete.png" width="150px" />
+<h2>More Information</h2>
+
+<p>Note that using a hard-coded string array is not a recommended design practice because your
+application code should focus on behavior, not content. Application content such as strings
+should be externalized from the code in order to make modifications to the content easier and
+facilitate localization of the content. The hard-coded strings are used in this tutorial only to
+make it simple and focus on the {@link android.widget.AutoCompleteTextView} widget.
+Instead, your application should declare such string arrays in an XML file. This can be done
+with a {@code <string-array<} resource in your project {@code res/values/strings.xml} file.
+For example:</p>
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string-array name="countries_array">
+ <item>Bahrain</item>
+ <item>Bangladesh</item>
+ <item>Barbados</item>
+ <item>Belarus</item>
+ <item>Belgium</item>
+ <item>Belize</item>
+ <item>Benin</item>
+ </string-array>
+</resources>
+</pre>
+<p>To use these resource strings for the {@link android.widget.ArrayAdapter}, replace the original
+{@link android.widget.ArrayAdapter} constructor line with the following:</p>
+<pre>
+String[] countries = getResources().getStringArray(R.array.countries_array);
+ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.list_item, countries);
+</pre>
+
+
<h3>References</h3>
<ul>
- <li>{@link android.R.layout}</li>
<li>{@link android.widget.ArrayAdapter}</li>
<li>{@link android.widget.AutoCompleteTextView}</li>
</ul>
diff --git a/docs/html/resources/tutorials/views/hello-datepicker.jd b/docs/html/resources/tutorials/views/hello-datepicker.jd
index fcd43f3..c50d650 100644
--- a/docs/html/resources/tutorials/views/hello-datepicker.jd
+++ b/docs/html/resources/tutorials/views/hello-datepicker.jd
@@ -1,52 +1,57 @@
-page.title=Hello, DatePicker
+page.title=Date Picker
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>A {@link android.widget.DatePicker} is a widget that allows the user to select a month, day and year.</p>
+<p>To provide a widget for selecting a date, use the {@link android.widget.DatePicker}
+widget, which allows the user to select the month, day, and year, in a familiar interface.</p>
+<p>In this tutorial, you'll create a {@link android.app.DatePickerDialog}, which presents the
+date picker in a floating dialog box at the press of a button. When the date is set by
+the user, a {@link android.widget.TextView} will update with the new date.</p>
<ol>
- <li>Start a new project/Activity called HelloDatePicker.</li>
- <li>Open the layout file and make it like so:
- <pre>
+ <li>Start a new project named <em>HelloDatePicker</em>.</li>
+ <li>Open the <code>res/layout/main.xml</code> file and insert the following:
+<pre>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
-
<TextView android:id="@+id/dateDisplay"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text=""/>
-
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text=""/>
<Button android:id="@+id/pickDate"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Change the date"/>
-
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Change the date"/>
</LinearLayout>
</pre>
- <p>For the layout, we're using a vertical LinearLayout, with a {@link android.widget.TextView} that
- will display the date and a {@link android.widget.Button} that will initiate the DatePicker dialog.
- With this layout, the TextView will sit above the Button.
- The text value in the TextView is set empty, as it will be filled
- with the current date when our Activity runs.</p>
- </li>
+ <p>This creates a basic {@link android.widget.LinearLayout} with a {@link android.widget.TextView}
+ that will display the date and a {@link android.widget.Button} that will open the {@link
+ android.app.DatePickerDialog}.</p>
+ </li>
- <li>Open HelloDatePicker.java. Insert the following to the HelloDatePicker class:
+ <li>Open <code>HelloDatePicker.java</code> and add the following members to the class:
<pre>
private TextView mDateDisplay;
private Button mPickDate;
-
private int mYear;
private int mMonth;
private int mDay;
static final int DATE_DIALOG_ID = 0;
+</pre>
+ <p>The first group of members define variables for the layout {@link android.view.View}s and the
+date items. The <code>DATE_DIALOG_ID</code> is a static integer that uniquely identifies the {@link
+android.app.Dialog} that will display the date picker.</p>
+ </li>
- @Override
+ <li>Now add the following code for the {@link android.app.Activity#onCreate(Bundle) onCreate()}
+method:
+<pre>
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
@@ -68,43 +73,30 @@
mMonth = c.get(Calendar.MONTH);
mDay = c.get(Calendar.DAY_OF_MONTH);
- // display the current date
+ // display the current date (this method is below)
updateDisplay();
}
</pre>
-<p class="note"><strong>Tip:</strong> Press Ctrl(or Cmd) + Shift + O to import all needed packages.</p>
- <p>We start by instantiating variables for our Views and date fields.
- The <code>DATE_DIALOG_ID</code> is a static integer that uniquely identifies the Dialog. In the
- <code>onCreate()</code> method, we get prepared by setting the layout and capturing the View elements.
- Then we create an on-click listener for the Button, so that when it is clicked it will
- show our DatePicker dialog. The <code>showDialog()</code> method will pop-up the date picker dialog
- by calling the <code>onCreateDialog()</code> callback method
- (which we'll define in the next section). We then create an
- instance of {@link java.util.Calendar} and get the current year, month and day. Finally, we call
- <code>updateDisplay()</code>—our own method (defined later) that will fill the TextView.</p>
+
+<p>First, the content is set to the <code>main.xml</code> layout. Then the {@link
+android.widget.TextView} and {@link android.widget.Button} elements are captured from the layout
+with {@link android.app.Activity#findViewById(int)}. A
+new {@link android.view.View.OnClickListener} is created for the
+{@link android.widget.Button}, so that when it is clicked, it
+will call {@link android.app.Activity#showDialog(int)}, passing the unique integer ID for
+the date picker dialog. Using {@link android.app.Activity#showDialog(int)} allows the {@link
+android.app.Activity} to manage the life-cycle of the dialog and will call the {@link
+android.app.Activity#onCreateDialog(int)} callback method to request the {@link android.app.Dialog}
+that should be displayed (which you'll
+define later). After the on-click listener is set, a new {@link java.util.Calendar} is created
+and the current year, month and day are acquired. Finally, the private
+<code>updateDisplay()</code> method is called in order to fill the {@link android.widget.TextView}
+with the current date.</p>
</li>
-<li>After the <code>onCreate()</code> method, add the <code>onCreateDialog()</code> callback method
-(which is called by <code>showDialog()</code>)
+<li>Add the <code>updateDisplay()</code> method:
<pre>
-@Override
-protected Dialog onCreateDialog(int id) {
- switch (id) {
- case DATE_DIALOG_ID:
- return new DatePickerDialog(this,
- mDateSetListener,
- mYear, mMonth, mDay);
- }
- return null;
-}
-</pre>
- <p>This method is passed the identifier we gave <code>showDialog()</code> and initializes
- the DatePicker to the date we retrieved from our Calendar instance.</p>
-</li>
-
-<li>Following that, add the <code>updateDisplay()</code> method:
-<pre>
- // updates the date we display in the TextView
+ // updates the date in the TextView
private void updateDisplay() {
mDateDisplay.setText(
new StringBuilder()
@@ -114,9 +106,13 @@
.append(mYear).append(" "));
}
</pre>
-<p>This uses the member date values to write the date to our TextView.</p>
+<p>This method uses the member date values declared for the class to write the date to the layout's
+{@link android.widget.TextView}, {@code mDateDisplay}, which was also declared and initialized
+above.</p>
</li>
-<li>Finally, add a listener that will be called when the user sets a new date:
+
+<li>Initialize a new {@link android.app.DatePickerDialog.OnDateSetListener} as a member of the
+<code>HelloDatePicker</code> class:
<pre>
// the callback received when the user "sets" the date in the dialog
private DatePickerDialog.OnDateSetListener mDateSetListener =
@@ -131,19 +127,45 @@
}
};
</pre>
- <p>This <code>OnDateSetListener</code> method listens for when the user is done setting the date
- (clicks the "Set" button). At that time, this fires and we update our member fields with
- the new date defined by the user and update our TextView by calling <code>updateDisplay()</code>.</p>
+<p>The {@link android.app.DatePickerDialog.OnDateSetListener} listens for when the user
+has set the date (by clicking the "Set" button). At that time, the {@link
+android.app.DatePickerDialog.OnDateSetListener#onDateSet(DatePicker,int,int,int) onDateSet()}
+callback method is called, which is defined to update the {@code mYear}, {@code mMonth}, and
+{@code mDay} member fields with the new date then call the private <code>updateDisplay()</code>
+method to update the {@link android.widget.TextView}.</p>
</li>
-<li>Now run it.</li>
+<li>Now add the {@link android.app.Activity#onCreateDialog(int)} callback method to the {@code
+HelloDatePicker} class:
+<pre>
+@Override
+protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case DATE_DIALOG_ID:
+ return new DatePickerDialog(this,
+ mDateSetListener,
+ mYear, mMonth, mDay);
+ }
+ return null;
+}
+</pre>
+<p>This is an {@link android.app.Activity} callback method that is passed the integer ID given to
+{@link android.app.Activity#showDialog(int)} (which is called by the button's {@link
+android.view.View.OnClickListener}). When the ID matches the switch case defined here, a {@link
+android.app.DatePickerDialog} is instantiated with the {@link
+android.app.DatePickerDialog.OnDateSetListener} created in the previous
+step, along with the date variables to initialize the widget date.</p>
+</li>
+
+<li>Run the application.</li>
</ol>
<p>When you press the "Change the date" button, you should see the following:</p>
<img src="images/hello-datepicker.png" width="150px" />
<h3>References</h3>
<ul>
-<li>{@link android.widget.DatePicker}</li>
+<li>{@link android.app.DatePickerDialog}</li>
+<li>{@link android.app.DatePickerDialog.OnDateSetListener}</li>
<li>{@link android.widget.Button}</li>
<li>{@link android.widget.TextView}</li>
<li>{@link java.util.Calendar}</li>
diff --git a/docs/html/resources/tutorials/views/hello-formstuff.jd b/docs/html/resources/tutorials/views/hello-formstuff.jd
index da4289c..3dd5f21 100644
--- a/docs/html/resources/tutorials/views/hello-formstuff.jd
+++ b/docs/html/resources/tutorials/views/hello-formstuff.jd
@@ -1,58 +1,99 @@
-page.title=Hello, Form Stuff
+page.title=Form Stuff
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>This page introduces a variety of widgets, like image buttons,
-text fields, checkboxes and radio buttons.</p>
+<p>This tutorial introduces a variety of widgets that are useful when creating forms, such as
+image buttons, text fields, checkboxes and radio buttons.</p>
<ol>
- <li>Start a new project/Activity called HelloFormStuff.</li>
- <li>Your layout file should have a basic LinearLayout:
- <pre>
+ <li>Start a new project named <em>HelloFormStuff</em>.</li>
+ <li>Your <code>res/layout/main.xml</code> file should already have a basic {@link
+android.widget.LinearLayout}:
+<pre>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
-
</LinearLayout>
</pre>
- <p>For each widget you want to add, just put the respective View inside here.</p>
-</li>
+ <p>For each widget you want to add, just put the respective View inside this {@link
+android.widget.LinearLayout}.</p>
+ </li>
</ol>
-<p class="note"><strong>Tip:</strong> As you add new Android code, press Ctrl(or Cmd) + Shift + O
-to import all needed packages.</p>
-
-
-<h2>ImageButton</h2>
-<p>A button with a custom image on it.
-We'll make it display a message when pressed.</p>
-<ol>
- <li><img src="images/android.png" align="right"/>
- Drag the Android image on the right (or your own image) into the
- res/drawable/ directory of your project.
- We'll use this for the button.</li>
- <li>Open the layout file and, inside the LinearLayout, add the {@link android.widget.ImageButton} element:
+<p>Each section below also assumes that your <code>HelloFormStuff</code> Activity has the following
+default implementation of the {@link android.app.Activity#onCreate(Bundle) onCreate()} method:</p>
<pre>
-<ImageButton
- android:id="@+id/android_button"
- android:layout_width="100dip"
- android:layout_height="wrap_content"
- android:src="@drawable/android" />
+public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+}
</pre>
- <p>The source of the button
- is from the res/drawable/ directory, where we've placed the android.png.</p>
- <p class="note"><strong>Tip:</strong> You can also reference some of the many built-in
- images from the Android {@link android.R.drawable} resources,
- like <code>ic_media_play</code>, for a "play" button image. To do so, change the source
- attribute to <code>android:src="@android:drawable/ic_media_play"</code>.</p>
-</li>
-<li>To make the button to actually do something, add the following
-code at the end of the <code>onCreate()</code> method:
+
+
+
+<h2>Custom Button</h2>
+
+<p>In this section, you will create a button with a custom image instead of text, using the {@link
+android.widget.Button} widget and an XML file that defines three different images to use for the
+different button states. When the button is pressed, a short message will be displayed.</p>
+
+<img src="images/android_pressed.png" style="float:right;" title="android_pressed.png"/>
+<img src="images/android_focused.png" style="float:right;clear:right;" title="android_focused.png"/>
+<img src="images/android_normal.png" style="float:right;clear:right;" title="android_normal.png"/>
+<ol>
+ <li>Copy the images on the right into the <code>res/drawable/</code> directory of
+your project. These will be used for the different button states.</li>
+ <li>Create a new file in the <code>res/drawable/</code> directory named
+<code>android_button.xml</code>.
+Insert the following XML:
<pre>
-final ImageButton button = (ImageButton) findViewById(R.id.android_button);
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/android_pressed"
+ android:state_pressed="true" />
+ <item android:drawable="@drawable/android_focused"
+ android:state_focused="true" />
+ <item android:drawable="@drawable/android_normal" />
+</selector>
+</pre>
+ <p>This defines a single drawable resource, which will change its image based on the current
+state of the button. The first <code><item></code> defines
+<code>android_pressed.png</code> as the image when the button is pressed (it's been
+activated); the second <code><item></code> defines <code>android_focused.png</code> as the image
+when the button is focused (when the button is highlighted using the trackball or directional
+pad); and the third <code><item></code> defines <code>android_normal.png</code> as the image
+for the normal state (when neither pressed nor focused). This XML file now represents a single
+drawable resource and when referenced by a {@link android.widget.Button} for its background,
+the image displayed will change based on these three states.</p>
+ <p class="note"><strong>Note:</strong> The order of the <code><item></code> elements is
+important. When this drawable is referenced, the <code><item></code>s are traversed in-order to
+determine which one is appropriate for the current button state. Because the "normal" image is last,
+it is only applied when the conditions <code>android:state_pressed</code> and
+<code>android:state_focused</code> have both evaluated false.</li>
+ <li>Open the <code>res/layout/main.xml</code> file and add the {@link
+android.widget.Button} element:
+<pre>
+ <Button
+ android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="10dp"
+ android:background="@drawable/android_button" />
+</pre>
+ <p>The <code>android:background</code> attribute specifies the drawable resource to use for the
+button background (which, when saved at <code>res/drawable/android.xml</code>, is
+referenced as <code>@drawable/android</code>). This replaces the normal background image
+used for buttons throughout the system. In order for the drawable to change its image based on
+the button state, the image must be applied to the background.</p>
+</li>
+
+<li>To make the button do something when pressed, add the following
+code at the end of the {@link android.app.Activity#onCreate(Bundle) onCreate()} method:
+<pre>
+final Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// Perform action on clicks
@@ -60,34 +101,41 @@
}
});
</pre>
-<p>This captures our ImageButton from the layout, then adds an on-click listener to it.
-The {@link android.view.View.OnClickListener} must define the <code>onClick()</code> method, which
-defines the action to be made when the button is clicked. Here, we show a
-{@link android.widget.Toast} message when clicked.</p>
+<p>This captures the {@link android.widget.Button} from the layout, then adds an {@link
+android.view.View.OnClickListener}. The {@link android.view.View.OnClickListener}
+must implement the {@link android.view.View.OnClickListener#onClick(View)} callback method, which
+defines the action to be made when the button is clicked. In this example, a
+{@link android.widget.Toast} message will be displayed.</p>
</li>
-<li>Run it.</li>
+<li>Now run the application.</li>
</ol>
<h2>EditText</h2>
-<p>A text field for user input. We'll make it display the text entered so far when the "Enter" key is pressed.</p>
+
+<p>In this section, you will create a text field for user input, using the {@link
+android.widget.EditText} widget. Once text has been entered into the field, the "Enter" key will
+display the text in a toast message.</p>
<ol>
- <li>Open the layout file and, inside the LinearLayout, add the {@link android.widget.EditText} element:
+ <li>Open the <code>res/layout/main.xml</code> file and add the {@link android.widget.EditText}
+element (inside the {@link android.widget.LinearLayout}):
<pre>
-<EditText
- android:id="@+id/edittext"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"/>
+ <EditText
+ android:id="@+id/edittext"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
</pre>
</li>
-<li>To do something with the text that the user enters, add the following code
-to the end of the <code>onCreate()</code> method:
+<li>To do something with the text that the user types, add the following code
+to the end of the {@link android.app.Activity#onCreate(Bundle) onCreate()} method:
<pre>
final EditText edittext = (EditText) findViewById(R.id.edittext);
edittext.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
- if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+ // If the event is a key-down event on the "enter" button
+ if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
+ (keyCode == KeyEvent.KEYCODE_ENTER)) {
// Perform action on key press
Toast.makeText(HelloFormStuff.this, edittext.getText(), Toast.LENGTH_SHORT).show();
return true;
@@ -96,37 +144,44 @@
}
});
</pre>
-<p>This captures our EditText element from the layout, then adds an on-key listener to it.
-The {@link android.view.View.OnKeyListener} must define the <code>onKey()</code> method, which
-defines the action to be made when a key is pressed. In this case, we want to listen for the
-Enter key (when pressed down), then pop up a {@link android.widget.Toast} message with the
-text from the EditText field. Be sure to return <var>true</var> after the event is handled,
-so that the event doesn't bubble-up and get handled by the View (which would result in a
-carriage return in the text field).</p>
-<li>Run it.</li>
+<p>This captures the {@link android.widget.EditText} element from the layout and adds an {@link
+android.view.View.OnKeyListener}. The {@link android.view.View.OnKeyListener} must implement the
+{@link android.view.View.OnKeyListener#onKey(View,int,KeyEvent)} method, which
+defines the action to be made when a key is pressed while the widget has focus. In this case, the
+method is defined to listen for the Enter key (when pressed down), then pop up a {@link
+android.widget.Toast} message with the text that has been entered. The {@link
+android.view.View.OnKeyListener#onKey(View,int,KeyEvent)} method should always return
+<code>true</code> if the event has been handled, so that the event doesn't bubble-up (which would
+result in a carriage return in the text field).</p>
+</li>
+<li>Run the application.</li>
</ol>
<h2>CheckBox</h2>
-<p>A checkbox for selecting items. We'll make it display the the current state when pressed.</p>
+
+<p>In this section, you will create a checkbox for selecting items, using the {@link
+android.widget.CheckBox} widget. When the checkbox is pressed, a toast message will
+indicate the current state of the checkbox.</p>
<ol>
- <li>Open the layout file and, inside the LinearLayout, add the {@link android.widget.CheckBox} element:
+ <li>Open the <code>res/layout/main.xml</code> file and add the {@link android.widget.CheckBox}
+element (inside the {@link android.widget.LinearLayout}):
<pre>
-<CheckBox android:id="@+id/checkbox"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="check it out" />
+ <CheckBox android:id="@+id/checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="check it out" />
</pre>
</li>
<li>To do something when the state is changed, add the following code
-to the end of the <code>onCreate()</code> method:
+to the end of the {@link android.app.Activity#onCreate(Bundle) onCreate()} method:
<pre>
final CheckBox checkbox = (CheckBox) findViewById(R.id.checkbox);
checkbox.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
- // Perform action on clicks
- if (checkbox.isChecked()) {
+ // Perform action on clicks, depending on whether it's now checked
+ if (((CheckBox) v).isChecked()) {
Toast.makeText(HelloFormStuff.this, "Selected", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(HelloFormStuff.this, "Not selected", Toast.LENGTH_SHORT).show();
@@ -134,52 +189,62 @@
}
});
</pre>
-<p>This captures our CheckBox element from the layout, then adds an on-click listener to it.
-The {@link android.view.View.OnClickListener} must define the <code>onClick()</code> method, which
-defines the action to be made when the checkbox is clicked. Here, we query the current state of the
-checkbox, then pop up a {@link android.widget.Toast} message that displays the current state.
-Notice that the CheckBox handles its own state change between checked and un-checked, so we just
-ask which it currently is.</p>
+<p>This captures the {@link android.widget.CheckBox} element from the layout, then adds an {@link
+android.view.View.OnClickListener}. The {@link android.view.View.OnClickListener} must implement the
+{@link android.view.View.OnClickListener#onClick(View)} callback method, which
+defines the action to be made when the checkbox is clicked. When clicked, {@link
+android.widget.CompoundButton#isChecked()} is called to check the new state of the check box. If it
+has been checked, then a {@link android.widget.Toast} displays the message "Selected", otherwise it
+displays "Not selected". Note that the {@link android.view.View} object that is passed in the {@link
+android.view.View.OnClickListener#onClick(View)} callback must be cast to a {@link
+android.widget.CheckBox} because the {@link android.widget.CompoundButton#isChecked()} method is
+not defined by the parent {@link android.view.View} class. The {@link android.widget.CheckBox}
+handles its own state changes, so you only need to query the current state.</p>
+</li>
<li>Run it.</li>
</ol>
-<p class="note"><strong>Tip:</strong> If you find that you need to change the state
-in another way (such as when loading a saved {@link android.preference.CheckBoxPreference}),
-use <code>setChecked(true)</code> or <code>toggle()</code>.</p>
+<p class="note"><strong>Tip:</strong> If you need to change the state
+yourself (such as when loading a saved {@link android.preference.CheckBoxPreference}),
+use the {@link android.widget.CompoundButton#setChecked(boolean)} or {@link
+android.widget.CompoundButton#toggle()} method.</p>
<h2>RadioButton</h2>
-<p>Two mutually-exclusive radio buttons—enabling one disables the other.
-When each is pressed, we'll pop up a message.</p>
+
+<p>In this section, you will create two mutually-exclusive radio buttons (enabling one disables
+the other), using the {@link android.widget.RadioGroup} and {@link android.widget.RadioButton}
+widgets. When either radio button is pressed, a toast message will be displayed.</p>
<ol>
- <li>Open the layout file and, inside the LinearLayout, add two {@link android.widget.RadioButton}s,
-inside a {@link android.widget.RadioGroup}:
+ <li>Open the <code>res/layout/main.xml</code> file and add two {@link
+android.widget.RadioButton}s, nested in a {@link android.widget.RadioGroup} (inside the {@link
+android.widget.LinearLayout}):
<pre>
-<RadioGroup
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <RadioButton android:id="@+id/radio_red"
- android:layout_width="wrap_content"
+ <RadioGroup
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:text="Red" />
-
- <RadioButton android:id="@+id/radio_blue"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Blue" />
-
-</RadioGroup>
+ android:orientation="vertical">
+ <RadioButton android:id="@+id/radio_red"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Red" />
+ <RadioButton android:id="@+id/radio_blue"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Blue" />
+ </RadioGroup>
</pre>
+<p>It's important that the {@link android.widget.RadioButton}s are grouped together by the {@link
+android.widget.RadioGroup} element so that no more than one can be selected at a time. This logic
+is automatically handled by the Android system. When one {@link android.widget.RadioButton} within
+a group is selected, all others are automatically deselected.</p>
</li>
-<li>To do something when each is selected, we'll need an OnClickListener. Unlike the other
-listeners we've created, instead of creating this one as an anonymous inner class,
-we'll create it as a new object. This way, we can re-use the OnClickLIstener for
-both RadioButtons. So, add the following code in the HelloFormStuff Activity
-(<em>outside</em> the <code>onCreate()</code> method):
+
+<li>To do something when each {@link android.widget.RadioButton} is selected, you need an
+{@link android.view.View.OnClickListener}. In this case, you want the listener to be re-usable, so
+add the following code to create a new member in the <code>HelloFormStuff</code> Activity:
<pre>
-OnClickListener radio_listener = new OnClickListener() {
+private OnClickListener radio_listener = new OnClickListener() {
public void onClick(View v) {
// Perform action on clicks
RadioButton rb = (RadioButton) v;
@@ -187,68 +252,124 @@
}
};
</pre>
-<p>Our <code>onClick()</code> method will be handed the View clicked, so the first thing to do
-is cast it into a RadioButton. Then we pop up a
-{@link android.widget.Toast} message that displays the selection.</p>
-<li>Now, at the bottom of the <code>onCreate()</code> method, add the following:
+<p>First, the {@link android.view.View} that is passed to the {@link
+android.view.View.OnClickListener#onClick(View)} method is cast into a RadioButton. Then a
+{@link android.widget.Toast} message displays the selected radio button's text.</p>
+<li>Now, at the bottom of the {@link android.app.Activity#onCreate(Bundle) onCreate()} method, add
+the following:
<pre>
final RadioButton radio_red = (RadioButton) findViewById(R.id.radio_red);
final RadioButton radio_blue = (RadioButton) findViewById(R.id.radio_blue);
radio_red.setOnClickListener(radio_listener);
radio_blue.setOnClickListener(radio_listener);
</pre>
-<p>This captures each of the RadioButtons from our layout and adds the newly-created
-OnClickListener to each.</p>
-<li>Run it.</li>
+<p>This captures each of the {@link android.widget.RadioButton}s from the layout and adds the
+newly-created {@link android.view.View.OnClickListener} to each.</p>
+<li>Run the application.</li>
</ol>
-<p class="note"><strong>Tip:</strong> If you find that you need to change the state of a
-RadioButton in another way (such as when loading a saved {@link android.preference.CheckBoxPreference}),
-use <code>setChecked(true)</code> or <code>toggle()</code>.</p>
+
+<p class="note"><strong>Tip:</strong> If you need to change the state
+yourself (such as when loading a saved {@link android.preference.CheckBoxPreference}),
+use the {@link android.widget.CompoundButton#setChecked(boolean)} or {@link
+android.widget.CompoundButton#toggle()} method.</p>
<h2>ToggleButton</h2>
-<p>A button used specifically for toggling something on and off.</p>
+
+<p>In this section, you'll create a button used specifically for toggling between two
+states, using the {@link android.widget.ToggleButton} widget. This widget is an excellent
+alternative to radio buttons if you have two simple states that are mutually exclusive ("on" and
+"off", for example).</p>
<ol>
- <li>Open the layout file and, inside the LinearLayout, add the {@link android.widget.ToggleButton} element:
+ <li>Open the <code>res/layout/main.xml</code> file and add the {@link android.widget.ToggleButton}
+element (inside the {@link android.widget.LinearLayout}):
<pre>
-<ToggleButton android:id="@+id/togglebutton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ <ToggleButton android:id="@+id/togglebutton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textOn="Vibrate on"
+ android:textOff="Vibrate off"/>
</pre>
+ <p>The attributes <code>android:textOn</code> and <code>android:textOff</code> specify the text
+for the button when the button has been toggled on or off. The default values are "ON" and
+"OFF".</p>
</li>
<li>To do something when the state is changed, add the following code
-to the end of the <code>onCreate()</code> method:
+to the end of the {@link android.app.Activity#onCreate(Bundle) onCreate()} method:
<pre>
final ToggleButton togglebutton = (ToggleButton) findViewById(R.id.togglebutton);
togglebutton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// Perform action on clicks
if (togglebutton.isChecked()) {
- Toast.makeText(HelloFormStuff.this, "ON", Toast.LENGTH_SHORT).show();
+ Toast.makeText(HelloFormStuff.this, "Checked", Toast.LENGTH_SHORT).show();
} else {
- Toast.makeText(HelloFormStuff.this, "OFF", Toast.LENGTH_SHORT).show();
+ Toast.makeText(HelloFormStuff.this, "Not checked", Toast.LENGTH_SHORT).show();
}
}
});
</pre>
-<p>This captures our ToggleButton element from the layout, then adds an on-click listener to it.
-The {@link android.view.View.OnClickListener} must define the <code>onClick()</code> method, which
-defines the action to be made when the button is clicked. Here, we query the current state of the
-ToggleButton, then pop up a {@link android.widget.Toast} message that displays the current state.
-Notice that the ToggleButton handles its own state change between checked and un-checked, so we just
-ask which it is.</p>
-<li>Run it.</li>
+<p>This captures the {@link android.widget.ToggleButton} element from the layout, then adds an
+{@link android.view.View.OnClickListener}. The {@link android.view.View.OnClickListener} must
+implement the {@link android.view.View.OnClickListener#onClick(View)} callback method, which
+defines the action to perform when the button is clicked. In this example, the callback
+method checks the new state of the button, then shows a {@link android.widget.Toast} message that
+indicates the current state.</p>
+
+<p>Notice that the {@link android.widget.ToggleButton} handles its own state change between checked
+and unchecked, so you just ask which it is.</p>
+<li>Run the application.</li>
</ol>
-<p class="note"><strong>Tip:</strong> By default, the text on the button is "ON" and "OFF", but
-you can change each of these with <code>setTextOn(<var>CharSequence</var>)</code> and
-<code>setTextOff(<var>CharSequence</var>)</code>. And, if you find that you need to change the state
-in another way (such as when loading a saved {@link android.preference.CheckBoxPreference}),
-use <code>setChecked(true)</code> or <code>toggle()</code>. </p>
+<p class="note"><strong>Tip:</strong> If you need to change the state
+yourself (such as when loading a saved {@link android.preference.CheckBoxPreference}),
+use the {@link android.widget.CompoundButton#setChecked(boolean)} or {@link
+android.widget.CompoundButton#toggle()} method.</p>
-<p>If you've added all the form items above, your application should look something like this:</p>
+
+
+<h2>RatingBar</h2>
+
+<p>In this section, you'll create a widget that allows the user to provide a rating,
+with the {@link android.widget.RatingBar} widget.</p>
+
+<ol>
+ <li>Open the <code>res/layout/main.xml</code> file and add the {@link android.widget.RatingBar}
+element (inside the {@link android.widget.LinearLayout}):
+<pre>
+ <RatingBar android:id="@+id/ratingbar"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:numStars="5"
+ android:stepSize="1.0"/>
+</pre>
+ <p>The <code>android:numStars</code> attribute defines how many stars to display for the rating
+bar. The <code>android:stepSize</code> attribute defines the granularity for each
+star (for example, a value of <code>0.5</code> would allow half-star ratings). </p>
+</li>
+<li>To do something when a new rating has been set, add the following code
+to the end of the {@link android.app.Activity#onCreate(Bundle) onCreate()} method:
+<pre>
+final RatingBar ratingbar = (RatingBar) findViewById(R.id.ratingbar);
+ratingbar.setOnRatingBarChangeListener(new OnRatingBarChangeListener() {
+ public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
+ Toast.makeText(HelloFormStuff.this, "New Rating: " + rating, Toast.LENGTH_SHORT).show();
+ }
+});
+</pre>
+<p>This captures the {@link android.widget.RatingBar} widget from the layout with {@link
+android.app.Activity#findViewById(int)} and then sets an {@link
+android.widget.RatingBar.OnRatingBarChangeListener}. The {@link
+android.widget.RatingBar.OnRatingBarChangeListener#onRatingChanged(RatingBar,float,boolean)
+onRatingChanged()} callback method then defines the action to perform when the user sets a rating.
+In this case, a simple {@link android.widget.Toast} message displays the new rating.</p>
+
+<li>Run the application.</li>
+</ol>
+
+<p>If you've added all the form widgets above, your application should look like this:</p>
<img src="images/hello-formstuff.png" width="150px" />
<h3>References</h3>
@@ -258,5 +379,6 @@
<li>{@link android.widget.CheckBox}</li>
<li>{@link android.widget.RadioButton}</li>
<li>{@link android.widget.ToggleButton}</li>
+ <li>{@link android.widget.RatingBar}</li>
</ul>
diff --git a/docs/html/resources/tutorials/views/hello-gallery.jd b/docs/html/resources/tutorials/views/hello-gallery.jd
index 084f912..12d5a91 100644
--- a/docs/html/resources/tutorials/views/hello-gallery.jd
+++ b/docs/html/resources/tutorials/views/hello-gallery.jd
@@ -1,16 +1,21 @@
-page.title=Hello, Gallery
+page.title=Gallery
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>A {@link android.widget.Gallery} is a View commonly used to display items in a horizontally scrolling list
-that locks the current selection at the center. When one is selected, we'll show a message.</p>
+<p>{@link android.widget.Gallery} is a layout widget used to display items in a
+horizontally scrolling list and positions the current selection at the center of the view.</p>
+
+<p>In this tutorial, you'll create a gallery of photos and then display a toast message each time a
+gallery item is selected.</p>
<ol>
- <li>Start a new project/Activity called HelloGallery.</li>
- <li>Add some images to your res/drawable/ directory.</li>
- <li>Open the layout file and make it like so:
+ <li>Start a new project named <em>HelloGallery</em>.</li>
+ <li>Find some photos you'd like to use, or use these <a
+href="{@docRoot}shareables/sample_images.zip">sample images</a>. Save the images into the project's
+<code>res/drawable/</code> directory.</li>
+ <li>Open the <code>res/layout/main.xml</code> file and insert the following:
<pre>
<?xml version="1.0" encoding="utf-8"?>
<Gallery xmlns:android="http://schemas.android.com/apk/res/android"
@@ -19,10 +24,10 @@
android:layout_height="wrap_content"
/>
</pre>
-</li>
+ </li>
-
-<li>Open the HelloGallery.java file. Insert the following for the <code>onCreate()</code> method:
+<li>Open the <code>HelloGallery.java</code> file and insert the following code for the
+{@link android.app.Activity#onCreate(Bundle) onCreate()} method:
<pre>
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -39,19 +44,46 @@
});
}
</pre>
- <p>We start as usual: set the layout and capture the View we want (our Gallery).
-We then set an Adapter, called ImageAdapter for the Gallery—this is a new class that
-we'll create next. Then we create an item click listener for the Gallery. This is like a normal
-on-click listener (which you might be familiar with for buttons), but it listens to each item
-that we've added to the Gallery. The <code>onItemClick()</code> callback method
-receives the AdapterView where the click occurred, the specific View that received the click, the
-position of the View clicked (zero-based), and the row id of the item clicked (if applicable). All
-that we care about is the position, so that we can pop up a {@link android.widget.Toast} message that
-tells us the index position of the item clicked. We do this with <code>Toast.makeText().show()</code>.
+ <p>This starts by setting the {@code main.xml} layout as the content view and then capturing the
+{@link android.widget.Gallery} from
+the layout with {@link
+android.app.Activity#findViewById(int)}. A custom {@link android.widget.BaseAdapter} called
+<code>ImageAdapter</code> is
+instantiated and applied to the {@link android.widget.Gallery} with {@link
+android.widget.AdapterView#setAdapter(T) setAdapter()}. (The <code>ImageAdapter</code> class is
+defined next.)
+Then an anonymous {@link android.widget.AdapterView.OnItemClickListener} is instantiated. The
+{@link android.widget.AdapterView.OnItemClickListener#onItemClick(AdapterView,View,int,long)}
+callback method receives the {@link android.widget.AdapterView} where the click occurred, the
+specific {@link android.view.View} that received the click, the
+position of the {@link android.view.View} clicked (zero-based), and the row ID of the item clicked
+(if applicable). In this example, all that's needed is the position of the click to show a {@link
+android.widget.Toast} message that says the position of the item, using
+{@link android.widget.Toast#makeText(Context,CharSequence,int)} and {@link
+android.widget.Toast#show()} (in a real world scenario, this ID could be used to get the full sized
+image for some other task).
</p>
</li>
+ <li>Create a new XML file in the <code>res/values/</code> directory named <code>attrs.xml</code>.
+Insert the following:
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <declare-styleable name="HelloGallery">
+ <attr name="android:galleryItemBackground" />
+ </declare-styleable>
+</resources>
+</pre>
+ <p>This is a custom styleable resource that can be applied to a layout. In this case, it will be
+applied to the individual items placed into the {@link android.widget.Gallery} widget. The
+<code><attr></code> element defines a specific attribute for the styleable, and in this case, it
+refers to an existing platform attribute, {@link android.R.attr#galleryItemBackground}, which
+defines a border styling for gallery items. In the next step, you'll
+see how this attribute is referenced and then later applied to each item in the gallery.</p>
+ </li>
-<li>After the <code>onCreate()</code> method, add the <code>ImageAdapter</code> class:
+ <li>Go back to the <code>HelloGallery.java</code> file. After the {@link
+ android.app.Activity#onCreate(Bundle)} method, define the custom <code>ImageAdapter</code> class:
<pre>
public class ImageAdapter extends BaseAdapter {
int mGalleryItemBackground;
@@ -100,26 +132,30 @@
}
</pre>
<p>First, there are a few member variables, including an array of IDs that reference
-the images we placed in our drawable resources directory.</p>
-<p>Next is the constructor, where we define the member Context. The rest of the constructor
-sets up a reference for our Gallery them, which adds the nice framing for each Gallery item.
-Once we have our <code>mGalleryItemBackground</code>, it's important to recycle the
-StyledAttribute for later re-use.</p>
-<p>The next three methods are required for basic member queries.
-But then we have the <code>getView()</code> method, which is called
-for each item read by our ImageAdapter, when the Gallery is being built. Here, we
-use our member Context to create a new {@link android.widget.ImageView}. We then define
-the image resource with the current position of the Gallery items (corresponding to our
-array of drawables), set the dimensions for the ImageView,
-set the image scaling to fit the ImageView dimensions, then finally set the
-background theme for the ImageView.</p>
+the images saved in the drawable resources directory ({@code res/drawable/}).</p>
+<p>Next is the class constructor, where the {@link android.content.Context} for an {@code
+ImageAdapter} instance is defined and the styleable
+resource defined in the last step is acquired and saved to a local field. At the end of the
+constructor, {@link android.content.res.TypedArray#recycle()} is called on the {@link
+android.content.res.TypedArray} so it can be re-used by the system.</p>
+<p>The methods {@link android.widget.Adapter#getCount()}, {@link
+android.widget.Adapter#getItem(int)}, and {@link android.widget.Adapter#getItemId(int)} are methods
+that must be implemented for simple queries on the {@link android.widget.Adapter}.
+The {@link android.widget.Adapter#getView(int,View,ViewGroup) method does the
+work to apply an image to an {@link android.widget.ImageView} that will be embedded in the
+{@link android.widget.Gallery}. In this method, the member {@link android.content.Context} is used
+to create a new {@link android.widget.ImageView}. The {@link android.widget.ImageView} is prepared
+by applying an image from the local array of drawable resources, setting the {@link
+android.widget.Gallery.LayoutParams} height and width for the image, setting the scale to fit the
+{@link android.widget.ImageView} dimensions, and then finally setting the background to use the
+styleable attribute acquired in the constructor.</p>
-<p>See {@link android.widget.ImageView.ScaleType}
-for other image scaling options, in case you want to avoid stretching images that don't
-exactly match the ImageView dimensions.</p>
+<p>See {@link android.widget.ImageView.ScaleType} for other image scaling options.</p>
+</li>
-<li>Now run it.</li>
+<li>Run the application.</li>
</ol>
+
<p>You should see something like this:</p>
<img src="images/hello-gallery.png" width="150px" />
@@ -129,7 +165,7 @@
<li>{@link android.widget.BaseAdapter}</li>
<li>{@link android.widget.Gallery}</li>
<li>{@link android.widget.ImageView}</li>
- <li>{@link android.widget.Toast}</li>
+ <li>{@link android.widget.AdapterView.OnItemClickListener}</li>
</ul>
diff --git a/docs/html/resources/tutorials/views/hello-gridview.jd b/docs/html/resources/tutorials/views/hello-gridview.jd
index ffb6c93..03bfc54 100644
--- a/docs/html/resources/tutorials/views/hello-gridview.jd
+++ b/docs/html/resources/tutorials/views/hello-gridview.jd
@@ -1,33 +1,44 @@
-page.title=Hello, GridView
+page.title=Grid View
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>A {@link android.widget.GridView} displays items in a two-dimensional, scrolling grid. The items
-are acquired from a {@link android.widget.ListAdapter}.</p>
+<p>{@link android.widget.GridView} is a {@link android.view.ViewGroup} that displays items in a
+two-dimensional,
+scrollable grid. The grid items are automatically inserted to the layout using a {@link
+android.widget.ListAdapter}.</p>
+
+<p>In this tutorial, you'll create a grid of image thumbnails. When an item is selected, a
+toast message will display the position of the image.</p>
<ol>
- <li>Start a new project/Activity called HelloGridView.</li>
- <li>Find some photos you'd like to use, or copy some from the SDK samples res/drawable/
- folder of your project.</li>
- <li>Open the layout and make it like so:
+ <li>Start a new project named <em>HelloGridView</em>.</li>
+ <li>Find some photos you'd like to use, or <a
+href="{@docRoot}shareables/sample_images.zip">download these sample images</a>. Save the image files
+into the project's
+<code>res/drawable/</code> directory.</li>
+ <li>Open the <code>res/layout/main.xml</code> file and insert the following:
<pre>
<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gridview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
+ android:columnWidth="90dp"
android:numColumns="auto_fit"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
- android:columnWidth="90dp"
android:stretchMode="columnWidth"
android:gravity="center"
/>
</pre>
+ <p>This {@link android.widget.GridView} will fill the entire screen. The attributes are rather
+self explanatory. For more information about valid attributes, see the {@link
+android.widget.GridView} reference.</p>
</li>
- <li>Open the HelloGridView Java file. Insert the following for the <code>onCreate()</code> method:
+ <li>Open <code>HelloGridView.java</code> and insert the following code for the
+{@link android.app.Activity#onCreate(Bundle) onCreate()} method:
<pre>
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -35,12 +46,33 @@
GridView gridview = (GridView) findViewById(R.id.gridview);
gridview.setAdapter(new ImageAdapter(this));
+
+ gridview.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ Toast.makeText(HelloGridView.this, "" + position, Toast.LENGTH_SHORT).show();
+ }
+ });
}
</pre>
- <p>Here, we get a handle on our GridView, from the layout, and give it an Adapter.
- We're actually going to create our own Adapter called ImageAdapter.</p>
+ <p>After the {@code main.xml} layout is set for the content view, the
+{@link android.widget.GridView} is captured from the layout with {@link
+android.app.Activity#findViewById(int)}. The {@link
+android.widget.GridView#setAdapter(T) setAdapter()} method then sets a custom adapter ({@code
+ImageAdapter}) as the source for all items to be displayed in the grid. The {@code ImageAdapter} is
+created in the next step.</p>
+<p>To do something when an item in the grid is clicked, the {@link
+android.widget.AdapterView#setOnItemClickListener(OnItemClickListener) setOnItemClickListener()}
+method is passed a new {@link android.widget.AdapterView.OnItemClickListener}. This anonymous
+instance defines the {@link
+android.widget.AdapterView.OnItemClickListener#onItemClick(AdapterView,View,int,long)
+onItemClick()} callback method to show a {@link android.widget.Toast} that displays the index
+position (zero-based) of the selected item (in a real world scenario, the position could be used to
+get the full sized
+image for some other task).</p>
+
</li>
-<li>Create a new class (nested or otherwise), called ImageAdapter, which extends {@link android.widget.BaseAdapter}:
+<li>Create a new class called <code>ImageAdapter</code> that extends {@link
+android.widget.BaseAdapter}:
<pre>
public class ImageAdapter extends BaseAdapter {
private Context mContext;
@@ -93,37 +125,58 @@
};
}
</pre>
- <p>First we take care of some required methods inherited from BaseAdapter.
- The constructor and <code>getCount()</code> are self-explanitory. Normally, <code>getItem()</code>
- should return the actual object at the specified position in our Adapter, but for this Hello World,
- we're not going to bother. Likewise, <code>getItemId()</code> should return the row id of
- the item, but right now we don't care.</p>
- <p>However, <code>getView()</code> is the method we care about. This one creates a new View for each image that we
- put in our ImageAdapter. So we're going to create an ImageView each time. When this is called, we're
- going to receive a View, which is likely a recycled View object (at least after the first call), so we
- check for this—if it's null, we initialize the ImageView and setup all the properties we want.
- The <code>LayoutParams()</code> initialization sets the height and width of the View—this ensures
- that no matter the drawable size, each image is resized and cropped to fit in the ImageView (if necessary).
- With <code>setScaleType()</code>, we say that images should be cropped toward the center (if necessary).
- And finally, we set the padding within the ImageView. (Note that, if the images have various aspect-ratios,
- as they do in this demo, then less padding will cause for more cropping of the image, if it does not match
- the dimensions given to the ImageView.) At the end of <code>getView()</code> we set the image resource and
- return the ImageView.</p>
- <p>All that's left is our array or drawable resources at the bottom.</p>
+<p>First, this implements some required methods inherited from {@link
+android.widget.BaseAdapter}. The constructor and {@link
+android.widget.Adapter#getCount()} are self-explanatory. Normally, {@link
+android.widget.Adapter#getItem(int)} should return the actual object at the specified position in
+the adapter, but it's ignored for this example. Likewise, {@link
+android.widget.Adapter#getItemId(int)} should return the row id of the item, but it's not
+needed here.</p>
+
+<p>The first method necessary is {@link android.widget.Adapter#getView(int,View,ViewGroup)
+getView()}. This method creates a new {@link android.view.View} for each image added to the {@code
+ImageAdapter}. When this is called, a {@link android.view.View} is passed in, which is normally a
+recycled object (at least after this has been called once), so there's a check to see if the
+object is null. If it <em>is</em> null, an {@link android.widget.ImageView} is instantiated and
+configured with desired properties for the image presentation:</p>
+<ul>
+ <li>{@link android.view.View#setLayoutParams(ViewGroup.LayoutParams)} sets
+the height and width for the View—this ensures that, no matter the size of the drawable, each
+image is resized and cropped to fit in these dimensions, as appropriate.</li>
+ <li>{@link android.widget.ImageView#setScaleType(ImageView.ScaleType)} declares that images should
+be cropped toward the center (if necessary).</li>
+ <li>{@link android.widget.ImageView#setPadding(int,int,int,int)} defines the padding for all
+sides. (Note that, if the images have different aspect-ratios, then less
+padding will cause for more cropping of the image if it does not match
+the dimensions given to the ImageView.)</li>
+</ul>
+
+<p>If the {@link android.view.View} passed to {@link
+android.widget.Adapter#getView(int,View,ViewGroup) getView()} is <em>not</em> null, then the local
+{@link android.widget.ImageView} is initialized with the recycled {@link android.view.View}
+object.</p>
+
+<p>At the end of the {@link android.widget.Adapter#getView(int,View,ViewGroup) getView()} method,
+the {@code
+position} integer passed into the method is used to select an image from the {@code mThumbIds}
+array, which is set as the image resource for the {@link android.widget.ImageView}.</p>
+<p>All that's left is to define the {@code mThumbIds} array of drawable resources.</p>
</li>
-<li>Run it.</li>
+<li>Run the application.</li>
</ol>
<p>Your grid layout should look something like this:</p>
<img src="images/hello-gridview.png" width="150px" />
-<p>Try experimenting with the behaviors of the GridView and ImageView by adjusting their properties. For example,
- instead of setting the ImageView LayoutParams, you can try using
- {@link android.widget.ImageView#setAdjustViewBounds(boolean)}. </p>
+<p>Try experimenting with the behaviors of the {@link android.widget.GridView} and {@link
+android.widget.ImageView} elements by adjusting their properties. For example, instead of using
+{@link android.view.View#setLayoutParams(ViewGroup.LayoutParams)}, try using
+{@link android.widget.ImageView#setAdjustViewBounds(boolean)}. </p>
<h3>References</h3>
<ul>
<li>{@link android.widget.GridView}</li>
<li>{@link android.widget.ImageView}</li>
<li>{@link android.widget.BaseAdapter}</li>
+ <li>{@link android.widget.AdapterView.OnItemClickListener}</li>
</ul>
diff --git a/docs/html/resources/tutorials/views/hello-linearlayout.jd b/docs/html/resources/tutorials/views/hello-linearlayout.jd
index 0e8947c..8e072b1 100644
--- a/docs/html/resources/tutorials/views/hello-linearlayout.jd
+++ b/docs/html/resources/tutorials/views/hello-linearlayout.jd
@@ -1,16 +1,18 @@
-page.title=Hello, LinearLayout
+page.title=Linear Layout
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>A {@link android.widget.LinearLayout} is a GroupView that will lay child View elements
-vertically or horizontally.</p>
+<p>{@link android.widget.LinearLayout} is a {@link android.view.ViewGroup} that displays child
+{@link android.view.View} elements in a linear direction, either vertically or horizontally.</p>
+<p>You should be careful about over-using the {@link android.widget.LinearLayout}. If you begin
+nesting multiple {@link android.widget.LinearLayout}s, you may want to consider using a {@link
+android.widget.RelativeLayout} instead.</p>
<ol>
- <li>Start a new project/Activity called HelloLinearLayout.</li>
- <li>Open the layout file.
- Make it like so:
+ <li>Start a new project named <em>HelloLinearLayout</em>.</li>
+ <li>Open the <code>res/layout/main.xml</code> file and insert the following:
<pre>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@@ -18,113 +20,114 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent">
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:layout_weight="1">
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1">
+ <TextView
+ android:text="red"
+ android:gravity="center_horizontal"
+ android:background="#aa0000"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"/>
+ <TextView
+ android:text="green"
+ android:gravity="center_horizontal"
+ android:background="#00aa00"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"/>
+ <TextView
+ android:text="blue"
+ android:gravity="center_horizontal"
+ android:background="#0000aa"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"/>
+ <TextView
+ android:text="yellow"
+ android:gravity="center_horizontal"
+ android:background="#aaaa00"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"/>
+ </LinearLayout>
- <TextView
- android:text="red"
- android:gravity="center_horizontal"
- android:background="#aa0000"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_weight="1"/>
-
- <TextView
- android:text="green"
- android:gravity="center_horizontal"
- android:background="#00aa00"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_weight="1"/>
-
- <TextView
- android:text="blue"
- android:gravity="center_horizontal"
- android:background="#0000aa"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_weight="1"/>
-
- <TextView
- android:text="yellow"
- android:gravity="center_horizontal"
- android:background="#aaaa00"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_weight="1"/>
-
- </LinearLayout>
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:layout_weight="1">
-
- <TextView
- android:text="row one"
- android:textSize="15pt"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"/>
-
- <TextView
- android:text="row two"
- android:textSize="15pt"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"/>
-
- <TextView
- android:text="row three"
- android:textSize="15pt"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"/>
-
- <TextView
- android:text="row four"
- android:textSize="15pt"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"/>
-
- </LinearLayout>
-
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1">
+ <TextView
+ android:text="row one"
+ android:textSize="15pt"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ <TextView
+ android:text="row two"
+ android:textSize="15pt"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ <TextView
+ android:text="row three"
+ android:textSize="15pt"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ <TextView
+ android:text="row four"
+ android:textSize="15pt"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ </LinearLayout>
+
</LinearLayout>
</pre>
- <p>Carefully inspect the XML. You'll notice how this layout works a lot like
- an HTML layout. There is one parent LinearLayout that is defined to lay
- its child elements vertically. The first child is another LinearLayout that uses a horizontal layout
- and the second uses a vertical layout. Each LinearLayout contains several {@link android.widget.TextView}
- elements.</p>
+
+<p>Carefully inspect this XML. There is a root {@link android.widget.LinearLayout} that defines
+its orientation to be vertical—all child {@link android.view.View}s (of which it has two) will
+be stacked vertically. The first child is
+another {@link android.widget.LinearLayout} that uses a horizontal orientation and the second child
+is a {@link android.widget.LinearLayout} that uses a vertical orientation. Each of these nested
+{@link android.widget.LinearLayout}s contain several {@link android.widget.TextView} elements, which
+are oriented with each other in the manner defined by their parent {@link
+android.widget.LinearLayout}.</p>
</li>
-<li>Now open the HelloLinearLayout Activity and be sure it loads this layout in the <code>onCreate()</code> method:</p>
+<li>Now open <code>HelloLinearLayout.java</code> and be sure it loads the
+<code>res/layout/main.xml</code> layout in the
+{@link android.app.Activity#onCreate(Bundle) onCreate()} method:</p>
<pre>
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
</pre>
-<p><code>R.layout.main</code> refers to the <code>main.xml</code> layout file.</p>
+<p>The {@link android.app.Activity#setContentView(int)} method loads the
+layout file for the {@link android.app.Activity}, specified by the resource
+ID — <code>R.layout.main</code> refers to the <code>res/layout/main.xml</code> layout
+file.</p>
</li>
-<li>Run it.</li>
+<li>Run the application.</li>
</ol>
<p>You should see the following:</p>
<img src="images/hello-linearlayout.png" width="150px" />
-<p>Notice how the various XML attributes define the View's behavior.
-Pay attention to the effect of the <code>layout_weight</code>. Try
- experimenting with different values to see how the screen real estate is
- distributed based on the weight of each element.</p>
+<p>Notice how the XML attributes define each View's behavior. Try
+experimenting with different values for <code>android:layout_weight</code> to see how the screen
+real estate is distributed based on the weight of each element. See the <a
+href="{@docRoot}guide/topics/ui/layout-objects.html#linearlayout">Common Layout Objects</a>
+document for more about how {@link android.widget.LinearLayout} handles the
+<code>android:layout_weight</code> attribute.</p>
<h3>References</h3>
<ul>
<li>{@link android.widget.LinearLayout}</li>
-<li>{@link android.widget.TextView}</li>
+ <li>{@link android.widget.TextView}</li>
</ul>
diff --git a/docs/html/resources/tutorials/views/hello-listview.jd b/docs/html/resources/tutorials/views/hello-listview.jd
index d90005b..194258e 100644
--- a/docs/html/resources/tutorials/views/hello-listview.jd
+++ b/docs/html/resources/tutorials/views/hello-listview.jd
@@ -1,36 +1,88 @@
-page.title=Hello, ListView
+page.title=List View
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>A {@link android.widget.ListView} is a View that shows items in a vertically scrolling list. The items are
- acquired from a {@link android.widget.ListAdapter}.</p>
+<p>{@link android.widget.ListView} is a {@link android.view.ViewGroup} that creates a list of
+scrollable items. The list items are automatically inserted to the list using a {@link
+android.widget.ListAdapter}.</p>
+<p>In this tutorial, you'll create a scrollable list of country names that are read from a string array.
+When a list item is selected, a toast message will display the position of the item in the list.</p>
<ol>
- <li>Start a new project/ListActivity called HelloListView.</li>
- <li>Open the HelloListView Java file. Make the class extend ListActivity (instead of Activity).
+ <li>Start a new project named <em>HelloListView</em>.</li>
+ <li>Create an XML file named <code>list_item.xml</code> and save it inside the
+<code>res/layout/</code> folder. Insert the following:
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="10dp"
+ android:textSize="16sp" >
+</TextView>
+</pre>
+ <p>This file defines the layout for each item that will be placed in the {@link
+android.widget.ListView}.</p>
+ </li>
+
+ <li>Open the <code>HelloListView.java</code> and make the class extend {@link
+android.app.ListActivity} (instead of {@link android.app.Activity}):
<pre>public class HelloListView extends ListActivity {</pre>
</li>
- <li>Insert the following for the <code>onCreate()</code> method:
+ <li>Insert the following code for the {@link android.app.Activity#onCreate(Bundle) onCreate()}
+method:
<pre>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- setListAdapter(new ArrayAdapter<String>(this,
- android.R.layout.simple_list_item_1, COUNTRIES));
- getListView().setTextFilterEnabled(true);
+
+ setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, COUNTRIES));
+
+ ListView lv = getListView();
+ lv.setTextFilterEnabled(true);
+
+ lv.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ // When clicked, show a toast with the TextView text
+ Toast.makeText(getApplicationContext(), ((TextView) view).getText(),
+ Toast.LENGTH_SHORT).show();
+ }
+ });
}
</pre>
- <p>Notice that we don't need to load a layout (at least, not in this case, because we're using
- the whole screen for our list). Instead, we just call <code>setListAdapter()</code> (which automatically
- adds a ListView to the ListActivity), and provide it with an ArrayAdapter that binds a
- <code>simple_list_item_1</code> layout item to each entry in the <code>COUNTRIES</code>
- array (added next). The next line of code adds a text filter to the ListView, so that when the user
- begins typing, the list will filter the entire view to display only the items that match the entry.</p>
- </li>
- <li>Following the <code>onCreate()</code> method, add the String array:
+<p>Notice that this does not load a layout file for the Activity (which you usually do with {@link
+android.app.Activity#setContentView(int)}). Instead, {@link
+android.app.ListActivity#setListAdapter(ListAdapter)} automatically
+adds a {@link android.widget.ListView} to fill the entire screen of the {@link
+android.app.ListActivity}. This method takes an {@link android.widget.ArrayAdapter}, which
+manages the array of list items that will be placed into the {@link android.widget.ListView}. The
+{@link android.widget.ArrayAdapter} constructor takes the application {@link
+android.content.Context}, the
+layout description for each list item (created in
+the previous step), and a {@link java.util.List} of objects to insert in the {@link
+android.widget.ListView} (defined next).</p>
+
+<p>The {@link android.widget.ListView#setTextFilterEnabled(boolean)} method turns on text filtering
+for the {@link android.widget.ListView}, so that when the user begins typing, the list will be
+filtered.</p>
+
+<p>The {@link android.widget.ListView#setOnItemClickListener(OnItemClickListener)} method defines
+the on-click listener for each item. When an item in the {@link android.widget.ListView} is clicked,
+the {@link android.widget.AdapterView.OnItemClickListener#onItemClick(AdapterView,View,int,long)
+onItemClick()} method is called and a {@link android.widget.Toast} message is displayed, using the
+text from the clicked item.</p>
+
+<p class="note"><strong>Tip:</strong> You can use list item designs provided by the platform
+instead of defining your own layout file for the {@link android.widget.ListAdapter}. For example,
+try using <code>android.R.layout.simple_list_item_1</code> instead of
+<code>R.layout.list_item</code>.</p>
+</li>
+
+<li>After the {@link android.app.Activity#onCreate(Bundle) onCreate()} method, add the string
+array:
<pre>
static final String[] COUNTRIES = new String[] {
"Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
@@ -76,12 +128,40 @@
"Yemen", "Yugoslavia", "Zambia", "Zimbabwe"
};
</pre>
+ <p>This is the array of strings that will be placed into the {@link android.widget.ListView}.</p>
</li>
-<li> Run it.</li>
+<li>Run the application.</li>
</ol>
-<p>You can scroll the list, or type to filter it. You should see something like this:</p>
+<p>You can scroll the list, or type to filter it, then click an item to see a message. You
+should see something like this:</p>
<img src="images/hello-listview.png" width="150px" />
+<p>Note that using a hard-coded string array is not the best design practice. One is
+used in this tutorial for simplicity, in order to demonstrate the {@link
+android.widget.ListView} widget. The better practice is to reference a string array
+defined by an external resource, such as with a {@code <string-array>}
+resource in your project {@code res/values/strings.xml} file. For example:</p>
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string-array name="countries_array">
+ <item>Bahrain</item>
+ <item>Bangladesh</item>
+ <item>Barbados</item>
+ <item>Belarus</item>
+ <item>Belgium</item>
+ <item>Belize</item>
+ <item>Benin</item>
+ </string-array>
+</resources>
+</pre>
+<p>To use these resource strings for the {@link android.widget.ArrayAdapter}, replace the original
+{@link android.app.ListActivity#setListAdapter(ListAdapter)} line with the following:</p>
+<pre>
+String[] countries = getResources().getStringArray(R.array.countries_array);
+setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, countries));
+</pre>
+
<h3>References</h3>
<ul>
<li>{@link android.widget.ListView}</li>
diff --git a/docs/html/resources/tutorials/views/hello-mapview.jd b/docs/html/resources/tutorials/views/hello-mapview.jd
index 531300f..a3fa548 100644
--- a/docs/html/resources/tutorials/views/hello-mapview.jd
+++ b/docs/html/resources/tutorials/views/hello-mapview.jd
@@ -1,273 +1,303 @@
-page.title=Hello, MapView
+page.title=Google Map View
parent.title=Hello, Views
parent.link=index.html
@jd:body
+<p>Using the Google Maps library, you can create your own map-viewing Activity. In this
+tutorial, you'll create a simple map application in two parts. In Part 1, you'll create an app that
+shows a map the user can pan and zoom. In Part 2, you'll add overlay items that mark
+points of interest.</p>
+
<div class="special">
-<p>This tutorial requires that you have the Google Maps external library
-installed in your SDK environment. By default the Android SDK includes the
-Google APIs add-on, which in turn includes the Maps external library. If you
-don't have the Google APIs SDK add-on, you can download it from this
-location:</p>
+<p>This tutorial requires that you have the external Google Maps library
+installed in your SDK environment. The Maps library is included with the Google APIs
+add-on, which you can install using the Android SDK and
+AVD Manager. To learn how, see <a href="{@docRoot}sdk/adding-components.html">Adding SDK
+Components</a>.</p>
-<p style="margin-left:2em;"><a
-href="http://code.google.com/android/add-ons/google-apis">http://code.google.com/android/add-ons/google-apis</a></p>
-
-<p>The Google APIs add-on requires Android 1.5 SDK or later release. After
-installing the add-on in your SDK, set your project properties to use the build
-target called "Google APIs Add-on". See the instructions for setting a build
+<p>After installing the Google APIs add-on in your SDK, set your project properties to use the build
+target called "Google APIs by Google Inc.". See the instructions for setting a build
target in <a href="{@docRoot}guide/developing/eclipse-adt.html">Developing in
Eclipse with ADT</a> or <a
href="{@docRoot}guide/developing/other-ide.html">Developing in Other IDEs</a>,
as appropriate for your environment. </p>
-<p>You will also need to use the android tool to set up an AVD that uses the
-Google APIs deployment target. See <a
+<p>You will also need to set up a new AVD that uses the same Google APIs deployment target. See <a
href="{@docRoot}guide/developing/tools/avd.html">Android Virtual Devices</a> for
-more information. Once you have set up your environment, you will be able to
-build and run the project described in this tutorial</a></p>
+more information.</p>
+
+<p>For reference material, see the <a
+href="http://code.google.com/android/add-ons/google-apis/reference/index.html">Google Maps
+library documentation</a>.</p>
</div>
-<p>A MapView allows you to create your own map-viewing Activity.
-First, we'll create a simple Activity that can view and navigate a map. Then we will add some overlay items.</p>
+<h2>Part 1: Creating a Map Activity</h2>
<ol>
- <li>Start a new project/Activity called HelloMapView.
+ <li>Start a new project named <em>HelloGoogleMaps</em>.</li>
- <li>Because we're using the Google Maps library,
- which is not a part of the standard Android library, we need to
- declare it in the Android Manifest. Open the AndroidManifest.xml
- file and add the following as a child of the <code><application></code> element:
+ <li>Because the Maps library is not a part of the standard Android library, you must
+ declare it in the Android Manifest. Open the <code>AndroidManifest.xml</code>
+ file and add the following as a child of the <code><application></code> element:
+ <pre><uses-library android:name="com.google.android.maps" /></pre>
+ </li>
- <pre><uses-library android:name="com.google.android.maps" /></pre>
- </li>
- <li>We also need access to the internet in order to retrieve the Google Maps tiles,
- so the application must request the {@link android.Manifest.permission#INTERNET INTERNET} permissions.
- In the manifest file, add the following as a child of the <code><manifest></code> element:
- <pre><uses-permission android:name="android.permission.INTERNET" /></pre>
- </li>
- <li>Now open the main layout file for your project. Define a layout with a com.google.android.maps.MapView
- inside a android.widget.RelativeLayout:
+ <li>You also need access to the Internet in order to retrieve map tiles,
+ so you must also request the {@link android.Manifest.permission#INTERNET} permission.
+ In the manifest file, add the following as a child of the <code><manifest></code> element:
+ <pre><uses-permission android:name="android.permission.INTERNET" /></pre>
+ </li>
+ <li>While you're in the manifest, give the map some more space by getting rid of the title bar
+with the "NoTitleBar" theme:
+<pre>
+<activity android:name=".HelloGoogleMaps" android:label="@string/app_name"
+ <strong>android:theme="@android:style/Theme.NoTitleBar"</strong>>
+</pre>
+ </li>
+
+
+ <li>Open the <code>res/layout/main.xml</code> file and add a single
+ {@code com.google.android.maps.MapView} as the root node:
<pre>
<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/mainlayout"
- android:orientation="vertical"
+<com.google.android.maps.MapView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/mapview"
android:layout_width="fill_parent"
- android:layout_height="fill_parent" >
-
- <com.google.android.maps.MapView
- android:id="@+id/mapview"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:clickable="true"
- android:apiKey="<em>Your Maps API Key</em>"
- />
-
-</RelativeLayout>
+ android:layout_height="fill_parent"
+ android:clickable="true"
+ android:apiKey="<em>Your Maps API Key goes here</em>"
+/>
</pre>
- <p>The <code>clickable</code> attribute defines whether you want to allow user-interaction with the map.
- In this case, we set it "true" so that the user can navigate.</p>
+ <p>The <code>android:clickable</code> attribute defines whether you want to allow
+ user-interaction with the map. If this is "false" then touching the map does nothing.</p>
- <p>The <code>apiKey</code> attribute holds the Google Maps API Key that proves your application and signer
- certificate has been registered with the Google Maps service. Because MapView uses Google Maps data, this key is required
- in order to receive the map data, even while you are developing. Registration is free and it only takes a couple
- minutes to register your certificate and receive a Maps API Key. For instructions on getting a key, read
- <a href="http://code.google.com/android/add-ons/google-apis/mapkey.html">Obtaining a Maps API Key</a>.
- (For the purpose of this tutorial, you should register with the fingerprint of the SDK debug certificate.)
- Once you've acquired the Maps API Key, insert it for the <code>apiKey</code> value.</p></li>
+ <p>The <code>android:apiKey</code> attribute holds the Maps API Key for your
+ application, which proves your application and signer certificate has been registered with the
+ Maps service. This is required in order to receive the map data, even while you are
+ developing. Registration to the service is free and it only takes a couple
+ minutes to register your certificate and get a Maps API Key.</p>
+ <p>Go now to get a key. For instructions, read
+ <a href="http://code.google.com/android/add-ons/google-apis/mapkey.html">Obtaining a Maps API
+ Key</a>. For the purpose of this tutorial, you should <a
+ href="http://code.google.com/android/add-ons/google-apis/mapkey.html#getdebugfingerprint">register
+ with the SDK debug certificate</a>, which will only be valid while your application is signed
+ with the debug key (once you sign with your private key, you will need a new API key).
+ When you get your key, insert it for the value of <code>android:apiKey</code>.</p>
+ </li>
- <li>Now open the HelloMapView.java file. For this Activity, we're going to extend the special sub-class of
- Activity called MapActivity, so change the class declaration to extend
- MapActicity, instead of Activity:</p>
+ <li>Now open the <code>HelloGoogleMaps.java</code> file. For this Activity, extend
+ {@code MapActivity} (instead of {@code android.app.Activity}):</p>
- <pre>public class HelloMapView extends MapActivity {</pre>
+ <pre>public class HelloGoogleMaps extends MapActivity {</pre>
+ <p>This is a special sub-class of {@link android.app.Activity}, provided by the Maps
+ library, which provides important map capabilities.</p>
- <li>The <code>isRouteDisplayed()</code> method is required, so add it inside the class:
+ <li>Inside every {@code MapActivity}, the <code>isRouteDisplayed()</code> method is required, so
+ override this method:
<pre>
@Override
protected boolean isRouteDisplayed() {
return false;
}
</pre>
-<p>You can actually run this now, but all it does is allow you to pan around the map.</p>
-<p>Android provides a handy {@link android.widget.ZoomControls} widget for zooming in and out of a View.
-MapView can automatically hook one for us by requesting it with the <code>getZoomControls()</code>
-method. Let's do this.</p>
+<p>This method is required for some accounting from the Maps service to see if you're currently
+displaying any route information. In this case, you're not, so return false.</p>
+</li>
-<li>Go back to the layout file. We need a new ViewGroup element, in which we'll
- place the ZoomControls. Just below the MapView element (but inside the RelativeLayout), add this element:
+<li>Now add the standard {@link android.app.Activity#onCreate(Bundle) onCreate()} callback method
+to the class:
<pre>
-<LinearLayout
- android:id="@+id/zoomview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignBottom="@id/mapview"
- android:layout_centerHorizontal="true"
-/></pre>
-
- <p>It doesn't really matter what kind of ViewGroup we use, because we just want a
- container that we can position within our root RelativeLayout.</p>
-
- <p>The last two attributes are available only to an element that's a child of a
- RelativeLayout. <code>layout_alignBottom</code> aligns the bottom of this element to the bottom of
- the element identified with a resource tag (which must be a sibling to this element).
- <code>layout_centerHorizontal</code> centers this on the horizontal plane.</p></li>
-
- <li>Now go back to the HelloMapView class. We'll now retrieve the ZoomControls object from
- the MapView and add it to our new layout element. First, at the top of the HelloMapView,
- instantiate handles for the MapView and LinearLayout, plus a ZoomControl object:
+@Override
+public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+}
+</pre>
+<p>This loads the layout file created above. In fact, this is now a workable application that will
+display map tiles and allow the user to pan around the map. But there's no ability to zoom.
+Fortunately, there's a very simple zoom feature built into the {@code MapView} class, which you can
+summon with {@code setBuiltInZoomControls(boolean)}. Do this at the end of the {@link
+android.app.Activity#onCreate(Bundle) onCreate()} method:</p>
<pre>
-LinearLayout linearLayout;
-MapView mapView;
-ZoomControls mZoom;</pre></li>
-
- <li>Then initialize each of these in <code>onCreate()</code>. We'll capture the LinearLayout and
- MapView through their layout resources. Then get the ZoomControls from the MapView::
-<pre>
-linearLayout = (LinearLayout) findViewById(R.id.zoomview);
-mapView = (MapView) findViewById(R.id.mapview);
-mZoom = (ZoomControls) mapView.getZoomControls();</pre>
-
- <p>By using the ZoomControls object provided by MapView, we don't have to do any of the work
- required to actually perform the zoom operations. The ZoomControls widget that MapView
- returns for us is already hooked into the MapView and works as soon as we add it to the
- layout. The controls will appear whenever the user touches the map, then dissapear after
- a few moments of inactivity.</p></li>
-
- <li>Now just plug our ZoomControls into the LinearLayout we added:
-
- <pre>linearLayout.addView(mZoom);</pre></li>
-
- <li>Run it.</li>
+ MapView mapView = (MapView) findViewById(R.id.mapview);
+ mapView.setBuiltInZoomControls(true);
+</pre>
+</li>
+<li>That's all there is to it. Run the application. (Remember, you must have an <a
+href="{@docRoot}guide/developing/tools/avd.html">AVD</a> configured to use the Google APIs
+target, or be using a development device that includes the Maps library.)</li>
</ol>
-<hr/>
+<h2>Part 2: Adding Overlay Items</h2>
-<p>So, we now have full interaction controls. All well and good, but what we really want our map
-for is custom markers and layovers. Let's add some Overlay
-objects to our map. To do this, we're going to
-implement the ItemizedOverlay
-class, which can manage a whole set of Overlay items for us.</p>
+<p>So, now you have a map, but in many cases you'll also want to create your own map
+markers and lay-overs. That's what you'll do now. In order to do so, you must implement the
+{@code ItemizedOverlay} class, which can manage a whole set of {@code Overlay} (which are the
+individual items placed on the map).</p>
<ol>
- <li>Create a new Java class named HelloItemizedOverlay that implements ItemizedOverlay.
+ <li>Create a new Java class named <code>HelloItemizedOverlay</code> that implements
+ {@code ItemizedOverlay}.
- <p>When using Eclipse, right-click the package name in the Eclipse Package Explorer, and select New > Class. Fill-in
- the Name field as <em>HelloItemizedOverlay</em>. For the Superclass, enter
- <em>com.google.android.maps.ItemizedOverlay</em>. Click the checkbox for <em>Constructors from
- superclass</em>. Click Finish.</p></li>
+ <p>When using Eclipse, right-click the package name in the Eclipse Package Explorer, and
+ select <strong>New > Class</strong>. Fill-in
+ the Name field as <em>HelloItemizedOverlay</em>. For the Superclass, enter
+ "com.google.android.maps.ItemizedOverlay". Click the checkbox for <em>Constructors from
+ superclass</em>. Click Finish.</p></li>
- <li> First thing, we need an OverlayItem ArrayList, in which we'll put each of the OverlayItem
- objects we want on our map. Add this at the top of the HelloItemizedOverlay class:
+ <li>First, you need an <code>OverlayItem</code> {@link java.util.ArrayList}, in which you'll put
+ each of the <code>OverlayItem</code> objects you want on the map. Add this at the top of the
+ <code>HelloItemizedOverlay</code> class:
- <pre>private ArrayList<OverlayItem> mOverlays = new ArrayList<OverlayItem>();</pre></li>
+ <pre>private ArrayList<OverlayItem> mOverlays = new ArrayList<OverlayItem>();</pre>
+ </li>
- <li>All the constructor does is define the default marker to be used on each of the OverlayItems.
- In order for the Drawable to actually get drawn, it must have its bounds defined. And we want the
- center-point at the bottom of the image to be the point at which it's attached to the map
- coordinates. We handle all this with the boundCenterBottom() method. Wrap this around our
- defaultMarker, so the super constructor call looks like this:
+ <li>Now define the <code>HelloItemizedOverlay</code> constructors. The constructor must
+ define the default marker for each of the {@code OverlayItem}s. In order for the {@link
+ android.graphics.drawable.Drawable} to actually get drawn, it must have its bounds defined. Most
+ commonly, you want the center-point at the bottom of the image to be the point at which it's
+ attached to the map coordinates. This is handled for you with the {@code boundCenterBottom()}
+ method. Wrap this around our defaultMarker, so the super constructor call looks like this:
+<pre>
+public HelloItemizedOverlay(Drawable defaultMarker) {
+ super(boundCenterBottom(defaultMarker));
+}
+</pre>
+ </li>
- <pre>super(boundCenterBottom(defaultMarker));</pre></li>
-
- <li>In order to add new OverlayItems to our ArrayList, we need a new public method. We'll handle
- this with the following method:
-
+ <li>In order to add new {@code OverlayItem}s to the ArrayList, you need a new method:
<pre>
public void addOverlay(OverlayItem overlay) {
mOverlays.add(overlay);
populate();
}</pre>
+ <p>Each time you add a new {@code OverlayItem} to the ArrayList, you must call
+ <code>populate()</code> for the {@code ItemizedOverlay}, which will read each of the
+ {@code OverlayItem}s and prepare them to be drawn.</p>
+ </li>
- <p>Each time we add a new OverlayItem, we must call <code>populate()</code>, which will read each of out
- OverlayItems and prepare them to be drawn.</p></li>
-
- <li>In order for the <code>populate()</code> method to read each OverlayItem, it will make a request to
- <code>createItem(int)</code>. We must define this method to properly read from our ArrayList. Replace the
- existing contents of the createItem method with a <code>get()</code> call to our ArrayList:
+ <li>When the <code>populate()</code> method executes, it will call <code>createItem(int)</code> in
+ the {@code ItemizedOverlay} to retrieve each {@code OverlayItem}. You must override this method to
+ properly read from the ArrayList and return the {@code OverlayItem} from the position specified
+ by the given integer. Your override method should look like this:
<pre>
@Override
protected OverlayItem createItem(int i) {
return mOverlays.get(i);
}
-</pre></li>
+</pre>
+ </li>
- <li>We're also required to override the <code>size()</code> method. Replace the existing contents of the
- method with a size request to our ArrayList:
+ <li>You must also override the <code>size()</code> method to return the current number of
+ items in the ArrayList:
+<pre>
+@Override
+public int size() {
+ return mOverlays.size();
+}
+</pre>
+ </li>
- <pre>return mOverlays.size();</pre></li>
+ <li>Now set up the ability to handle touch events on the overlay items. First, you're
+ going to need a reference to the application {@link android.content.Context} as a member of
+ this class. So add {@code Context mContext} as a class member, then initialize it with a
+ new class constructor:
+<pre>
+public HelloItemizedOverlay(Drawable defaultMarker, Context context) {
+ super(defaultMarker);
+ mContext = context;
+}
+</pre>
+ <p>This passes the {@code defaultMarker} up to the default constructor to bound its coordinates
+ and then initialize {@code mContext} with the given {@link android.content.Context}.</p>
+
+ <p>Then override the {@code onTap(int)} callback method, which will handle the event
+ when an item is tapped by the user:</p>
+<pre>
+@Override
+protected boolean onTap(int index) {
+ OverlayItem item = mOverlays.get(index);
+ AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
+ dialog.setTitle(item.getTitle());
+ dialog.setMessage(item.getSnippet())
+ dialog.show();
+ return true;
+}
+</pre>
+ <p>This uses the member {@code android.content.Context} to create a new {@link
+android.app.AlertDialog.Builder} and uses the tapped {@code OverlayItem}'s title and snippet for
+the dialog's title and message text. (You'll see the {@code OverlayItem} title and snippet defined
+when you create it below.)</p>
+ </li>
+
</ol>
+<p>You're now done with the <code>HelloItemizedOverlay</code> class and can start using it
+to add items on the map.</p>
-<p>That's it for the HelloItemizedOverlay class. We're now ready to use it.</p>
-
-<hr/>
-<p>Go back to the HelloMapView
-class. We'll start by creating one OverlayItem, adding to an instance of our HelloItemizedOverlay,
-and then adding this to the MapView.</p>
+<p>Go back to the <code>HelloGoogleMaps</code> class. In the following procedure, you'll create an
+{@code OverlayItem} and add it to an instance of the <code>HelloItemizedOverlay</code> class, then
+add the <code>HelloItemizedOverlay</code> to the <code>MapView</code> using a {@code GeoPoint}
+to define its coordinates on the map.</p>
<img src="images/androidmarker.png" align="right" />
-<p>First, we need the image that we'll use for our map overlay. Here, we'll use the Android on the
-right as our marker. Drag this image (or your own) to the res/drawable/ directory of your project workspace.</p>
-
-<p>Now we're ready to work in the HelloMapView:</p>
-
<ol>
- <li>First we need some more types. Add the following at the top of the HelloMapView class:
+ <li>First, you need the image for the map overlay. If you don't have one handy, use the Android on
+ the right. Drag this image (or your own) into the <code>res/drawable/</code> directory of your
+ project.</li>
+
+ <li>At the end of your existing {@code onCreate()} method, instantiate :
<pre>
-List<Overlay> mapOverlays;
-Drawable drawable;
-HelloItemizedOverlay itemizedOverlay;</pre></li>
+List<Overlay> mapOverlays = mapView.getOverlays();
+Drawable drawable = this.getResources().getDrawable(R.drawable.androidmarker);
+HelloItemizedOverlay itemizedoverlay = new HelloItemizedOverlay(drawable);</pre>
- <li>Now pick up where we left off in the <code>onCreate()</code> method. Instantiate the
- new fields:
+ <p>All overlay elements on a map are held by the {@code MapView}, so when you want to add some,
+ you have to get a list from the <code>getOverlays()</code> method. Then instantiate the {@link
+ android.graphics.drawable.Drawable} used for the map marker, which was saved in the {@code
+ res/drawable/} directory. The constructor for {@code HelloItemizedOverlay} (your custom {@code
+ ItemizedOverlay}) takes the Drawable in order to set the default marker for all overlay
+ items.</p>
+ </li>
-<pre>
-mapOverlays = mapView.getOverlays();
-drawable = this.getResources().getDrawable(R.drawable.androidmarker);
-itemizedoverlay = new HelloItemizedOverlay(drawable);</pre>
-
- <p>All overlay elements on a map are held by the MapView, so when we want to add some, we must
- first retrieve the List with <code>getOverlays()</code> methods. We instantiate the Drawable, which will
- be used as our map marker, by using our Context resources to get the Drawable we placed in
- the res/drawable/ directory (androidmarker.png). Our HelloItemizedOverlay takes the Drawable in order to set the
- default marker.</p></li>
-
- <li>Now let's make our first OverlayItem by creating a GeoPoint
- that defines our map coordinates, then pass it to a new OverlayItem:
-
+ <li>Now create a {@code GeoPoint} that defines the map coordinates for the first overlay item,
+ and pass it to a new {@code OverlayItem}:
<pre>
GeoPoint point = new GeoPoint(19240000,-99120000);
-OverlayItem overlayitem = new OverlayItem(point, "", "");</pre>
+OverlayItem overlayitem = new OverlayItem(point, "Hola, Mundo!", "I'm in Mexico City!");
+</pre>
- <p>GeoPoint coordinates are based in microdegrees (degrees * 1e6). The OverlayItem takes this
- GeoPoint and two strings. Here, we won't concern ourselves with the strings, which can display
- text when we click our marker, because we haven't yet written the click handler for the OverlayItem.</p></li>
+ <p>{@code GeoPoint} coordinates are specified in microdegrees (<code>degrees * 1e6</code>). The
+ {@code OverlayItem} constructor accepts the {@code GeoPoint} location, a string for the
+ item's title, and a string for the item's snippet text, respectively.</p>
+ </li>
- <li>All that's left is for us to add this OverlayItem to our collection in the HelloItemizedOverlay,
- and add this to the List of Overlay objects retrieved from the MapView:
-
+ <li>All that's left is to add this {@code OverlayItem} to your collection in the
+ {@code HelloItemizedOverlay} instance, then add the {@code HelloItemizedOverlay} to the MapView:
<pre>
itemizedoverlay.addOverlay(overlayitem);
-mapOverlays.add(itemizedoverlay);</pre></li>
+mapOverlays.add(itemizedoverlay);
+</pre>
+ </li>
- <li>Run it!</li>
+ <li>Now run the application.</li>
</ol>
-<p>We've sent our droid to Mexico City. Hola, Mundo!</p>
<p>You should see the following:</p>
<img src="images/hello-mapview.png" width="150px" />
+<p>When you tap the overlay item, you'll see the dialog appear.</p>
-<p>Because we created our ItemizedOverlay class with an ArrayList, we can continue adding new
-OverlayItems. Try adding another one. Before the <code>addOverlay()</code> method is called, add these lines:</p>
+<p>Because the {@code ItemizedOverlay} class uses an {@code java.util.ArrayList} for all of the
+{@code OverlayItem}s, it's easy to add more. Try adding another one. Before the
+<code>addOverlay()</code> method is called, add these lines:</p>
<pre>
GeoPoint point2 = new GeoPoint(35410000, 139460000);
-OverlayItem overlayitem2 = new OverlayItem(point2, "", "");
+OverlayItem overlayitem2 = new OverlayItem(point2, "Sekai, konichiwa!", "I'm in Japan!");
</pre>
-<p>Run it again... We've sent a new droid to Tokyo. Sekai, konichiwa!</p>
+<p>Run the application again. (You probably need to move the map to find the new overlay item.)</p>
diff --git a/docs/html/resources/tutorials/views/hello-relativelayout.jd b/docs/html/resources/tutorials/views/hello-relativelayout.jd
index 1b91537..adc1179 100644
--- a/docs/html/resources/tutorials/views/hello-relativelayout.jd
+++ b/docs/html/resources/tutorials/views/hello-relativelayout.jd
@@ -1,33 +1,38 @@
-page.title=Hello, RelativeLayout
+page.title=Relative Layout
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>A {@link android.widget.RelativeLayout} is a ViewGroup that allows you to layout child elements
-in positions relative to the parent or siblings elements.</p>
+<p>{@link android.widget.RelativeLayout} is a {@link android.view.ViewGroup} that displays
+child {@link android.view.View} elements in relative positions. The position of a {@link
+android.view.View} can be specified as relative to sibling elements (such as to the left-of or below
+a given element) or in positions relative to the {@link android.widget.RelativeLayout} area (such as
+aligned to the bottom, left of center).</p>
+
+<p>A {@link android.widget.RelativeLayout} is a very powerful utility for designing a user
+interface because it can eliminate nested {@link android.view.ViewGroup}s. If you find
+yourself using several nested {@link android.widget.LinearLayout} groups, you may be able to
+replace them with a single {@link android.widget.RelativeLayout}.</p>
<ol>
- <li>Start a new project/Activity called HelloRelativeLayout.</li>
- <li>Open the layout file. Make it like so:
+ <li>Start a new project named <em>HelloRelativeLayout</em>.</li>
+ <li>Open the <code>res/layout/main.xml</code> file and insert the following:
<pre>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
-
<TextView
android:id="@+id/label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Type here:"/>
-
<EditText
android:id="@+id/entry"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:layout_below="@id/label"/>
-
<Button
android:id="@+id/ok"
android:layout_width="wrap_content"
@@ -36,39 +41,48 @@
android:layout_alignParentRight="true"
android:layout_marginLeft="10dip"
android:text="OK" />
-
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ok"
android:layout_alignTop="@id/ok"
android:text="Cancel" />
-
</RelativeLayout>
</pre>
-<p>Pay attention to each of the additional <code>layout_*</code> attributes (besides the
-usual width and height, which are required for all elements). When using relative layout,
-we use attributes like <code>layout_below</code> and <code>layout_toLeftOf</code> to describe
-how we'd like to position each View. Naturally, these are different relative positions, and the
-value of the attribute is the id of the element we want the position relative to.</p>
+<p>Notice each of the <code>android:layout_*</code> attributes, such as <code>layout_below</code>,
+<code>layout_alignParentRight</code>, and <code>layout_toLeftOf</code>. When using a {@link
+android.widget.RelativeLayout}, you can use these attributes to describe
+how you want to position each {@link android.view.View}. Each one of these attributes define a
+different kind
+of relative position. Some attributes use the resource ID of a sibling {@link android.view.View}
+to define its own relative position. For example, the last {@link android.widget.Button} is
+defined to lie to the left-of and aligned-with-the-top-of the {@link android.view.View}
+identified by the ID <code>ok</code> (which is the previous {@link android.widget.Button}).</p>
+<p>All of the available layout attributes are defined in {@link
+android.widget.RelativeLayout.LayoutParams}.</p>
</li>
-<li>Make sure your Activity loads this layout in the <code>onCreate()</code> method:</p>
+<li>Make sure you load this layout in the
+{@link android.app.Activity#onCreate(Bundle) onCreate()} method:</p>
<pre>
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
</pre>
-<p><code>R.layout.main</code> refers to the <code>main.xml</code> layout file.</p>
+<p>The {@link android.app.Activity#setContentView(int)} method loads the
+layout file for the {@link android.app.Activity}, specified by the resource
+ID — <code>R.layout.main</code> refers to the <code>res/layout/main.xml</code> layout
+file.</p>
</li>
-<li>Run it.</li>
+<li>Run the application.</li>
</ol>
-<p>You should see the following:</p>
+<p>You should see the following layout:</p>
<img src="images/hello-relativelayout.png" width="150px" />
<h3>Resources</h3>
<ul>
<li>{@link android.widget.RelativeLayout}</li>
+ <li>{@link android.widget.RelativeLayout.LayoutParams}</li>
<li>{@link android.widget.TextView}</li>
<li>{@link android.widget.EditText}</li>
<li>{@link android.widget.Button}</li>
diff --git a/docs/html/resources/tutorials/views/hello-spinner.jd b/docs/html/resources/tutorials/views/hello-spinner.jd
index 3a04214..7a3a9c3 100644
--- a/docs/html/resources/tutorials/views/hello-spinner.jd
+++ b/docs/html/resources/tutorials/views/hello-spinner.jd
@@ -1,17 +1,17 @@
-page.title=Hello, Spinner
+page.title=Spinner
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>A {@link android.widget.Spinner} is a widget that allows the user to select an item from a group.
-It is similar to a dropdown list and will allow scrolling when the
-list exceeds the available vertical space on the screen.</p>
+<p>{@link android.widget.Spinner} is a widget similar to a drop-down list for selecting items.</p>
+<p>In this tutorial, you'll create
+a simple spinner widget that displays a list of planets. When one is selected, a toast message
+will display the selected item.</p>
<ol>
- <li>Start a new project/Activity called HelloSpinner.</li>
- <li>Open the layout file.
- Make it like so:
+ <li>Start a new project named <em>HelloSpinner</em>.</li>
+ <li>Open the <code>res/layout/main.xml</code> file and insert the following:
<pre>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
@@ -19,41 +19,34 @@
android:padding="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
-
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
- android:text="Please select a planet:"
+ android:text="@string/planet_prompt"
/>
-
<Spinner
android:id="@+id/spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:drawSelectorOnTop="true"
android:prompt="@string/planet_prompt"
/>
-
</LinearLayout>
</pre>
- <p>Notice that the Spinner's <code>android:prompt</code> is a string resource. In
- this case, Android does not allow it to be a string, it must be a reference to a resource.
- So...</p>
+ <p>Notice that the {@link android.widget.TextView}'s <code>android:text</code> attribute and the
+{@link android.widget.Spinner}'s <code>android:prompt</code> attribute both reference the same
+string resource. This text behaves as a title for the widget. When applied to the {@link
+android.widget.Spinner}, the title text will appear
+in the selection dialog that appears upon selecting the widget.</p>
</li>
-<li>Open the strings.xml file in res/values/ and add the following <code><string></code>
-element inside the <code><resources></code> element:
+<li>Create a <code>strings.xml</code> file in <code>res/values/</code> and edit the file to look
+like this:
<pre>
-<string name="planet_prompt">Choose a planet</string>
-</pre>
-</li>
-
-<li>Create a new XML file in res/values/ called arrays.xml. Insert the following:
-<pre>
+<?xml version="1.0" encoding="utf-8"?>
<resources>
-
- <string-array name="planets">
+ <string name="planet_prompt">Choose a planet</string>
+ <string-array name="planets_array">
<item>Mercury</item>
<item>Venus</item>
<item>Earth</item>
@@ -63,35 +56,85 @@
<item>Uranus</item>
<item>Neptune</item>
</string-array>
-
</resources>
</pre>
- <p>This is the list of items (planets) that the user can select from in the Spinner widget.</p>
+ <p>The {@code <string>} element defines the title string referenced by the {@link
+android.widget.TextView} and {@link android.widget.Spinner} in the layout above. The {@code
+<string-array} element defines the list of strings that will be displayed as the list in
+the {@link android.widget.Spinner} widget.</p>
</li>
-<li>Now open the HelloSpinner.java file. Insert the following code into the HelloSpinner class:
+<li>Now open the <code>HelloSpinner.java</code> file and insert the following code for the {@link
+android.app.Activity#onCreate(Bundle) onCreate()} method:
<pre>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
- Spinner s = (Spinner) findViewById(R.id.spinner);
- ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
- this, R.array.planets, android.R.layout.simple_spinner_item);
+ Spinner spinner = (Spinner) findViewById(R.id.spinner);
+ ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
+ this, R.array.planets_array, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- s.setAdapter(adapter);
+ spinner.setAdapter(adapter);
}
</pre>
- <p>That's it. We start by creating a Spinner from our layout. We then create an {@link android.widget.ArrayAdapter}
- that binds each element of our string array to a layout view—we pass <code>createFromResource</code> our Context,
- the array of selectable items and the type of layout we'd like each one bound to. We then call
- <code>setDropDownViewResource()</code> to define the type of layout in which to present the
- entire collection. Finally, we set this Adapter to associate with our Spinner,
- so the string items have a place to go.</p>
+ <p>After the {@code main.xml} layout is set as the content view, the {@link
+android.widget.Spinner} widget is captured from the layout with {@link
+android.app.Activity#findViewById(int)}. The {@link
+android.widget.ArrayAdapter#createFromResource(Context,int,int) createFromResource()} method then
+creates a new {@link android.widget.ArrayAdapter}, which binds each item in the string array
+to the initial appearance for the {@link android.widget.Spinner} (which is how each item will
+appear in the spinner when selected). The {@code
+R.array.planets_array} ID references the {@code string-array} defined above and the
+{@code android.R.layout.simple_spinner_item} ID references a layout for the standard spinner
+appearance, defined by the platform. Then {@link
+android.widget.ArrayAdapter#setDropDownViewResource(int)} is called to define the appearance for
+each item when the widget is opened ({@code simple_spinner_dropdown_item} is
+another standard layout defined by the platform). Finally, the {@link android.widget.ArrayAdapter}
+is set to associate all of its items with the {@link android.widget.Spinner} by calling {@link
+android.widget.AdapterView#setAdapter(T)}.</p>
</li>
-<li>Now run it.</li>
+<li>Now create a nested class that implements {@link
+android.widget.AdapterView.OnItemSelectedListener}. This will provide a callback method that will
+notify your application when an item has been selected from the {@link
+android.widget.Spinner}. Here's what this class should look like:
+<pre>
+public class MyOnItemSelectedListener implements OnItemSelectedListener {
+
+ public void onItemSelected(AdapterView<?> parent,
+ View view, int pos, long id) {
+ Toast.makeText(parent.getContext()), "The planet is " +
+ parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show();
+ }
+
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Do nothing.
+ }
+}
+</pre>
+<p>The {@link android.widget.AdapterView.OnItemSelectedListener} requires the {@link
+android.widget.AdapterView.OnItemSelectedListener#onItemSelected(AdapterView,View,int,long)
+onItemSelected()} and {@link
+android.widget.AdapterView.OnItemSelectedListener#onNothingSelected(AdapterView)
+onNothingSelected()} callback methods. The former is called when an item from the {@link
+android.widget.AdapterView} is selected, in which case, a short {@link android.widget.Toast}
+message displays the selected text; and the latter is called when a selection disappears from the
+{@link android.widget.AdapterView}, which doesn't happen in this case, so it's ignored.</p>
+</li>
+
+<li>Now the {@code MyOnItemSelectedListener} needs to be applied to the {@link
+android.widget.Spinner}. Go back to the {@link android.app.Activity#onCreate(Bundle) onCreate()}
+method and add the following line to the end:
+<pre>
+ spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
+</pre>
+<p>This creates a new anonymous instance of the {@code MyOnItemSelectedListener} and sets it as the
+listener for the {@link android.widget.Spinner}.</p>
+</li>
+
+<li>Run the application.</li>
</ol>
<p>It should look like this:</p>
<img src="images/hello-spinner.png" width="150px" />
diff --git a/docs/html/resources/tutorials/views/hello-tablelayout.jd b/docs/html/resources/tutorials/views/hello-tablelayout.jd
index 83d6f5d..c8c5982 100644
--- a/docs/html/resources/tutorials/views/hello-tablelayout.jd
+++ b/docs/html/resources/tutorials/views/hello-tablelayout.jd
@@ -1,16 +1,15 @@
-page.title=Hello, TableLayout
+page.title=Table Layout
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>A {@link android.widget.TableLayout} is a ViewGroup that
-will lay child View elements into rows and columns.</p>
+<p>{@link android.widget.TableLayout} is a {@link android.view.ViewGroup} that
+displays child {@link android.view.View} elements in rows and columns.</p>
<ol>
- <li>Start a new project/Activity called HelloTableLayout.</li>
- <li>Open the layout file.
- Make it like so:
+ <li>Start a new project named <em>HelloTableLayout</em>.</li>
+ <li>Open the <code>res/layout/main.xml</code> file and insert the following:
<pre>
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
@@ -89,21 +88,28 @@
</TableRow>
</TableLayout>
</pre>
-<p>Notice how this resembles the structure of an HTML table. <code>TableLayout</code> is like the
-<code>table</code> element; <code>TableRow</code> is like a <code>tr</code> element; but for our cells like
-the html <code>td</code> element, we can use any kind of View. Here, we use <code>TextView</code> for the cells.</p>
+<p>Notice how this resembles the structure of an HTML table. The {@link android.widget.TableLayout}
+element is like the HTML <code><table></code> element; {@link android.widget.TableRow} is like
+a <code>><tr>></code> element;
+but for the cells, you can use any kind of {@link android.view.View} element. In this example, a
+{@link android.widget.TextView} is used for each cell. In between some of the rows, there is also a
+basic {@link android.view.View}, which is used to draw a horizontal line.</p>
</li>
-<li>Make sure your Activity loads this layout in the <code>onCreate()</code> method:
+<li>Make sure your <em>HelloTableLayout</em> Activity loads this layout in the
+{@link android.app.Activity#onCreate(Bundle) onCreate()} method:
<pre>
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
</pre>
-<p><code>R.layout.main</code> refers to the <code>main.xml</code> layout file.</p>
+<p>The {@link android.app.Activity#setContentView(int)} method loads the
+layout file for the {@link android.app.Activity}, specified by the resource
+ID — <code>R.layout.main</code> refers to the <code>res/layout/main.xml</code> layout
+file.</p>
</li>
-<li>Run it.</li>
+<li>Run the application.</li>
</ol>
<p>You should see the following:</p>
<img src="images/hello-tablelayout.png" width="150px" />
diff --git a/docs/html/resources/tutorials/views/hello-tabwidget.jd b/docs/html/resources/tutorials/views/hello-tabwidget.jd
index 8424616..199ceef 100644
--- a/docs/html/resources/tutorials/views/hello-tabwidget.jd
+++ b/docs/html/resources/tutorials/views/hello-tabwidget.jd
@@ -1,14 +1,72 @@
-page.title=Hello, TabWidget
+page.title=Tab Layout
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>A {@link android.widget.TabWidget} offers the ability to easily draw an interface that uses
-tabs to navigate between different views.</p>
+<p>To create a tabbed UI, you need to use a {@link android.widget.TabHost} and a {@link
+android.widget.TabWidget}. The {@link android.widget.TabHost} must be the root node for the layout,
+which contains both the {@link android.widget.TabWidget} for displaying the tabs and a {@link
+android.widget.FrameLayout} for displaying the tab content.</p>
+
+<p>You can implement your tab content in one of two ways: use the tabs to swap
+{@link android.view.View}s within the same {@link android.app.Activity}, or use the tabs to change
+between entirely separate activities. Which method you want for your application will depend on your
+demands, but if each tab provides a distinct user activity, then it probably makes sense to use
+a separate {@link android.app.Activity} for each tab, so that you can better manage the application
+in discrete groups, rather than one massive application and layout.</p>
+
+<p>In this tutorial, you'll create a tabbed UI that uses a separate {@link
+android.app.Activity} for each tab.</p>
<ol>
- <li>Start a new project/Activity called HelloTabWidget.</li>
- <li>Open the layout file and make it like so:</li>
+ <li>Start a new project named <em>HelloTabWidget</em>.</li>
+ <li>First, create three separate {@link android.app.Activity} classes in your project:
+<code>ArtistsActivity</code>, <code>AlbumsActivity</code>, and <code>SongsActivity</code>. These
+will each represent a separate tab. For now, make each one display a simple message using a {@link
+android.widget.TextView}. For example:
+<pre>
+public class ArtistsActivity extends Activity {
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ TextView textview = new TextView(this);
+ textview.setText("This is the Artists tab");
+ setContentView(textview);
+ }
+}
+</pre>
+ <p>Notice that this doesn't use a layout file. Just create a {@link
+android.widget.TextView}, give it some text and set that as the content. Duplicate this for
+each of the three activities.</p>
+
+ <li>You're going to need an icon for each of your tabs. And for each one, you should create an
+image for two different states: one for when the tab is selected, and one for when it is not. The
+general design recommendation is for the selected tab icon to be a darker color (grey), and the
+non-selected icon to be lighter (white). For example:
+ <p>
+ <img src="images/ic_tab_artists_white.png" title="ic_tab_artists_white.png" alt="" />
+ <img src="images/ic_tab_artists_grey.png" title="ic_tab_artists_grey.png" alt="" />
+ </p>
+ <p>Copy these images for use in this tutorial. Save them into your project
+<code>res/drawable/</code> directory. You now need to create a {@link
+android.graphics.drawable.Drawable} with XML that specifies which image
+to use for each state. Create a new file in <code>res/drawable/</code> named
+<code>ic_tab_artists.xml</code> and insert the following:</p>
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- When selected, use grey -->
+ <item android:drawable="@drawable/ic_tab_artists_grey"
+ android:state_selected="true" />
+ <!-- When not selected, use white-->
+ <item android:drawable="@drawable/ic_tab_artists_white" />
+</selector>
+</pre>
+ <p>This is an XML definition for a {@link android.graphics.drawable.Drawable}, which you will
+reference as the image for a tab. When the image state changes, the image will automatically
+switch between the images defined here.</p>
+
+ <li>Open the <code>res/layout/main.xml</code> file and insert the following:
<pre>
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
@@ -18,7 +76,8 @@
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
- android:layout_height="fill_parent">
+ android:layout_height="fill_parent"
+ android:padding="5dp">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="fill_parent"
@@ -26,94 +85,111 @@
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <TextView
- android:id="@+id/textview1"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:text="this is a tab" />
- <TextView
- android:id="@+id/textview2"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:text="this is another tab" />
- <TextView
- android:id="@+id/textview3"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:text="this is a third tab" />
- </FrameLayout>
+ android:layout_height="fill_parent"
+ android;padding="5dp" />
</LinearLayout>
</TabHost>
</pre>
- <p>Here, we've created a {@link android.widget.TabHost} that contains the entire layout of the Activity.
- A TabHost requires two descendant elements: a {@link android.widget.TabWidget} and a {@link android.widget.FrameLayout}.
- In order to properly layout these elements, we've put them inside a vertical {@link android.widget.LinearLayout}.
- The FrameLayout is where we keep the content that will change with each tab. Each child in the FrameLayout will
- be associated with a different tab.
- In this case, each tab simply shows a different {@link android.widget.TextView} with some text. </p>
- <p>Notice that the TabWidget and the FrameLayout elements have specific <code>android</code> namespace IDs. These are necessary
- so that the TabHost can automatically retireve references to them, populate the TabWidget with the tabs that we'll define
- in our code, and swap the views in the FrameLayout. We've also defined our own IDs for each TextView, which we'll use to
- associate each tab with the view that it should reveal.</p>
- <p>Of course, you can
- make these child views as large as complex as you'd like — instead of the TextView elements,
- you could start with other layout views and build a unique layout hierarchy for each tab.</p>
+ <p>This is the layout that will display the tabs and provide navigation between each {@link
+ android.app.Activity} created above.</p>
+ <p>The {@link android.widget.TabHost} requires that a {@link android.widget.TabWidget} and a
+{@link android.widget.FrameLayout} both live somewhere within it. To position the {@link
+android.widget.TabWidget} and {@link android.widget.FrameLayout} vertically, a {@link
+android.widget.LinearLayout} is used. The {@link android.widget.FrameLayout} is where the content
+for each tab goes, which is empty now because the {@link android.widget.TabHost} will automatically
+embed each {@link android.app.Activity} within it.</p>
+ <p>Notice that the {@link android.widget.TabWidget} and the {@link android.widget.FrameLayout}
+ elements have the IDs {@code tabs} and {@code tabcontent}, respectively. These names
+ must be used so that the {@link android.widget.TabHost} can retrieve references to each of
+ them. It expects exactly these names.</p>
</li>
- <li>Now we'll add our code. Open HelloTabWidget.java and make it a <code>TabActivity</code>.
- <p>By default, Eclipse creates a class that extends <code>Activity</code>. Change it to
- extend <code>TabActivity</code>:</p>
- <pre>
+
+ <li>Now open <code>HelloTabWidget.java</code> and make it extend {@link
+ android.app.TabActivity}:</p>
+<pre>
public class HelloTabWidget extends TabActivity {
-</pre>
+</pre>
</li>
- <li>Now fill in the the <code>onCreate</code> method like this:
- <pre>
+ <li>Use the following code for the {@link android.app.Activity#onCreate(Bundle) onCreate()}
+ method:
+<pre>
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
- mTabHost = getTabHost();
-
- mTabHost.addTab(mTabHost.newTabSpec("tab_test1").setIndicator("TAB 1").setContent(R.id.textview1));
- mTabHost.addTab(mTabHost.newTabSpec("tab_test2").setIndicator("TAB 2").setContent(R.id.textview2));
- mTabHost.addTab(mTabHost.newTabSpec("tab_test3").setIndicator("TAB 3").setContent(R.id.textview3));
-
- mTabHost.setCurrentTab(0);
+ Resources res = getResources(); // Resource object to get Drawables
+ TabHost tabHost = getTabHost(); // The activity TabHost
+ TabHost.TabSpec spec; // Resusable TabSpec for each tab
+ Intent intent; // Reusable Intent for each tab
+
+ // Create an Intent to launch an Activity for the tab (to be reused)
+ intent = new Intent().setClass(this, ArtistsActivity.class);
+
+ // Initialize a TabSpec for each tab and add it to the TabHost
+ spec = tabHost.newTabSpec("artists").setIndicator("Artists",
+ res.getDrawable(R.drawable.ic_tab_artists))
+ .setContent(intent);
+ tabHost.addTab(spec);
+
+ // Do the same for the other tabs
+ intent = new Intent().setClass(this, AlbumsActivity.class);
+ spec = tabHost.newTabSpec("albums").setIndicator("Albums",
+ res.getDrawable(R.drawable.ic_tab_albums))
+ .setContent(intent);
+ mTabHost.addTab(spec);
+
+ intent = new Intent().setClass(this, SongsActivity.class);
+ spec = tabHost.newTabSpec("songs").setIndicator("Songs",
+ res.getDrawable(R.drawable.ic_tab_songs))
+ .setContent(intent);
+ tabHost.addTab(spec);
+
+ tabHost.setCurrentTab(getIntent());
}
</pre>
- <p>As usual, we start by setting our layout.</p>
- <p>We then call the TabActivity method <code>getTabHost()</code>,
- which returns us a reference to the TabHost we created in our layout. Upon our TabHost, we call <code>addTab()</code>
- for each of the tabs that we want to add to the TabWidget. Each time we call this, we pass a
- {@link android.widget.TabHost.TabSpec} that we build on the fly, and with it, chain together two necessary methods:
- <code>setIndicator()</code> to set the text for the tab button, and <code>setContent()</code> to define
- which View we want to associate with the tab and reveal when pressed. Our indicator is just a text string and
- our content is an ID reference to the TextView elements we inserted in the FrameLayout.</p>
- <p>At the end, we call <code>setCurrentTab()</code> to define which tab should be opened by default. The tabs
- are saved like a zero-based array, so to open the first tab, we pass zero (<var>0</var>).</p>
+ <p>This sets up each tab with their text and icon, and assigns each one an {@link
+android.app.Activity}.</p>
+ <p>A reference to the {@link android.widget.TabHost} is first captured with {@link
+android.app.TabActivity#getTabHost()}. Then, for
+each tab, a {@link android.widget.TabHost.TabSpec} is created to define the tab properties. The
+{@link android.widget.TabHost#newTabSpec(String)} method creates a new {@link
+android.widget.TabHost.TabSpec} identified by the given string tag. For each
+{@link android.widget.TabHost.TabSpec}, {@link
+android.widget.TabHost.TabSpec#setIndicator(CharSequence,Drawable)} is called to set the text and
+icon for the tab, and {@link android.widget.TabHost.TabSpec#setContent(Intent)} is called to specify
+the {@link android.content.Intent} to opens the appropriate {@link android.app.Activity}. Each
+{@link android.widget.TabHost.TabSpec} is then added to the {@link android.widget.TabHost} by
+calling {@link android.widget.TabHost#addTab(TabHost.TabSpec)}.</p>
+
+ <p>At the very end, {@link
+ android.widget.TabHost#setCurrentTab(int)} opens the tab to be displayed by default, specified
+ by the index position of the tab.</p>
+
+ <p>Notice that not once was the {@link android.widget.TabWidget} object referenced. This is
+ because a {@link android.widget.TabWidget} must always be a child of a {@link
+ android.widget.TabHost}, which is what you use for almost all interaction with the tabs. So when
+ a tab is added to the {@link android.widget.TabHost}, it's automatically added to the child
+ {@link android.widget.TabWidget}.</p>
</li>
- <li>To clean-up the presentation a bit more, let's remove the window title that appears at the top of the layout.
- Android includes a theme that removes that title for us. To add it, open the Android Manifest file and add
- the <var>NoTitleBar</var> theme to the <code><application></code> tag. It should end up like this:
- <pre>
-<application android:icon="@drawable/icon" android:theme="@android:style/Theme.NoTitleBar">
+
+ <li>Now open the Android Manifest file and add the <code>NoTitleBar</code> theme to the
+<em>HelloTabWidget</em>'s
+ <code><activity></code> tag. This will remove the default application title from the top
+ of the layout, leaving more space for the tabs, which effectively operate as their own titles.
+ The <code><activity></code> tag should look like this:
+<pre>
+<activity android:name=".HelloTabWidget" android:label="@string/app_name"
+ android:theme="@android:style/Theme.NoTitleBar">
</pre>
</li>
- <li>That's it. Run your application.</li>
+ <li>Run the application.</li>
</ol>
<p>Your application should look like this:</p>
<img src="images/hello-tabwidget.png" width="150px" />
-<div class="special"><p>You can include icons in your tabs by passing a
-{@link android.graphics.drawable.Drawable} when you call <code>setIndicator()</code>. Here's an example
-that uses a Drawable created from an image in the project resources:</p>
-<pre>setIndicator("TAB 1", getResources().getDrawable(R.drawable.tab_icon))</pre>
-</div>
-
<h3>References</h3>
<ul>
<li>{@link android.widget.TabWidget}</li>
diff --git a/docs/html/resources/tutorials/views/hello-timepicker.jd b/docs/html/resources/tutorials/views/hello-timepicker.jd
index 1a6c8f9..cf16c8e 100644
--- a/docs/html/resources/tutorials/views/hello-timepicker.jd
+++ b/docs/html/resources/tutorials/views/hello-timepicker.jd
@@ -1,106 +1,97 @@
-page.title=Hello, TimePicker
+page.title=Time Picker
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>A {@link android.widget.TimePicker} is a widget that allows the
-user to select the time by hour, minute and AM or PM.</p>
+<p>To provide a widget for selecting a time, use the {@link android.widget.TimePicker}
+widget, which allows the user to select the hour and minute in a familiar interface.</p>
+<p>In this tutorial, you'll create a {@link android.app.TimePickerDialog}, which presents the
+time picker in a floating dialog box at the press of a button. When the time is set by
+the user, a {@link android.widget.TextView} will update with the new date.</p>
<ol>
- <li>Start a new project/Activity called HelloTimePicker.</li>
- <li>Open the layout file and make it like so:
+ <li>Start a new project named <em>HelloTimePicker</em>.</li>
+ <li>Open the <code>res/layout/main.xml</code> file and insert the following:
<pre>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
-
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""/>
-
<Button android:id="@+id/pickTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Change the time"/>
-
</LinearLayout>
</pre>
- <p>For the layout, we're using a vertical LinearLayout, with a {@link android.widget.TextView} that
- will display the time and a {@link android.widget.Button} that will initiate the
- {@link android.widget.TimePicker} dialog.
- With this layout, the TextView will sit above the Button.
- The text value in the TextView is set empty, as it will be filled by our Activity
- with the current time.</p>
- </li>
+<p>This is a basic {@link android.widget.LinearLayout} with a {@link android.widget.TextView}
+that will display the time and a {@link android.widget.Button} that will open the {@link
+android.app.TimePickerDialog}.</p>
+ </li>
- <li>Open HelloTimePicker.java. Insert the following to the HelloTimePicker class:
+ <li>Open <code>HelloTimePicker.java</code> and insert the following class members:
<pre>
-private TextView mTimeDisplay;
-private Button mPickTime;
+ private TextView mTimeDisplay;
+ private Button mPickTime;
-private int mHour;
-private int mMinute;
+ private int mHour;
+ private int mMinute;
-static final int TIME_DIALOG_ID = 0;
-
-@Override
-protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
- // capture our View elements
- mTimeDisplay = (TextView) findViewById(R.id.timeDisplay);
- mPickTime = (Button) findViewById(R.id.pickTime);
-
- // add a click listener to the button
- mPickTime.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- showDialog(TIME_DIALOG_ID);
- }
- });
-
- // get the current time
- final Calendar c = Calendar.getInstance();
- mHour = c.get(Calendar.HOUR_OF_DAY);
- mMinute = c.get(Calendar.MINUTE);
-
- // display the current date
- updateDisplay();
-}
+ static final int TIME_DIALOG_ID = 0;
</pre>
-<p class="note"><strong>Tip:</strong> Press Ctrl(or Cmd) + Shift + O to import all needed packages.</p>
- <p>We start by instantiating variables for our View elements and time fields.
- The <code>TIME_DIALOG_ID</code> is a static integer that uniquely identifies the dialog. In the
- <code>onCreate()</code> method, we get prepared by setting the layout and capturing the View elements.
- We then set an on-click listener for the Button, so that when it is clicked, it will
- show our TimePicker dialog. The <code>showDialog()</code> method will perform a callback
- to our Activity. (We'll define this callback in the next section.) We then create an
- instance of {@link java.util.Calendar} and get the current hour and minute. Finally, we call
- <code>updateDisplay()</code>—our own method that will fill the TextView with the time.</p>
-</li>
-
-<li>After the <code>onCreate()</code> method, add the <code>onCreateDialog()</code> callback method:
+<p>This declares variables for the layout elements and time fields.
+The <code>TIME_DIALOG_ID</code> is a static integer that uniquely identifies the dialog.</p>
+ </li>
+ <li>Now insert the following code for the {@link android.app.Activity#onCreate(Bundle) onCreate()}
+method:
<pre>
-@Override
-protected Dialog onCreateDialog(int id) {
- switch (id) {
- case TIME_DIALOG_ID:
- return new TimePickerDialog(this,
- mTimeSetListener, mHour, mMinute, false);
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ // capture our View elements
+ mTimeDisplay = (TextView) findViewById(R.id.timeDisplay);
+ mPickTime = (Button) findViewById(R.id.pickTime);
+
+ // add a click listener to the button
+ mPickTime.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ showDialog(TIME_DIALOG_ID);
+ }
+ });
+
+ // get the current time
+ final Calendar c = Calendar.getInstance();
+ mHour = c.get(Calendar.HOUR_OF_DAY);
+ mMinute = c.get(Calendar.MINUTE);
+
+ // display the current date
+ updateDisplay();
}
- return null;
-}
</pre>
- <p>This is passed the identifier we gave <code>showDialog()</code> and initializes
- the TimePicker to the time we retrieved from our Calendar instance. It will be called by
- <code>showDialog()</code>.</p>
+
+<p>First, the content is set to the <code>main.xml</code> layout and then the {@link
+android.widget.TextView} and {@link android.widget.Button} are captured with {@link
+android.app.Activity#findViewById(int)}.
+Then an {@link android.view.View.OnClickListener} is created for the {@link android.widget.Button},
+so that when clicked, it will call {@link
+android.app.Activity#showDialog(int)}, passing the unique integer ID for the time picker
+dialog. Using {@link android.app.Activity#showDialog(int)} allows the {@link
+android.app.Activity} to manage the life-cycle of the dialog and will call the {@link
+android.app.Activity#onCreateDialog(int)} callback method to request the {@link android.app.Dialog}
+that should be displayed (which you'll define later). After the on-click listener is set, a new
+{@link java.util.Calendar} is created to get the current hour and minute. Finally, the
+private <code>updateDisplay()</code> method is called in order to fill the {@link
+android.widget.TextView} with the current time.</p>
</li>
-<li>Now add our <code>updateDisplay()</code> method:
+<li>Add the <code>updateDisplay()</code> and <code>pad()</code> methods:
<pre>
// updates the time we display in the TextView
private void updateDisplay() {
@@ -109,13 +100,22 @@
.append(pad(mHour)).append(":")
.append(pad(mMinute)));
}
+
+private static String pad(int c) {
+ if (c >= 10)
+ return String.valueOf(c);
+ else
+ return "0" + String.valueOf(c);
+}
</pre>
- <p>This simply takes our member fields for the time and inserts them in
- the <code>mTimeDisplay</code> TextView. Note that we call a new method, <code>pad()</code>,
- on the hour and minute. (We'll create this method in the last step.)</p>
+<p>The <code>updateDisplay()</code> method uses the member fields for the time and inserts them in
+the <code>mTimeDisplay</code> {@link android.widget.TextView}. The <code>pad()</code> method returns
+the appropriate string representation of the hour or minute—it will prefix a zero to the
+number if it's a single digit.</p>
</li>
-<li>Next, add a listener to be called when the time is reset:
+<li>Add a class member for a {@link android.app.TimePickerDialog.OnTimeSetListener} that will be
+called when the user sets a new time:
<pre>
// the callback received when the user "sets" the time in the dialog
private TimePickerDialog.OnTimeSetListener mTimeSetListener =
@@ -127,24 +127,31 @@
}
};
</pre>
- <p>Now when the user is done setting the time (clicks the "Set" button), we update our member fields with
- the new time and update our TextView.</p>
-</li>
-<li>Finally, add the <code>pad()</code> method that we called from the <code>updateDisplay()</code>:
-<pre>
-private static String pad(int c) {
- if (c >= 10)
- return String.valueOf(c);
- else
- return "0" + String.valueOf(c);
-}
-</pre>
- <p>This method returns the appropriate String representation of the hour or minute.
- It will prefix a zero to the number if it's a single digit.
- </p>
+<p>When the user is done setting the time (clicks the "Set" button), the
+<code>onTimeSet()</code> method is called and it updates the member fields with
+the new time and updates the layout's {@link android.widget.TextView}.</p>
</li>
-<li>Now run it.</li>
+<li>Add the {@link android.app.Activity#onCreateDialog(int)} callback method:
+<pre>
+@Override
+protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case TIME_DIALOG_ID:
+ return new TimePickerDialog(this,
+ mTimeSetListener, mHour, mMinute, false);
+ }
+ return null;
+}
+</pre>
+<p>This is an {@link android.app.Activity} callback that is passed the identifier you passed to
+{@link android.app.Activity#showDialog(int)}, in the {@link android.widget.Button}'s on-click
+listener. When the ID matches, this initializes the {@link android.app.TimePickerDialog} with the
+member variables initialized at the end of <code>onCreate()</code> and the {@link
+android.app.TimePickerDialog.OnTimeSetListener} created in the previous step.</p>
+</li>
+
+<li>Run the application.</li>
</ol>
<p>When you press the "Change the time" button, you should see the following:</p>
<img src="images/hello-timepicker.png" width="150px" />
@@ -152,6 +159,7 @@
<h3>References</h3>
<ol>
<li>{@link android.widget.TimePicker}</li>
+ <li>{@link android.app.TimePickerDialog.OnTimeSetListener}</li>
<li>{@link android.widget.Button}</li>
<li>{@link android.widget.TextView}</li>
<li>{@link java.util.Calendar}</li>
diff --git a/docs/html/resources/tutorials/views/hello-webview.jd b/docs/html/resources/tutorials/views/hello-webview.jd
index c4388ea..17b2646 100644
--- a/docs/html/resources/tutorials/views/hello-webview.jd
+++ b/docs/html/resources/tutorials/views/hello-webview.jd
@@ -1,63 +1,72 @@
-page.title=Hello, WebView
+page.title=Web View
parent.title=Hello, Views
parent.link=index.html
@jd:body
-<p>A {@link android.webkit.WebView} allows you to create your own web browser Activity. In this tutorial,
-we'll create a simple Activity that can view web pages.</p>
+<p>{@link android.webkit.WebView} allows you to create your own window for viewing web pages (or even
+develop a complete browser). In this tutorial, you'll create a simple {@link android.app.Activity}
+that can view and navigate web pages.</p>
<ol>
- <li>Create a new project/Activity called HelloWebView.</li>
- <li>Open the layout file. Insert a WebView so it looks like so:
+ <li>Create a new project named <em>HelloWebView</em>.</li>
+ <li>Open the <code>res/layout/main.xml</code> file and insert the following:
<pre>
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <WebView
- android:id="@+id/webview"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- />
-
-</LinearLayout>
-</pre></li>
-
- <li>Now open the HelloWebView.java file.
- At the top of the class, instantiate a WebView object:
-<pre>WebView webview;</pre>
- <p> Then add the following at the end of the <code>onCreate()</code> method:</p>
-<pre>
-webview = (WebView) findViewById(R.id.webview);
-webview.getSettings().setJavaScriptEnabled(true);
-webview.loadUrl("http://www.google.com");
+<WebView
+ android:id="@+id/webview"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+/>
</pre>
+ </li>
- <p>This captures the WebView we created in our layout, then requests a
- {@link android.webkit.WebSettings} object and enables JavaScript.
- Then we load a URL.</p></li>
+ <li>Now open the <code>HelloWebView.java</code> file.
+ At the top of the class, declare a {@link android.webkit.WebView} object:
+<pre>WebView mWebView;</pre>
+ <p>Then use the following code for the {@link android.app.Activity#onCreate(Bundle) onCreate()}
+ method:</p>
+<pre>
+public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
- <li>Because we're accessing the internet, we need to add the appropriate
- permissions to the Android manifest file. So open the AndroidManifest.xml file
- and, add the following as a child of the <code><manifest></code> element:
+ mWebView = (WebView) findViewById(R.id.webview);
+ mWebView.getSettings().setJavaScriptEnabled(true);
+ mWebView.loadUrl("http://www.google.com");
+}
+</pre>
+ <p>This initializes the member {@link android.webkit.WebView} with the one from the
+ {@link android.app.Activity} layout; requests a {@link android.webkit.WebSettings} object with
+ {@link android.webkit.WebView#getSettings()}; and enables JavaScript for the {@link
+ android.webkit.WebView} with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)}.
+ Finally, an initial web page is loaded with {@link
+ android.webkit.WebView#loadUrl(String)}.</p>
+ </li>
- <pre><uses-permission android:name="android.permission.INTERNET" /></pre></li>
+ <li>Because this application needs access to the Internet, you need to add the appropriate
+ permissions to the Android manifest file. Open the <code>AndroidManifest.xml</code> file
+ and add the following as a child of the <code><manifest></code> element:
- <li>Now run it.</li>
-</ol>
-<p> You now have the world's simplest web page viewer.
- It's not quite a browser yet. It only loads the page we've requested.</p>
+ <pre><uses-permission android:name="android.permission.INTERNET" /></pre></li>
-<hr/>
+ <li>While you're in the manifest, give some more space for web pages by removing the title
+ bar, with the "NoTitleBar" theme:
+<pre>
+<activity android:name=".HelloGoogleMaps" android:label="@string/app_name"
+ <strong>android:theme="@android:style/Theme.NoTitleBar"</strong>>
+</pre>
+ </li>
-<p>We can load a page, but as soon as we click a link, the default Android web browser
-handles the Intent, instead of our own WebView handling the action. So now we'll
-override the {@link android.webkit.WebViewClient} to enable us to handle our own URL loading.</p>
+ <li>Now run the application.
+ <p>You now have a simplest web page viewer.
+ It's not quite a browser yet because as soon as you click a link, the default Android Browser
+ handles the Intent to view a web page, because this {@link android.app.Activity} isn't
+ technically enabled to do so. Instead of adding an intent filter to view web pages, you can
+ override the {@link android.webkit.WebViewClient} class and enable this {@link
+ android.app.Activity} to handle its own URL requests.</p>
+ </li>
-<ol>
- <li>In the HelloAndroid Activity, add this nested private class:
+ <li>In the <code>HelloAndroid</code> Activity, add this nested class:
<pre>
private class HelloWebViewClient extends WebViewClient {
@Override
@@ -65,42 +74,51 @@
view.loadUrl(url);
return true;
}
-}</pre></li>
+}
+</pre>
+ </li>
+ <li>Then towards the end of the {@link android.app.Activity#onCreate(Bundle)} method, set an
+ instance of the <code>HelloWebViewClient</code> as the {@link android.webkit.WebViewClient}:
+ <pre>mWebView.setWebViewClient(new WebViewClientDemo());</pre>
- <li>Now, in the <code>onCreate()</code> method, set an instance of the <code>HelloWebViewClient</code>
- as our WebViewClient:
- <pre>webview.setWebViewClient(new WebViewClientDemo());</pre>
-
- <p>This line should immediately follow the initialization of our WebView object.</p>
- <p>What we've done is create a WebViewClient that will load any URL selected in our
-WebView in the same WebView. You can see this in the <code>shouldOverrideUrlLoading()</code>
-method, above—it is passed the current WebView and the URL, so all we do
-is load the URL in the given view. Returning <var>true</var> says that we've handled the URL
-ourselves and the event should not bubble-up.</p>
- <p>If you try it again, new pages will now load in the HelloWebView Activity. However, you'll notice that
-we can't navigate back. We need to handle the back button
-on the device, so that it will return to the previous page, rather than exit the application.</p>
+ <p>This line can go anywhere following the initialization of the {@link
+ android.webkit.WebView} object.</p>
+ <p>This creates a {@link android.webkit.WebViewClient} that will load any URL selected from this
+ {@link android.webkit.WebView} into the same {@link android.webkit.WebView}. The
+ {@link android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)} method is passed
+the current {@link android.webkit.WebView} and the URL requested, so all it needs to do is load
+the URL in the given view. Returning <code>true</code> says that the method has handled the URL
+and the event should not propagate (in which case, an Intent would be created that's handled by
+the Browser application).</p>
+ <p>If you run the application again, new pages will now load in this Activity.
+ However, you can't navigate back to previous pages. To do this, you need to handle the BACK
+ button on the device, so that it will return to the previous page, rather than exit the
+ application.</p>
</li>
- <li>To handle the back button key press, add the following method inside the HelloWebView
-Activity:
+ <li>To handle the BACK button key press, add the following method inside the
+ <code>HelloWebView</code> Activity:
<pre>
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if ((keyCode == KeyEvent.KEYCODE_BACK) && webview.canGoBack()) {
- webview.goBack();
+ if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
+ mWebView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
-}</pre>
- <p>The condition uses a {@link android.view.KeyEvent} to check
- whether the key pressed is the BACK button and whether the
- WebView is actually capable of navigating back (if it has a history). If both are
- <em>not</em> true, then we send the event up the chain (and the Activity will close).
- But if both <em>are</em> true, then we call <code>goBack()</code>,
- which will navigate back one step in the history. We then return true to indicate
- that we've handled the event.</p>
+}
+</pre>
+ <p>This {@link android.app.Activity#onKeyDown(int,KeyEvent)} callback method will be called
+ anytime a button is pressed while in the Activity. The condition inside uses the {@link
+ android.view.KeyEvent} to check whether the key pressed is the BACK button and whether the
+ {@link android.webkit.WebView} is actually capable of navigating back (if it has a history). If
+ both are true, then the {@link android.webkit.WebView#goBack()} method is called,
+ which will navigate back one step in the {@link android.webkit.WebView}
+ history.Returning <code>true</code> indicates that the event has been handled. If this condition
+ is not met, then the event is sent back to the system.</p>
</li>
+<li>Run the application again. You'll now be able to follow links and navigate back through the
+page history.</li>
</ol>
<p>When you open the application, it should look like this:</p>
<img src="images/hello-webview.png" width="150px" />
@@ -111,8 +129,3 @@
<li>{@link android.webkit.WebViewClient}</li>
<li>{@link android.view.KeyEvent}</li>
</ul>
-
-
-
-
-
diff --git a/docs/html/resources/tutorials/views/images/android_focused.png b/docs/html/resources/tutorials/views/images/android_focused.png
new file mode 100644
index 0000000..f84d0fe
--- /dev/null
+++ b/docs/html/resources/tutorials/views/images/android_focused.png
Binary files differ
diff --git a/docs/html/resources/tutorials/views/images/android_normal.png b/docs/html/resources/tutorials/views/images/android_normal.png
new file mode 100644
index 0000000..94a7084
--- /dev/null
+++ b/docs/html/resources/tutorials/views/images/android_normal.png
Binary files differ
diff --git a/docs/html/resources/tutorials/views/images/android_pressed.png b/docs/html/resources/tutorials/views/images/android_pressed.png
new file mode 100644
index 0000000..fe81ff9
--- /dev/null
+++ b/docs/html/resources/tutorials/views/images/android_pressed.png
Binary files differ
diff --git a/docs/html/resources/tutorials/views/images/hello-autocomplete.png b/docs/html/resources/tutorials/views/images/hello-autocomplete.png
old mode 100755
new mode 100644
index e1fd80d..0b3e680
--- a/docs/html/resources/tutorials/views/images/hello-autocomplete.png
+++ b/docs/html/resources/tutorials/views/images/hello-autocomplete.png
Binary files differ
diff --git a/docs/html/resources/tutorials/views/images/hello-formstuff.png b/docs/html/resources/tutorials/views/images/hello-formstuff.png
old mode 100755
new mode 100644
index 3b4bf54..949319f
--- a/docs/html/resources/tutorials/views/images/hello-formstuff.png
+++ b/docs/html/resources/tutorials/views/images/hello-formstuff.png
Binary files differ
diff --git a/docs/html/resources/tutorials/views/images/hello-listview.png b/docs/html/resources/tutorials/views/images/hello-listview.png
old mode 100755
new mode 100644
index a1cf7aa..165b1ac
--- a/docs/html/resources/tutorials/views/images/hello-listview.png
+++ b/docs/html/resources/tutorials/views/images/hello-listview.png
Binary files differ
diff --git a/docs/html/resources/tutorials/views/images/hello-mapview.png b/docs/html/resources/tutorials/views/images/hello-mapview.png
old mode 100755
new mode 100644
index 0956760..6bd9740
--- a/docs/html/resources/tutorials/views/images/hello-mapview.png
+++ b/docs/html/resources/tutorials/views/images/hello-mapview.png
Binary files differ
diff --git a/docs/html/resources/tutorials/views/images/hello-tabwidget.png b/docs/html/resources/tutorials/views/images/hello-tabwidget.png
index 6a52356..6580c5b 100644
--- a/docs/html/resources/tutorials/views/images/hello-tabwidget.png
+++ b/docs/html/resources/tutorials/views/images/hello-tabwidget.png
Binary files differ
diff --git a/docs/html/resources/tutorials/views/images/hello-webview.png b/docs/html/resources/tutorials/views/images/hello-webview.png
old mode 100755
new mode 100644
index 283ce7d..248c6d4
--- a/docs/html/resources/tutorials/views/images/hello-webview.png
+++ b/docs/html/resources/tutorials/views/images/hello-webview.png
Binary files differ
diff --git a/docs/html/resources/tutorials/views/images/ic_tab_artists_grey.png b/docs/html/resources/tutorials/views/images/ic_tab_artists_grey.png
new file mode 100644
index 0000000..9baa30e
--- /dev/null
+++ b/docs/html/resources/tutorials/views/images/ic_tab_artists_grey.png
Binary files differ
diff --git a/docs/html/resources/tutorials/views/images/ic_tab_artists_white.png b/docs/html/resources/tutorials/views/images/ic_tab_artists_white.png
new file mode 100644
index 0000000..3b010d5
--- /dev/null
+++ b/docs/html/resources/tutorials/views/images/ic_tab_artists_white.png
Binary files differ
diff --git a/docs/html/resources/tutorials/views/index.jd b/docs/html/resources/tutorials/views/index.jd
index 2cb5d3a..6ea7683 100644
--- a/docs/html/resources/tutorials/views/index.jd
+++ b/docs/html/resources/tutorials/views/index.jd
@@ -3,74 +3,95 @@
<style>
.view {float:left; margin:10px; font-size:120%; font-weight:bold;}
-.view img {border:1px solid black; margin:5px 0 0; padding:5px;}
+#jd-content .view img {border:1px solid black; margin:8px 0 0 0; padding:5px;}
</style>
-<p>This collection of "Hello World"-style tutorials is designed
-to get you quickly started with common Android Views and widgets. The aim is to let you copy and paste
-these kinds of boring bits so you can focus on developing the code that makes your Android application rock.
-Of course, we'll discuss some of the given code so that it all makes sense.</p>
+<p>This is a collection of "Hello World"-style tutorials designed
+to get you started quickly with common Android layouts and widgets.</p>
-<p>Note that a certain amount of knowledge is assumed for these tutorials. If you haven't
-completed the <a href="{@docRoot}resources/tutorials/hello-world.html">Hello, World</a> tutorial,
-please do so—it will teach you many things you should know about basic
-Android development and Eclipse features. More specifically, you should know:</p>
+<p>A certain amount of knowledge is assumed for these tutorials. Before you start,
+you should have completed the <a href="{@docRoot}resources/tutorials/hello-world.html">Hello,
+World</a> tutorial—it will teach you several things about basic
+Android development. More specifically, you should know:</p>
<ul>
- <li>How to create a new Android project.</li>
- <li>The basic structure of an Android project (resource files, layout files, etc.).</li>
- <li>The essential components of an {@link android.app.Activity}.</li>
- <li>How to build and run a project.</li>
+ <li>How to create an Android project and run it</li>
+ <li>The basic structure of an Android project (resource files, layout files, etc.)</li>
+ <li>The basic components of an {@link android.app.Activity}</li>
</ul>
-<p>Please, also notice that, in order to make these tutorials simple, some may
-not convey the better Android coding practices. In particular, many of them
-use hard-coded strings in the layout files—the better practice is to reference strings from
-your strings.xml file.</p>
-<p>With this knowledge, you're ready to begin, so take your pick.</p>
-<div>
+<p class="note"><strong>Note:</strong> In order to make these tutorials as simple as possible,
+some code may not conform to best practices for coding Android applications. In particular,
+hard-coded strings are used in places, when the better practice is to reference strings from a
+<code>res/values/strings.xml</code> resource file.</p>
+
+<p class="note"><strong>Tip:</strong> After you have pasted sample code into an Eclipse project,
+press <strong>Ctrl (or Cmd) + Shift + O</strong> to import the required packages.</p>
+
+<h2>Layouts</h2>
<div class="view">
-<a href="hello-linearlayout.html">LinearLayout</a><br/>
-<a href="hello-linearlayout.html"><img src="images/hello-linearlayout.png" height="285" width="200" /></a>
-</div>
-<div class="view">
-<a href="hello-relativelayout.html">RelativeLayout</a><br/>
-<a href="hello-relativelayout.html"><img src="images/hello-relativelayout.png" height="285" width="200" /></a>
-</div>
-<div class="view">
-<a href="hello-tablelayout.html">TableLayout</a><br/>
-<a href="hello-tablelayout.html"><img src="images/hello-tablelayout.png" height="285" width="200" /></a>
+<a href="hello-linearlayout.html">Linear Layout</a><br/>
+<a href="hello-linearlayout.html"><img src="images/hello-linearlayout.png" height="285" width="200"
+/></a>
</div>
<div class="view">
-<a href="hello-datepicker.html">DatePicker</a><br/>
-<a href="hello-datepicker.html"><img src="images/hello-datepicker.png" height="285" width="200" /></a>
+<a href="hello-relativelayout.html">Relative Layout</a><br/>
+<a href="hello-relativelayout.html"><img src="images/hello-relativelayout.png" height="285"
+width="200" /></a>
</div>
<div class="view">
-<a href="hello-timepicker.html">TimePicker</a><br/>
-<a href="hello-timepicker.html"><img src="images/hello-timepicker.png" height="285" width="200" /></a>
+<a href="hello-tablelayout.html">Table Layout</a><br/>
+<a href="hello-tablelayout.html"><img src="images/hello-tablelayout.png" height="285" width="200"
+/></a>
</div>
+
+<div class="view">
+<a href="hello-gridview.html">Grid View</a><br/>
+<a href="hello-gridview.html"><img src="images/hello-gridview.png" height="285" width="200" /></a>
+</div>
+
+<div class="view">
+<a href="hello-tabwidget.html">Tab Layout</a><br/>
+<a href="hello-tabwidget.html"><img src="images/hello-tabwidget.png" height="285" width="200" /></a>
+</div>
+
+<div class="view">
+<a href="hello-listview.html">List View</a><br/>
+<a href="hello-listview.html"><img src="images/hello-listview.png" height="285" width="200" /></a>
+</div>
+
+<p style="clear:left"> </p>
+
+<h2>Widgets & Other Views</h2>
+
+<div class="view">
+<a href="hello-datepicker.html">Date Picker</a><br/>
+<a href="hello-datepicker.html"><img src="images/hello-datepicker.png" height="285" width="200"
+/></a>
+</div>
+
+<div class="view">
+<a href="hello-timepicker.html">Time Picker</a><br/>
+<a href="hello-timepicker.html"><img src="images/hello-timepicker.png" height="285" width="200"
+/></a>
+</div>
+
<div class="view">
<a href="hello-formstuff.html">Form Stuff</a><br/>
<a href="hello-formstuff.html"><img src="images/hello-formstuff.png" height="285" width="200" /></a>
</div>
+
<div class="view">
<a href="hello-spinner.html">Spinner</a><br/>
<a href="hello-spinner.html"><img src="images/hello-spinner.png" height="285" width="200" /></a>
</div>
<div class="view">
-<a href="hello-autocomplete.html">AutoComplete</a><br/>
-<a href="hello-autocomplete.html"><img src="images/hello-autocomplete.png" height="285" width="200" /></a>
-</div>
-<div class="view">
-<a href="hello-listview.html">ListView</a><br/>
-<a href="hello-listview.html"><img src="images/hello-listview.png" height="285" width="200" /></a>
-</div>
-<div class="view">
-<a href="hello-gridview.html">GridView</a><br/>
-<a href="hello-gridview.html"><img src="images/hello-gridview.png" height="285" width="200" /></a>
+<a href="hello-autocomplete.html">Auto Complete</a><br/>
+<a href="hello-autocomplete.html"><img src="images/hello-autocomplete.png" height="285" width="200"
+/></a>
</div>
<div class="view">
@@ -79,40 +100,20 @@
</div>
<div class="view">
-<a href="hello-tabwidget.html">TabWidget</a><br/>
-<a href="hello-tabwidget.html"><img src="images/hello-tabwidget.png" height="285" width="200" /></a>
-</div>
-
-<div class="view">
-<a href="hello-mapview.html">MapView</a><br/>
+<a href="hello-mapview.html">Google Map View</a><br/>
<a href="hello-mapview.html"><img src="images/hello-mapview.png" height="285" width="200" /></a>
</div>
<div class="view">
-<a href="hello-webview.html">WebView</a><br/>
+<a href="hello-webview.html">Web View</a><br/>
<a href="hello-webview.html"><img src="images/hello-webview.png" height="285" width="200" /></a>
</div>
-<!--
-TODO
-
-<div class="view">
-<a href="hello-popupwindow.html">PopupWindow<br/>
-<img src="images/hello-popupwindow.png" height="285" width="200" /></a>
-</div>
-<div class="view">
-<a href="hello-tabhost.html">TabHost / TabWidget<br/>
-<img src="images/hello-tabhost.png" height="285" width="200" /></a>
-</div>
-ProgressBar; RatingBar; FrameLayout
-
--->
<p class="note" style="clear:left">
-There are plenty more Views and widgets available. See the {@link android.view.View} class
+There are plenty more layouts and widgets available. See the {@link android.view.View} class
for more on View layouts, and the {@link android.widget widget package}
for more useful widgets. And for more raw code samples, visit the
-<a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/index.html">Api Demos</a>.
-These can also be found offline, in <code>/<sdk>/samples/ApiDemos</code>.</p>
-</div>
+<a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/index.html">Api
+Demos</a>.</p>
diff --git a/docs/html/sdk/download.jd b/docs/html/sdk/download.jd
index 47505e6..029de21 100644
--- a/docs/html/sdk/download.jd
+++ b/docs/html/sdk/download.jd
@@ -58,10 +58,8 @@
<h2>Thank you for downloading the Android SDK!</h2>
<p>Your download should be underway. If not, <a id="click-download">click here to start the download</a>.</p>
<p>To set up your Android development environment, please read the guide to
- <a href="installing.html">Installing the Android SDK</a>.
- Once you have completed the installation, see the
- <a href="/guide/index.html">Dev Guide</a> for documentation about
- developing Android applications.</p>
+ <a href="installing.html">Installing the Android SDK</a> and ensure that your development
+ machine meets the system requirements linked on that page.</p>
</div>
<script type="text/javascript">
diff --git a/docs/html/sdk/eclipse-adt.jd b/docs/html/sdk/eclipse-adt.jd
index f861afd..1d99c91 100644
--- a/docs/html/sdk/eclipse-adt.jd
+++ b/docs/html/sdk/eclipse-adt.jd
@@ -25,7 +25,7 @@
Development Tools (ADT), that is designed to give you a powerful,
integrated environment in which to build Android applications. </p>
-<p>ADT extends the capabilites of Eclipse to let you quickly set up new Android
+<p>ADT extends the capabilities of Eclipse to let you quickly set up new Android
projects, create an application UI, add components based on the Android
Framework API, debug your applications using the Android SDK tools, and even
export signed (or unsigned) APKs in order to distribute your application.</p>
@@ -77,7 +77,7 @@
<li>Click <strong>Add Site...</strong> </li>
<li>In the Add Site dialog that appears, enter this URL in the "Location" field:
<pre style="margin-left:0">https://dl-ssl.google.com/android/eclipse/</pre>
- <p>Note: If you have trouble aqcuiring the plugin, try using "http" in the Location URL,
+ <p>Note: If you have trouble acquiring the plugin, try using "http" in the Location URL,
instead of "https" (https is preferred for security reasons).</p>
<p>Click <strong>OK</strong>.</p></li>
<li>Back in the Available Software view, you should see the plugin listed by the URL,
@@ -94,13 +94,13 @@
<!-- 3.5 steps -->
<ol>
<li>Start Eclipse, then select <strong>Help</strong> > <strong>Install
- New Softare</strong>. </li>
+ New Software</strong>. </li>
<li>In the Available Software dialog, click <strong>Add...</strong>.</li>
<li>In the Add Site dialog that appears, enter a name for the remote site
(for example, "Android Plugin") in the "Name" field.
<p>In the "Location" field, enter this URL:</p>
<pre>https://dl-ssl.google.com/android/eclipse/</pre>
- <p>Note: If you have trouble aqcuiring the plugin, you can try
+ <p>Note: If you have trouble acquiring the plugin, you can try
using "http" in the URL, instead of "https" (https is preferred for
security reasons).</p>
<p>Click <strong>OK</strong>.</p>
diff --git a/docs/html/shareables/sample_images.zip b/docs/html/shareables/sample_images.zip
new file mode 100644
index 0000000..007a68a
--- /dev/null
+++ b/docs/html/shareables/sample_images.zip
Binary files differ
diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h
index c87007c..f935bb9 100644
--- a/include/media/AudioSystem.h
+++ b/include/media/AudioSystem.h
@@ -244,6 +244,8 @@
DEVICE_OUT_WIRED_HEADPHONE | DEVICE_OUT_BLUETOOTH_SCO | DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
DEVICE_OUT_BLUETOOTH_SCO_CARKIT | DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER | DEVICE_OUT_AUX_DIGITAL | DEVICE_OUT_DEFAULT),
+ DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER),
// input devices
DEVICE_IN_COMMUNICATION = 0x10000,
diff --git a/include/media/IMediaDeathNotifier.h b/include/media/IMediaDeathNotifier.h
new file mode 100644
index 0000000..bb3d0d8
--- /dev/null
+++ b/include/media/IMediaDeathNotifier.h
@@ -0,0 +1,61 @@
+/*
+ * 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 ANDROID_IMEDIADEATHNOTIFIER_H
+#define ANDROID_IMEDIADEATHNOTIFIER_H
+
+#include <utils/threads.h>
+#include <media/IMediaPlayerService.h>
+#include <utils/SortedVector.h>
+
+namespace android {
+
+class IMediaDeathNotifier: virtual public RefBase
+{
+public:
+ IMediaDeathNotifier() { addObitRecipient(this); }
+ virtual ~IMediaDeathNotifier() { removeObitRecipient(this); }
+
+ virtual void died() = 0;
+ static const sp<IMediaPlayerService>& getMediaPlayerService();
+
+private:
+ IMediaDeathNotifier &operator=(const IMediaDeathNotifier &);
+ IMediaDeathNotifier(const IMediaDeathNotifier &);
+
+ static void addObitRecipient(const wp<IMediaDeathNotifier>& recipient);
+ static void removeObitRecipient(const wp<IMediaDeathNotifier>& recipient);
+
+ class DeathNotifier: public IBinder::DeathRecipient
+ {
+ public:
+ DeathNotifier() {}
+ virtual ~DeathNotifier();
+
+ virtual void binderDied(const wp<IBinder>& who);
+ };
+
+ friend class DeathNotifier;
+
+ static Mutex sServiceLock;
+ static sp<IMediaPlayerService> sMediaPlayerService;
+ static sp<DeathNotifier> sDeathNotifier;
+ static SortedVector< wp<IMediaDeathNotifier> > sObitRecipients;
+};
+
+}; // namespace android
+
+#endif // ANDROID_IMEDIADEATHNOTIFIER_H
diff --git a/include/media/IOMX.h b/include/media/IOMX.h
index 39bd5b1..2f61cbe 100644
--- a/include/media/IOMX.h
+++ b/include/media/IOMX.h
@@ -42,6 +42,11 @@
typedef void *buffer_id;
typedef void *node_id;
+ // Given the calling process' pid, returns true iff
+ // the implementation of the OMX interface lives in the same
+ // process.
+ virtual bool livesLocally(pid_t pid) = 0;
+
struct ComponentInfo {
String8 mName;
List<String8> mRoles;
@@ -77,9 +82,13 @@
node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms,
buffer_id *buffer) = 0;
+ // This API clearly only makes sense if the caller lives in the
+ // same process as the callee, i.e. is the media_server, as the
+ // returned "buffer_data" pointer is just that, a pointer into local
+ // address space.
virtual status_t allocateBuffer(
node_id node, OMX_U32 port_index, size_t size,
- buffer_id *buffer) = 0;
+ buffer_id *buffer, void **buffer_data) = 0;
virtual status_t allocateBufferWithBackup(
node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms,
@@ -157,6 +166,7 @@
OMX_U32 flags;
OMX_TICKS timestamp;
OMX_PTR platform_private;
+ OMX_PTR data_ptr;
} extended_buffer_data;
} u;
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index 7132b18..87d23f6 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -21,8 +21,7 @@
#include <ui/Surface.h>
#include <media/IMediaPlayerClient.h>
#include <media/IMediaPlayer.h>
-#include <media/IMediaPlayerService.h>
-#include <utils/SortedVector.h>
+#include <media/IMediaDeathNotifier.h>
namespace android {
@@ -123,12 +122,13 @@
virtual void notify(int msg, int ext1, int ext2) = 0;
};
-class MediaPlayer : public BnMediaPlayerClient
+class MediaPlayer : public BnMediaPlayerClient,
+ public virtual IMediaDeathNotifier
{
public:
MediaPlayer();
~MediaPlayer();
- void onFirstRef();
+ void died();
void disconnect();
status_t setDataSource(const char *url);
status_t setDataSource(int fd, int64_t offset, int64_t length);
@@ -164,19 +164,6 @@
status_t getDuration_l(int *msec);
status_t setDataSource(const sp<IMediaPlayer>& player);
- static const sp<IMediaPlayerService>& getMediaPlayerService();
- static void addObitRecipient(const wp<MediaPlayer>& recipient);
- static void removeObitRecipient(const wp<MediaPlayer>& recipient);
-
- class DeathNotifier: public IBinder::DeathRecipient
- {
- public:
- DeathNotifier() {}
- virtual ~DeathNotifier();
-
- virtual void binderDied(const wp<IBinder>& who);
- };
-
sp<IMediaPlayer> mPlayer;
thread_id_t mLockThreadId;
Mutex mLock;
@@ -196,13 +183,6 @@
float mRightVolume;
int mVideoWidth;
int mVideoHeight;
-
- friend class DeathNotifier;
-
- static Mutex sServiceLock;
- static sp<IMediaPlayerService> sMediaPlayerService;
- static sp<DeathNotifier> sDeathNotifier;
- static SortedVector< wp<MediaPlayer> > sObitRecipients;
};
}; // namespace android
diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h
index 8c7392b..9ea6c7b 100644
--- a/include/media/mediarecorder.h
+++ b/include/media/mediarecorder.h
@@ -23,6 +23,7 @@
#include <utils/List.h>
#include <utils/Errors.h>
#include <media/IMediaPlayerClient.h>
+#include <media/IMediaDeathNotifier.h>
namespace android {
@@ -145,12 +146,14 @@
virtual void notify(int msg, int ext1, int ext2) = 0;
};
-class MediaRecorder : public BnMediaPlayerClient
+class MediaRecorder : public BnMediaPlayerClient,
+ public virtual IMediaDeathNotifier
{
public:
MediaRecorder();
~MediaRecorder();
+ void died();
status_t initCheck();
status_t setCamera(const sp<ICamera>& camera);
status_t setPreviewSurface(const sp<Surface>& surface);
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index 2bc2734..3b21468 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -60,6 +60,7 @@
kKeyAuthor = 'auth', // cstring
kKeyCDTrackNumber = 'cdtr', // cstring
kKeyDate = 'date', // cstring
+ kKeyWriter = 'writ', // cstring
};
enum {
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index 351763c..82dd2b5 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -94,12 +94,15 @@
kRequiresFlushCompleteEmulation = 16,
kRequiresAllocateBufferOnOutputPorts = 32,
kRequiresFlushBeforeShutdown = 64,
+ kDefersOutputBufferAllocation = 128,
};
struct BufferInfo {
IOMX::buffer_id mBuffer;
bool mOwnedByComponent;
sp<IMemory> mMem;
+ size_t mSize;
+ void *mData;
MediaBuffer *mMediaBuffer;
};
@@ -109,6 +112,7 @@
};
sp<IOMX> mOMX;
+ bool mOMXLivesLocally;
IOMX::node_id mNode;
uint32_t mQuirks;
bool mIsEncoder;
diff --git a/include/ui/CameraParameters.h b/include/ui/CameraParameters.h
index 9e4e140..a5ea133 100644
--- a/include/ui/CameraParameters.h
+++ b/include/ui/CameraParameters.h
@@ -109,9 +109,10 @@
// The height (in pixels) of EXIF thumbnail in Jpeg picture.
// Example value: "384". Read/write.
static const char KEY_JPEG_THUMBNAIL_HEIGHT[];
- // Supported EXIF thumbnail sizes (width x height).
- // Example value: "512x384,320x240". Read only.
- static const char KEY_SUPPORTED_THUMBNAIL_SIZES[];
+ // Supported EXIF thumbnail sizes (width x height). 0x0 means not thumbnail
+ // in EXIF.
+ // Example value: "512x384,320x240,0x0". Read only.
+ static const char KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES[];
// The quality of the EXIF thumbnail in Jpeg picture. The range is 1 to 100,
// with 100 being the best.
// Example value: "90". Read/write.
diff --git a/include/ui/ISurface.h b/include/ui/ISurface.h
index 2ca0026..c7f181c 100644
--- a/include/ui/ISurface.h
+++ b/include/ui/ISurface.h
@@ -55,8 +55,11 @@
class BufferHeap {
public:
enum {
- /* rotate source image 90 degrees */
+ /* rotate source image */
+ ROT_0 = 0,
ROT_90 = HAL_TRANSFORM_ROT_90,
+ ROT_180 = HAL_TRANSFORM_ROT_180,
+ ROT_270 = HAL_TRANSFORM_ROT_270,
};
BufferHeap();
@@ -86,7 +89,7 @@
virtual void unregisterBuffers() = 0;
virtual sp<OverlayRef> createOverlay(
- uint32_t w, uint32_t h, int32_t format) = 0;
+ uint32_t w, uint32_t h, int32_t format, int32_t orientation) = 0;
};
// ----------------------------------------------------------------------------
diff --git a/libs/audioflinger/Android.mk b/libs/audioflinger/Android.mk
index f5c03bb..b68bfc1 100644
--- a/libs/audioflinger/Android.mk
+++ b/libs/audioflinger/Android.mk
@@ -47,7 +47,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- AudioPolicyManagerGeneric.cpp
+ AudioPolicyManagerBase.cpp
LOCAL_SHARED_LIBRARIES := \
libcutils \
@@ -60,7 +60,7 @@
LOCAL_SHARED_LIBRARIES += libdl
endif
-LOCAL_MODULE:= libaudiopolicygeneric
+LOCAL_MODULE:= libaudiopolicybase
ifeq ($(BOARD_HAVE_BLUETOOTH),true)
LOCAL_CFLAGS += -DWITH_A2DP
@@ -70,7 +70,7 @@
LOCAL_CFLAGS += -DAUDIO_POLICY_TEST
endif
-include $(BUILD_SHARED_LIBRARY)
+include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
@@ -87,11 +87,10 @@
libutils \
libbinder \
libmedia \
- libhardware_legacy \
- libaudiopolicygeneric
+ libhardware_legacy
ifeq ($(strip $(BOARD_USES_GENERIC_AUDIO)),true)
- LOCAL_STATIC_LIBRARIES += libaudiointerface
+ LOCAL_STATIC_LIBRARIES += libaudiointerface libaudiopolicybase
LOCAL_CFLAGS += -DGENERIC_AUDIO
else
LOCAL_SHARED_LIBRARIES += libaudio libaudiopolicy
diff --git a/libs/audioflinger/AudioPolicyManagerBase.cpp b/libs/audioflinger/AudioPolicyManagerBase.cpp
new file mode 100644
index 0000000..055dbca
--- /dev/null
+++ b/libs/audioflinger/AudioPolicyManagerBase.cpp
@@ -0,0 +1,1925 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AudioPolicyManagerBase"
+//
+#define LOG_NDEBUG 0
+#include <utils/Log.h>
+#include <hardware_legacy/AudioPolicyManagerBase.h>
+#include <media/mediarecorder.h>
+
+namespace android {
+
+
+// ----------------------------------------------------------------------------
+// AudioPolicyInterface implementation
+// ----------------------------------------------------------------------------
+
+
+status_t AudioPolicyManagerBase::setDeviceConnectionState(AudioSystem::audio_devices device,
+ AudioSystem::device_connection_state state,
+ const char *device_address)
+{
+
+ LOGV("setDeviceConnectionState() device: %x, state %d, address %s", device, state, device_address);
+
+ // connect/disconnect only 1 device at a time
+ if (AudioSystem::popCount(device) != 1) return BAD_VALUE;
+
+ if (strlen(device_address) >= MAX_DEVICE_ADDRESS_LEN) {
+ LOGE("setDeviceConnectionState() invalid address: %s", device_address);
+ return BAD_VALUE;
+ }
+
+ // handle output devices
+ if (AudioSystem::isOutputDevice(device)) {
+
+#ifndef WITH_A2DP
+ if (AudioSystem::isA2dpDevice(device)) {
+ LOGE("setDeviceConnectionState() invalid device: %x", device);
+ return BAD_VALUE;
+ }
+#endif
+
+ switch (state)
+ {
+ // handle output device connection
+ case AudioSystem::DEVICE_STATE_AVAILABLE:
+ if (mAvailableOutputDevices & device) {
+ LOGW("setDeviceConnectionState() device already connected: %x", device);
+ return INVALID_OPERATION;
+ }
+ LOGV("setDeviceConnectionState() connecting device %x", device);
+
+ // register new device as available
+ mAvailableOutputDevices |= device;
+
+#ifdef WITH_A2DP
+ // handle A2DP device connection
+ if (AudioSystem::isA2dpDevice(device)) {
+ status_t status = handleA2dpConnection(device, device_address);
+ if (status != NO_ERROR) {
+ mAvailableOutputDevices &= ~device;
+ return status;
+ }
+ } else
+#endif
+ {
+ if (AudioSystem::isBluetoothScoDevice(device)) {
+ LOGV("setDeviceConnectionState() BT SCO device, address %s", device_address);
+ // keep track of SCO device address
+ mScoDeviceAddress = String8(device_address, MAX_DEVICE_ADDRESS_LEN);
+#ifdef WITH_A2DP
+ if ((mA2dpDeviceAddress == mScoDeviceAddress) &&
+ (mPhoneState != AudioSystem::MODE_NORMAL)) {
+ mpClientInterface->suspendOutput(mA2dpOutput);
+ }
+#endif
+ }
+ }
+ break;
+ // handle output device disconnection
+ case AudioSystem::DEVICE_STATE_UNAVAILABLE: {
+ if (!(mAvailableOutputDevices & device)) {
+ LOGW("setDeviceConnectionState() device not connected: %x", device);
+ return INVALID_OPERATION;
+ }
+
+
+ LOGV("setDeviceConnectionState() disconnecting device %x", device);
+ // remove device from available output devices
+ mAvailableOutputDevices &= ~device;
+
+#ifdef WITH_A2DP
+ // handle A2DP device disconnection
+ if (AudioSystem::isA2dpDevice(device)) {
+ status_t status = handleA2dpDisconnection(device, device_address);
+ if (status != NO_ERROR) {
+ mAvailableOutputDevices |= device;
+ return status;
+ }
+ } else
+#endif
+ {
+ if (AudioSystem::isBluetoothScoDevice(device)) {
+ mScoDeviceAddress = "";
+#ifdef WITH_A2DP
+ if ((mA2dpDeviceAddress == mScoDeviceAddress) &&
+ (mPhoneState != AudioSystem::MODE_NORMAL)) {
+ mpClientInterface->restoreOutput(mA2dpOutput);
+ }
+#endif
+ }
+ }
+ } break;
+
+ default:
+ LOGE("setDeviceConnectionState() invalid state: %x", state);
+ return BAD_VALUE;
+ }
+
+ // request routing change if necessary
+ uint32_t newDevice = getNewDevice(mHardwareOutput, false);
+#ifdef WITH_A2DP
+ checkOutputForAllStrategies(newDevice);
+ // A2DP outputs must be closed after checkOutputForAllStrategies() is executed
+ if (state == AudioSystem::DEVICE_STATE_UNAVAILABLE && AudioSystem::isA2dpDevice(device)) {
+ closeA2dpOutputs();
+ }
+#endif
+ updateDeviceForStrategy();
+ setOutputDevice(mHardwareOutput, newDevice);
+
+ if (device == AudioSystem::DEVICE_OUT_WIRED_HEADSET) {
+ device = AudioSystem::DEVICE_IN_WIRED_HEADSET;
+ } else if (device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO ||
+ device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET ||
+ device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT) {
+ device = AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+ } else {
+ return NO_ERROR;
+ }
+ }
+ // handle input devices
+ if (AudioSystem::isInputDevice(device)) {
+
+ switch (state)
+ {
+ // handle input device connection
+ case AudioSystem::DEVICE_STATE_AVAILABLE: {
+ if (mAvailableInputDevices & device) {
+ LOGW("setDeviceConnectionState() device already connected: %d", device);
+ return INVALID_OPERATION;
+ }
+ mAvailableInputDevices |= device;
+ }
+ break;
+
+ // handle input device disconnection
+ case AudioSystem::DEVICE_STATE_UNAVAILABLE: {
+ if (!(mAvailableInputDevices & device)) {
+ LOGW("setDeviceConnectionState() device not connected: %d", device);
+ return INVALID_OPERATION;
+ }
+ mAvailableInputDevices &= ~device;
+ } break;
+
+ default:
+ LOGE("setDeviceConnectionState() invalid state: %x", state);
+ return BAD_VALUE;
+ }
+
+ audio_io_handle_t activeInput = getActiveInput();
+ if (activeInput != 0) {
+ AudioInputDescriptor *inputDesc = mInputs.valueFor(activeInput);
+ uint32_t newDevice = getDeviceForInputSource(inputDesc->mInputSource);
+ if (newDevice != inputDesc->mDevice) {
+ LOGV("setDeviceConnectionState() changing device from %x to %x for input %d",
+ inputDesc->mDevice, newDevice, activeInput);
+ inputDesc->mDevice = newDevice;
+ AudioParameter param = AudioParameter();
+ param.addInt(String8(AudioParameter::keyRouting), (int)newDevice);
+ mpClientInterface->setParameters(activeInput, param.toString());
+ }
+ }
+
+ return NO_ERROR;
+ }
+
+ LOGW("setDeviceConnectionState() invalid device: %x", device);
+ return BAD_VALUE;
+}
+
+AudioSystem::device_connection_state AudioPolicyManagerBase::getDeviceConnectionState(AudioSystem::audio_devices device,
+ const char *device_address)
+{
+ AudioSystem::device_connection_state state = AudioSystem::DEVICE_STATE_UNAVAILABLE;
+ String8 address = String8(device_address);
+ if (AudioSystem::isOutputDevice(device)) {
+ if (device & mAvailableOutputDevices) {
+#ifdef WITH_A2DP
+ if (AudioSystem::isA2dpDevice(device) &&
+ address != "" && mA2dpDeviceAddress != address) {
+ return state;
+ }
+#endif
+ if (AudioSystem::isBluetoothScoDevice(device) &&
+ address != "" && mScoDeviceAddress != address) {
+ return state;
+ }
+ state = AudioSystem::DEVICE_STATE_AVAILABLE;
+ }
+ } else if (AudioSystem::isInputDevice(device)) {
+ if (device & mAvailableInputDevices) {
+ state = AudioSystem::DEVICE_STATE_AVAILABLE;
+ }
+ }
+
+ return state;
+}
+
+void AudioPolicyManagerBase::setPhoneState(int state)
+{
+ LOGV("setPhoneState() state %d", state);
+ uint32_t newDevice = 0;
+ if (state < 0 || state >= AudioSystem::NUM_MODES) {
+ LOGW("setPhoneState() invalid state %d", state);
+ return;
+ }
+
+ if (state == mPhoneState ) {
+ LOGW("setPhoneState() setting same state %d", state);
+ return;
+ }
+
+ // if leaving call state, handle special case of active streams
+ // pertaining to sonification strategy see handleIncallSonification()
+ if (mPhoneState == AudioSystem::MODE_IN_CALL) {
+ LOGV("setPhoneState() in call state management: new state is %d", state);
+ for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
+ handleIncallSonification(stream, false, true);
+ }
+ }
+
+ // store previous phone state for management of sonification strategy below
+ int oldState = mPhoneState;
+ mPhoneState = state;
+ bool force = false;
+
+ // are we entering or starting a call
+ if ((oldState != AudioSystem::MODE_IN_CALL) && (state == AudioSystem::MODE_IN_CALL)) {
+ LOGV(" Entering call in setPhoneState()");
+ // force routing command to audio hardware when starting a call
+ // even if no device change is needed
+ force = true;
+ } else if ((oldState == AudioSystem::MODE_IN_CALL) && (state != AudioSystem::MODE_IN_CALL)) {
+ LOGV(" Exiting call in setPhoneState()");
+ // force routing command to audio hardware when exiting a call
+ // even if no device change is needed
+ force = true;
+ }
+
+ // check for device and output changes triggered by new phone state
+ newDevice = getNewDevice(mHardwareOutput, false);
+#ifdef WITH_A2DP
+ checkOutputForAllStrategies(newDevice);
+ // suspend A2DP output if SCO device address is the same as A2DP device address.
+ // no need to check that a SCO device is actually connected as mScoDeviceAddress == ""
+ // if none is connected and the test below will fail.
+ if (mA2dpDeviceAddress == mScoDeviceAddress) {
+ if (oldState == AudioSystem::MODE_NORMAL) {
+ mpClientInterface->suspendOutput(mA2dpOutput);
+ } else if (state == AudioSystem::MODE_NORMAL) {
+ mpClientInterface->restoreOutput(mA2dpOutput);
+ }
+ }
+#endif
+ updateDeviceForStrategy();
+
+ AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput);
+
+ // force routing command to audio hardware when ending call
+ // even if no device change is needed
+ if (oldState == AudioSystem::MODE_IN_CALL && newDevice == 0) {
+ newDevice = hwOutputDesc->device();
+ }
+ // change routing is necessary
+ setOutputDevice(mHardwareOutput, newDevice, force);
+
+ // if entering in call state, handle special case of active streams
+ // pertaining to sonification strategy see handleIncallSonification()
+ if (state == AudioSystem::MODE_IN_CALL) {
+ LOGV("setPhoneState() in call state management: new state is %d", state);
+ for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
+ handleIncallSonification(stream, true, true);
+ }
+ }
+
+ // Flag that ringtone volume must be limited to music volume until we exit MODE_RINGTONE
+ if (state == AudioSystem::MODE_RINGTONE &&
+ (hwOutputDesc->mRefCount[AudioSystem::MUSIC] ||
+ (systemTime() - mMusicStopTime) < seconds(SONIFICATION_HEADSET_MUSIC_DELAY))) {
+ mLimitRingtoneVolume = true;
+ } else {
+ mLimitRingtoneVolume = false;
+ }
+}
+
+void AudioPolicyManagerBase::setRingerMode(uint32_t mode, uint32_t mask)
+{
+ LOGV("setRingerMode() mode %x, mask %x", mode, mask);
+
+ mRingerMode = mode;
+}
+
+void AudioPolicyManagerBase::setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config)
+{
+ LOGV("setForceUse() usage %d, config %d, mPhoneState %d", usage, config, mPhoneState);
+
+ switch(usage) {
+ case AudioSystem::FOR_COMMUNICATION:
+ if (config != AudioSystem::FORCE_SPEAKER && config != AudioSystem::FORCE_BT_SCO &&
+ config != AudioSystem::FORCE_NONE) {
+ LOGW("setForceUse() invalid config %d for FOR_COMMUNICATION", config);
+ return;
+ }
+ mForceUse[usage] = config;
+ break;
+ case AudioSystem::FOR_MEDIA:
+ if (config != AudioSystem::FORCE_HEADPHONES && config != AudioSystem::FORCE_BT_A2DP &&
+ config != AudioSystem::FORCE_WIRED_ACCESSORY && config != AudioSystem::FORCE_NONE) {
+ LOGW("setForceUse() invalid config %d for FOR_MEDIA", config);
+ return;
+ }
+ mForceUse[usage] = config;
+ break;
+ case AudioSystem::FOR_RECORD:
+ if (config != AudioSystem::FORCE_BT_SCO && config != AudioSystem::FORCE_WIRED_ACCESSORY &&
+ config != AudioSystem::FORCE_NONE) {
+ LOGW("setForceUse() invalid config %d for FOR_RECORD", config);
+ return;
+ }
+ mForceUse[usage] = config;
+ break;
+ case AudioSystem::FOR_DOCK:
+ if (config != AudioSystem::FORCE_NONE && config != AudioSystem::FORCE_BT_CAR_DOCK &&
+ config != AudioSystem::FORCE_BT_DESK_DOCK && config != AudioSystem::FORCE_WIRED_ACCESSORY) {
+ LOGW("setForceUse() invalid config %d for FOR_DOCK", config);
+ }
+ mForceUse[usage] = config;
+ break;
+ default:
+ LOGW("setForceUse() invalid usage %d", usage);
+ break;
+ }
+
+ // check for device and output changes triggered by new phone state
+ uint32_t newDevice = getNewDevice(mHardwareOutput, false);
+#ifdef WITH_A2DP
+ checkOutputForAllStrategies(newDevice);
+#endif
+ updateDeviceForStrategy();
+ setOutputDevice(mHardwareOutput, newDevice);
+}
+
+AudioSystem::forced_config AudioPolicyManagerBase::getForceUse(AudioSystem::force_use usage)
+{
+ return mForceUse[usage];
+}
+
+void AudioPolicyManagerBase::setSystemProperty(const char* property, const char* value)
+{
+ LOGV("setSystemProperty() property %s, value %s", property, value);
+ if (strcmp(property, "ro.camera.sound.forced") == 0) {
+ if (atoi(value)) {
+ LOGV("ENFORCED_AUDIBLE cannot be muted");
+ mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = false;
+ } else {
+ LOGV("ENFORCED_AUDIBLE can be muted");
+ mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = true;
+ }
+ }
+}
+
+audio_io_handle_t AudioPolicyManagerBase::getOutput(AudioSystem::stream_type stream,
+ uint32_t samplingRate,
+ uint32_t format,
+ uint32_t channels,
+ AudioSystem::output_flags flags)
+{
+ audio_io_handle_t output = 0;
+ uint32_t latency = 0;
+ routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream);
+ uint32_t device = getDeviceForStrategy(strategy);
+ LOGV("getOutput() stream %d, samplingRate %d, format %d, channels %x, flags %x", stream, samplingRate, format, channels, flags);
+
+#ifdef AUDIO_POLICY_TEST
+ if (mCurOutput != 0) {
+ LOGV("getOutput() test output mCurOutput %d, samplingRate %d, format %d, channels %x, mDirectOutput %d",
+ mCurOutput, mTestSamplingRate, mTestFormat, mTestChannels, mDirectOutput);
+
+ if (mTestOutputs[mCurOutput] == 0) {
+ LOGV("getOutput() opening test output");
+ AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
+ outputDesc->mDevice = mTestDevice;
+ outputDesc->mSamplingRate = mTestSamplingRate;
+ outputDesc->mFormat = mTestFormat;
+ outputDesc->mChannels = mTestChannels;
+ outputDesc->mLatency = mTestLatencyMs;
+ outputDesc->mFlags = (AudioSystem::output_flags)(mDirectOutput ? AudioSystem::OUTPUT_FLAG_DIRECT : 0);
+ outputDesc->mRefCount[stream] = 0;
+ mTestOutputs[mCurOutput] = mpClientInterface->openOutput(&outputDesc->mDevice,
+ &outputDesc->mSamplingRate,
+ &outputDesc->mFormat,
+ &outputDesc->mChannels,
+ &outputDesc->mLatency,
+ outputDesc->mFlags);
+ if (mTestOutputs[mCurOutput]) {
+ AudioParameter outputCmd = AudioParameter();
+ outputCmd.addInt(String8("set_id"),mCurOutput);
+ mpClientInterface->setParameters(mTestOutputs[mCurOutput],outputCmd.toString());
+ addOutput(mTestOutputs[mCurOutput], outputDesc);
+ }
+ }
+ return mTestOutputs[mCurOutput];
+ }
+#endif //AUDIO_POLICY_TEST
+
+ // open a direct output if:
+ // 1 a direct output is explicitely requested
+ // 2 the audio format is compressed
+ if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) ||
+ (format !=0 && !AudioSystem::isLinearPCM(format))) {
+
+ LOGV("getOutput() opening direct output device %x", device);
+ AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
+ outputDesc->mDevice = device;
+ outputDesc->mSamplingRate = samplingRate;
+ outputDesc->mFormat = format;
+ outputDesc->mChannels = channels;
+ outputDesc->mLatency = 0;
+ outputDesc->mFlags = (AudioSystem::output_flags)(flags | AudioSystem::OUTPUT_FLAG_DIRECT);
+ outputDesc->mRefCount[stream] = 1;
+ output = mpClientInterface->openOutput(&outputDesc->mDevice,
+ &outputDesc->mSamplingRate,
+ &outputDesc->mFormat,
+ &outputDesc->mChannels,
+ &outputDesc->mLatency,
+ outputDesc->mFlags);
+
+ // only accept an output with the requeted parameters
+ if (output == 0 ||
+ (samplingRate != 0 && samplingRate != outputDesc->mSamplingRate) ||
+ (format != 0 && format != outputDesc->mFormat) ||
+ (channels != 0 && channels != outputDesc->mChannels)) {
+ LOGV("getOutput() failed opening direct output: samplingRate %d, format %d, channels %d",
+ samplingRate, format, channels);
+ if (output != 0) {
+ mpClientInterface->closeOutput(output);
+ }
+ delete outputDesc;
+ return 0;
+ }
+ addOutput(output, outputDesc);
+ return output;
+ }
+
+ if (channels != 0 && channels != AudioSystem::CHANNEL_OUT_MONO &&
+ channels != AudioSystem::CHANNEL_OUT_STEREO) {
+ return 0;
+ }
+ // open a non direct output
+
+ // get which output is suitable for the specified stream. The actual routing change will happen
+ // when startOutput() will be called
+ uint32_t a2dpDevice = device & AudioSystem::DEVICE_OUT_ALL_A2DP;
+ if (AudioSystem::popCount((AudioSystem::audio_devices)device) == 2) {
+#ifdef WITH_A2DP
+ if (a2dpUsedForSonification() && a2dpDevice != 0) {
+ // if playing on 2 devices among which one is A2DP, use duplicated output
+ LOGV("getOutput() using duplicated output");
+ LOGW_IF((mA2dpOutput == 0), "getOutput() A2DP device in multiple %x selected but A2DP output not opened", device);
+ output = mDuplicatedOutput;
+ } else
+#endif
+ {
+ // if playing on 2 devices among which none is A2DP, use hardware output
+ output = mHardwareOutput;
+ }
+ LOGV("getOutput() using output %d for 2 devices %x", output, device);
+ } else {
+#ifdef WITH_A2DP
+ if (a2dpDevice != 0) {
+ // if playing on A2DP device, use a2dp output
+ LOGW_IF((mA2dpOutput == 0), "getOutput() A2DP device %x selected but A2DP output not opened", device);
+ output = mA2dpOutput;
+ } else
+#endif
+ {
+ // if playing on not A2DP device, use hardware output
+ output = mHardwareOutput;
+ }
+ }
+
+
+ LOGW_IF((output ==0), "getOutput() could not find output for stream %d, samplingRate %d, format %d, channels %x, flags %x",
+ stream, samplingRate, format, channels, flags);
+
+ return output;
+}
+
+status_t AudioPolicyManagerBase::startOutput(audio_io_handle_t output, AudioSystem::stream_type stream)
+{
+ LOGV("startOutput() output %d, stream %d", output, stream);
+ ssize_t index = mOutputs.indexOfKey(output);
+ if (index < 0) {
+ LOGW("startOutput() unknow output %d", output);
+ return BAD_VALUE;
+ }
+
+ AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
+ routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream);
+
+#ifdef WITH_A2DP
+ if (mA2dpOutput != 0 && !a2dpUsedForSonification() && strategy == STRATEGY_SONIFICATION) {
+ setStrategyMute(STRATEGY_MEDIA, true, mA2dpOutput);
+ }
+#endif
+
+ // incremenent usage count for this stream on the requested output:
+ // NOTE that the usage count is the same for duplicated output and hardware output which is
+ // necassary for a correct control of hardware output routing by startOutput() and stopOutput()
+ outputDesc->changeRefCount(stream, 1);
+
+ setOutputDevice(output, getNewDevice(output));
+
+ // handle special case for sonification while in call
+ if (mPhoneState == AudioSystem::MODE_IN_CALL) {
+ handleIncallSonification(stream, true, false);
+ }
+
+ // apply volume rules for current stream and device if necessary
+ checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, outputDesc->device());
+
+ return NO_ERROR;
+}
+
+status_t AudioPolicyManagerBase::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream)
+{
+ LOGV("stopOutput() output %d, stream %d", output, stream);
+ ssize_t index = mOutputs.indexOfKey(output);
+ if (index < 0) {
+ LOGW("stopOutput() unknow output %d", output);
+ return BAD_VALUE;
+ }
+
+ AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
+ routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream);
+
+ // handle special case for sonification while in call
+ if (mPhoneState == AudioSystem::MODE_IN_CALL) {
+ handleIncallSonification(stream, false, false);
+ }
+
+ if (outputDesc->mRefCount[stream] > 0) {
+ // decrement usage count of this stream on the output
+ outputDesc->changeRefCount(stream, -1);
+ // store time at which the last music track was stopped - see computeVolume()
+ if (stream == AudioSystem::MUSIC) {
+ mMusicStopTime = systemTime();
+ }
+
+ setOutputDevice(output, getNewDevice(output));
+
+#ifdef WITH_A2DP
+ if (mA2dpOutput != 0 && !a2dpUsedForSonification() && strategy == STRATEGY_SONIFICATION) {
+ setStrategyMute(STRATEGY_MEDIA, false, mA2dpOutput, mOutputs.valueFor(mHardwareOutput)->mLatency*2);
+ }
+#endif
+ return NO_ERROR;
+ } else {
+ LOGW("stopOutput() refcount is already 0 for output %d", output);
+ return INVALID_OPERATION;
+ }
+}
+
+void AudioPolicyManagerBase::releaseOutput(audio_io_handle_t output)
+{
+ LOGV("releaseOutput() %d", output);
+ ssize_t index = mOutputs.indexOfKey(output);
+ if (index < 0) {
+ LOGW("releaseOutput() releasing unknown output %d", output);
+ return;
+ }
+
+#ifdef AUDIO_POLICY_TEST
+ int testIndex = testOutputIndex(output);
+ if (testIndex != 0) {
+ AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
+ if (outputDesc->refCount() == 0) {
+ mpClientInterface->closeOutput(output);
+ delete mOutputs.valueAt(index);
+ mOutputs.removeItem(output);
+ mTestOutputs[testIndex] = 0;
+ }
+ return;
+ }
+#endif //AUDIO_POLICY_TEST
+
+ if (mOutputs.valueAt(index)->mFlags & AudioSystem::OUTPUT_FLAG_DIRECT) {
+ mpClientInterface->closeOutput(output);
+ delete mOutputs.valueAt(index);
+ mOutputs.removeItem(output);
+ }
+}
+
+audio_io_handle_t AudioPolicyManagerBase::getInput(int inputSource,
+ uint32_t samplingRate,
+ uint32_t format,
+ uint32_t channels,
+ AudioSystem::audio_in_acoustics acoustics)
+{
+ audio_io_handle_t input = 0;
+ uint32_t device = getDeviceForInputSource(inputSource);
+
+ LOGV("getInput() inputSource %d, samplingRate %d, format %d, channels %x, acoustics %x", inputSource, samplingRate, format, channels, acoustics);
+
+ if (device == 0) {
+ return 0;
+ }
+
+ // adapt channel selection to input source
+ switch(inputSource) {
+ case AUDIO_SOURCE_VOICE_UPLINK:
+ channels = AudioSystem::CHANNEL_IN_VOICE_UPLINK;
+ break;
+ case AUDIO_SOURCE_VOICE_DOWNLINK:
+ channels = AudioSystem::CHANNEL_IN_VOICE_DNLINK;
+ break;
+ case AUDIO_SOURCE_VOICE_CALL:
+ channels = (AudioSystem::CHANNEL_IN_VOICE_UPLINK | AudioSystem::CHANNEL_IN_VOICE_DNLINK);
+ break;
+ default:
+ break;
+ }
+
+ AudioInputDescriptor *inputDesc = new AudioInputDescriptor();
+
+ inputDesc->mInputSource = inputSource;
+ inputDesc->mDevice = device;
+ inputDesc->mSamplingRate = samplingRate;
+ inputDesc->mFormat = format;
+ inputDesc->mChannels = channels;
+ inputDesc->mAcoustics = acoustics;
+ inputDesc->mRefCount = 0;
+ input = mpClientInterface->openInput(&inputDesc->mDevice,
+ &inputDesc->mSamplingRate,
+ &inputDesc->mFormat,
+ &inputDesc->mChannels,
+ inputDesc->mAcoustics);
+
+ // only accept input with the exact requested set of parameters
+ if (input == 0 ||
+ (samplingRate != inputDesc->mSamplingRate) ||
+ (format != inputDesc->mFormat) ||
+ (channels != inputDesc->mChannels)) {
+ LOGV("getInput() failed opening input: samplingRate %d, format %d, channels %d",
+ samplingRate, format, channels);
+ if (input != 0) {
+ mpClientInterface->closeInput(input);
+ }
+ delete inputDesc;
+ return 0;
+ }
+ mInputs.add(input, inputDesc);
+ return input;
+}
+
+status_t AudioPolicyManagerBase::startInput(audio_io_handle_t input)
+{
+ LOGV("startInput() input %d", input);
+ ssize_t index = mInputs.indexOfKey(input);
+ if (index < 0) {
+ LOGW("startInput() unknow input %d", input);
+ return BAD_VALUE;
+ }
+ AudioInputDescriptor *inputDesc = mInputs.valueAt(index);
+
+#ifdef AUDIO_POLICY_TEST
+ if (mTestInput == 0)
+#endif //AUDIO_POLICY_TEST
+ {
+ // refuse 2 active AudioRecord clients at the same time
+ if (getActiveInput() != 0) {
+ LOGW("startInput() input %d failed: other input already started", input);
+ return INVALID_OPERATION;
+ }
+ }
+
+ AudioParameter param = AudioParameter();
+ param.addInt(String8(AudioParameter::keyRouting), (int)inputDesc->mDevice);
+
+ // use Voice Recognition mode or not for this input based on input source
+ int vr_enabled = inputDesc->mInputSource == AUDIO_SOURCE_VOICE_RECOGNITION ? 1 : 0;
+ param.addInt(String8("vr_mode"), vr_enabled);
+ LOGV("AudioPolicyManager::startInput(%d), setting vr_mode to %d", inputDesc->mInputSource, vr_enabled);
+
+ mpClientInterface->setParameters(input, param.toString());
+
+ inputDesc->mRefCount = 1;
+ return NO_ERROR;
+}
+
+status_t AudioPolicyManagerBase::stopInput(audio_io_handle_t input)
+{
+ LOGV("stopInput() input %d", input);
+ ssize_t index = mInputs.indexOfKey(input);
+ if (index < 0) {
+ LOGW("stopInput() unknow input %d", input);
+ return BAD_VALUE;
+ }
+ AudioInputDescriptor *inputDesc = mInputs.valueAt(index);
+
+ if (inputDesc->mRefCount == 0) {
+ LOGW("stopInput() input %d already stopped", input);
+ return INVALID_OPERATION;
+ } else {
+ AudioParameter param = AudioParameter();
+ param.addInt(String8(AudioParameter::keyRouting), 0);
+ mpClientInterface->setParameters(input, param.toString());
+ inputDesc->mRefCount = 0;
+ return NO_ERROR;
+ }
+}
+
+void AudioPolicyManagerBase::releaseInput(audio_io_handle_t input)
+{
+ LOGV("releaseInput() %d", input);
+ ssize_t index = mInputs.indexOfKey(input);
+ if (index < 0) {
+ LOGW("releaseInput() releasing unknown input %d", input);
+ return;
+ }
+ mpClientInterface->closeInput(input);
+ delete mInputs.valueAt(index);
+ mInputs.removeItem(input);
+ LOGV("releaseInput() exit");
+}
+
+void AudioPolicyManagerBase::initStreamVolume(AudioSystem::stream_type stream,
+ int indexMin,
+ int indexMax)
+{
+ LOGV("initStreamVolume() stream %d, min %d, max %d", stream , indexMin, indexMax);
+ if (indexMin < 0 || indexMin >= indexMax) {
+ LOGW("initStreamVolume() invalid index limits for stream %d, min %d, max %d", stream , indexMin, indexMax);
+ return;
+ }
+ mStreams[stream].mIndexMin = indexMin;
+ mStreams[stream].mIndexMax = indexMax;
+}
+
+status_t AudioPolicyManagerBase::setStreamVolumeIndex(AudioSystem::stream_type stream, int index)
+{
+
+ if ((index < mStreams[stream].mIndexMin) || (index > mStreams[stream].mIndexMax)) {
+ return BAD_VALUE;
+ }
+
+ // Force max volume if stream cannot be muted
+ if (!mStreams[stream].mCanBeMuted) index = mStreams[stream].mIndexMax;
+
+ LOGV("setStreamVolumeIndex() stream %d, index %d", stream, index);
+ mStreams[stream].mIndexCur = index;
+
+ // compute and apply stream volume on all outputs according to connected device
+ status_t status = NO_ERROR;
+ for (size_t i = 0; i < mOutputs.size(); i++) {
+ status_t volStatus = checkAndSetVolume(stream, index, mOutputs.keyAt(i), mOutputs.valueAt(i)->device());
+ if (volStatus != NO_ERROR) {
+ status = volStatus;
+ }
+ }
+ return status;
+}
+
+status_t AudioPolicyManagerBase::getStreamVolumeIndex(AudioSystem::stream_type stream, int *index)
+{
+ if (index == 0) {
+ return BAD_VALUE;
+ }
+ LOGV("getStreamVolumeIndex() stream %d", stream);
+ *index = mStreams[stream].mIndexCur;
+ return NO_ERROR;
+}
+
+status_t AudioPolicyManagerBase::dump(int fd)
+{
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+ String8 result;
+
+ snprintf(buffer, SIZE, "\nAudioPolicyManager Dump: %p\n", this);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Hardware Output: %d\n", mHardwareOutput);
+ result.append(buffer);
+#ifdef WITH_A2DP
+ snprintf(buffer, SIZE, " A2DP Output: %d\n", mA2dpOutput);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Duplicated Output: %d\n", mDuplicatedOutput);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " A2DP device address: %s\n", mA2dpDeviceAddress.string());
+ result.append(buffer);
+#endif
+ snprintf(buffer, SIZE, " SCO device address: %s\n", mScoDeviceAddress.string());
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Output devices: %08x\n", mAvailableOutputDevices);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Input devices: %08x\n", mAvailableInputDevices);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Phone state: %d\n", mPhoneState);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Ringer mode: %d\n", mRingerMode);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Force use for communications %d\n", mForceUse[AudioSystem::FOR_COMMUNICATION]);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Force use for media %d\n", mForceUse[AudioSystem::FOR_MEDIA]);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Force use for record %d\n", mForceUse[AudioSystem::FOR_RECORD]);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Force use for dock %d\n", mForceUse[AudioSystem::FOR_DOCK]);
+ result.append(buffer);
+ write(fd, result.string(), result.size());
+
+ snprintf(buffer, SIZE, "\nOutputs dump:\n");
+ write(fd, buffer, strlen(buffer));
+ for (size_t i = 0; i < mOutputs.size(); i++) {
+ snprintf(buffer, SIZE, "- Output %d dump:\n", mOutputs.keyAt(i));
+ write(fd, buffer, strlen(buffer));
+ mOutputs.valueAt(i)->dump(fd);
+ }
+
+ snprintf(buffer, SIZE, "\nInputs dump:\n");
+ write(fd, buffer, strlen(buffer));
+ for (size_t i = 0; i < mInputs.size(); i++) {
+ snprintf(buffer, SIZE, "- Input %d dump:\n", mInputs.keyAt(i));
+ write(fd, buffer, strlen(buffer));
+ mInputs.valueAt(i)->dump(fd);
+ }
+
+ snprintf(buffer, SIZE, "\nStreams dump:\n");
+ write(fd, buffer, strlen(buffer));
+ snprintf(buffer, SIZE, " Stream Index Min Index Max Index Cur Can be muted\n");
+ write(fd, buffer, strlen(buffer));
+ for (size_t i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
+ snprintf(buffer, SIZE, " %02d", i);
+ mStreams[i].dump(buffer + 3, SIZE);
+ write(fd, buffer, strlen(buffer));
+ }
+
+ return NO_ERROR;
+}
+
+// ----------------------------------------------------------------------------
+// AudioPolicyManagerBase
+// ----------------------------------------------------------------------------
+
+AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clientInterface)
+ :
+#ifdef AUDIO_POLICY_TEST
+ Thread(false),
+#endif //AUDIO_POLICY_TEST
+ mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), mLimitRingtoneVolume(false)
+{
+ mpClientInterface = clientInterface;
+
+ for (int i = 0; i < AudioSystem::NUM_FORCE_USE; i++) {
+ mForceUse[i] = AudioSystem::FORCE_NONE;
+ }
+
+ // devices available by default are speaker, ear piece and microphone
+ mAvailableOutputDevices = AudioSystem::DEVICE_OUT_EARPIECE |
+ AudioSystem::DEVICE_OUT_SPEAKER;
+ mAvailableInputDevices = AudioSystem::DEVICE_IN_BUILTIN_MIC;
+
+#ifdef WITH_A2DP
+ mA2dpOutput = 0;
+ mDuplicatedOutput = 0;
+ mA2dpDeviceAddress = String8("");
+#endif
+ mScoDeviceAddress = String8("");
+
+ // open hardware output
+ AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
+ outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER;
+ mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
+ &outputDesc->mSamplingRate,
+ &outputDesc->mFormat,
+ &outputDesc->mChannels,
+ &outputDesc->mLatency,
+ outputDesc->mFlags);
+
+ if (mHardwareOutput == 0) {
+ LOGE("Failed to initialize hardware output stream, samplingRate: %d, format %d, channels %d",
+ outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels);
+ } else {
+ addOutput(mHardwareOutput, outputDesc);
+ setOutputDevice(mHardwareOutput, (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER, true);
+ }
+
+ updateDeviceForStrategy();
+#ifdef AUDIO_POLICY_TEST
+ AudioParameter outputCmd = AudioParameter();
+ outputCmd.addInt(String8("set_id"), 0);
+ mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString());
+
+ mTestDevice = AudioSystem::DEVICE_OUT_SPEAKER;
+ mTestSamplingRate = 44100;
+ mTestFormat = AudioSystem::PCM_16_BIT;
+ mTestChannels = AudioSystem::CHANNEL_OUT_STEREO;
+ mTestLatencyMs = 0;
+ mCurOutput = 0;
+ mDirectOutput = false;
+ for (int i = 0; i < NUM_TEST_OUTPUTS; i++) {
+ mTestOutputs[i] = 0;
+ }
+
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+ snprintf(buffer, SIZE, "AudioPolicyManagerTest");
+ run(buffer, ANDROID_PRIORITY_AUDIO);
+#endif //AUDIO_POLICY_TEST
+}
+
+AudioPolicyManagerBase::~AudioPolicyManagerBase()
+{
+#ifdef AUDIO_POLICY_TEST
+ exit();
+#endif //AUDIO_POLICY_TEST
+ for (size_t i = 0; i < mOutputs.size(); i++) {
+ mpClientInterface->closeOutput(mOutputs.keyAt(i));
+ delete mOutputs.valueAt(i);
+ }
+ mOutputs.clear();
+ for (size_t i = 0; i < mInputs.size(); i++) {
+ mpClientInterface->closeInput(mInputs.keyAt(i));
+ delete mInputs.valueAt(i);
+ }
+ mInputs.clear();
+}
+
+#ifdef AUDIO_POLICY_TEST
+bool AudioPolicyManagerBase::threadLoop()
+{
+ LOGV("entering threadLoop()");
+ while (!exitPending())
+ {
+ String8 command;
+ int valueInt;
+ String8 value;
+
+ Mutex::Autolock _l(mLock);
+ mWaitWorkCV.waitRelative(mLock, milliseconds(50));
+
+ command = mpClientInterface->getParameters(0, String8("test_cmd_policy"));
+ AudioParameter param = AudioParameter(command);
+
+ if (param.getInt(String8("test_cmd_policy"), valueInt) == NO_ERROR &&
+ valueInt != 0) {
+ LOGV("Test command %s received", command.string());
+ String8 target;
+ if (param.get(String8("target"), target) != NO_ERROR) {
+ target = "Manager";
+ }
+ if (param.getInt(String8("test_cmd_policy_output"), valueInt) == NO_ERROR) {
+ param.remove(String8("test_cmd_policy_output"));
+ mCurOutput = valueInt;
+ }
+ if (param.get(String8("test_cmd_policy_direct"), value) == NO_ERROR) {
+ param.remove(String8("test_cmd_policy_direct"));
+ if (value == "false") {
+ mDirectOutput = false;
+ } else if (value == "true") {
+ mDirectOutput = true;
+ }
+ }
+ if (param.getInt(String8("test_cmd_policy_input"), valueInt) == NO_ERROR) {
+ param.remove(String8("test_cmd_policy_input"));
+ mTestInput = valueInt;
+ }
+
+ if (param.get(String8("test_cmd_policy_format"), value) == NO_ERROR) {
+ param.remove(String8("test_cmd_policy_format"));
+ int format = AudioSystem::INVALID_FORMAT;
+ if (value == "PCM 16 bits") {
+ format = AudioSystem::PCM_16_BIT;
+ } else if (value == "PCM 8 bits") {
+ format = AudioSystem::PCM_8_BIT;
+ } else if (value == "Compressed MP3") {
+ format = AudioSystem::MP3;
+ }
+ if (format != AudioSystem::INVALID_FORMAT) {
+ if (target == "Manager") {
+ mTestFormat = format;
+ } else if (mTestOutputs[mCurOutput] != 0) {
+ AudioParameter outputParam = AudioParameter();
+ outputParam.addInt(String8("format"), format);
+ mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
+ }
+ }
+ }
+ if (param.get(String8("test_cmd_policy_channels"), value) == NO_ERROR) {
+ param.remove(String8("test_cmd_policy_channels"));
+ int channels = 0;
+
+ if (value == "Channels Stereo") {
+ channels = AudioSystem::CHANNEL_OUT_STEREO;
+ } else if (value == "Channels Mono") {
+ channels = AudioSystem::CHANNEL_OUT_MONO;
+ }
+ if (channels != 0) {
+ if (target == "Manager") {
+ mTestChannels = channels;
+ } else if (mTestOutputs[mCurOutput] != 0) {
+ AudioParameter outputParam = AudioParameter();
+ outputParam.addInt(String8("channels"), channels);
+ mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
+ }
+ }
+ }
+ if (param.getInt(String8("test_cmd_policy_sampleRate"), valueInt) == NO_ERROR) {
+ param.remove(String8("test_cmd_policy_sampleRate"));
+ if (valueInt >= 0 && valueInt <= 96000) {
+ int samplingRate = valueInt;
+ if (target == "Manager") {
+ mTestSamplingRate = samplingRate;
+ } else if (mTestOutputs[mCurOutput] != 0) {
+ AudioParameter outputParam = AudioParameter();
+ outputParam.addInt(String8("sampling_rate"), samplingRate);
+ mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
+ }
+ }
+ }
+
+ if (param.get(String8("test_cmd_policy_reopen"), value) == NO_ERROR) {
+ param.remove(String8("test_cmd_policy_reopen"));
+
+ mpClientInterface->closeOutput(mHardwareOutput);
+ delete mOutputs.valueFor(mHardwareOutput);
+ mOutputs.removeItem(mHardwareOutput);
+
+ AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
+ outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER;
+ mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
+ &outputDesc->mSamplingRate,
+ &outputDesc->mFormat,
+ &outputDesc->mChannels,
+ &outputDesc->mLatency,
+ outputDesc->mFlags);
+ if (mHardwareOutput == 0) {
+ LOGE("Failed to reopen hardware output stream, samplingRate: %d, format %d, channels %d",
+ outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels);
+ } else {
+ AudioParameter outputCmd = AudioParameter();
+ outputCmd.addInt(String8("set_id"), 0);
+ mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString());
+ addOutput(mHardwareOutput, outputDesc);
+ }
+ }
+
+
+ mpClientInterface->setParameters(0, String8("test_cmd_policy="));
+ }
+ }
+ return false;
+}
+
+void AudioPolicyManagerBase::exit()
+{
+ {
+ AutoMutex _l(mLock);
+ requestExit();
+ mWaitWorkCV.signal();
+ }
+ requestExitAndWait();
+}
+
+int AudioPolicyManagerBase::testOutputIndex(audio_io_handle_t output)
+{
+ for (int i = 0; i < NUM_TEST_OUTPUTS; i++) {
+ if (output == mTestOutputs[i]) return i;
+ }
+ return 0;
+}
+#endif //AUDIO_POLICY_TEST
+
+// ---
+
+void AudioPolicyManagerBase::addOutput(audio_io_handle_t id, AudioOutputDescriptor *outputDesc)
+{
+ outputDesc->mId = id;
+ mOutputs.add(id, outputDesc);
+}
+
+
+#ifdef WITH_A2DP
+status_t AudioPolicyManagerBase::handleA2dpConnection(AudioSystem::audio_devices device,
+ const char *device_address)
+{
+ // when an A2DP device is connected, open an A2DP and a duplicated output
+ LOGV("opening A2DP output for device %s", device_address);
+ AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
+ outputDesc->mDevice = device;
+ mA2dpOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
+ &outputDesc->mSamplingRate,
+ &outputDesc->mFormat,
+ &outputDesc->mChannels,
+ &outputDesc->mLatency,
+ outputDesc->mFlags);
+ if (mA2dpOutput) {
+ // add A2DP output descriptor
+ addOutput(mA2dpOutput, outputDesc);
+ // set initial stream volume for A2DP device
+ applyStreamVolumes(mA2dpOutput, device);
+ if (a2dpUsedForSonification()) {
+ mDuplicatedOutput = mpClientInterface->openDuplicateOutput(mA2dpOutput, mHardwareOutput);
+ }
+ if (mDuplicatedOutput != 0 ||
+ !a2dpUsedForSonification()) {
+ // If both A2DP and duplicated outputs are open, send device address to A2DP hardware
+ // interface
+ AudioParameter param;
+ param.add(String8("a2dp_sink_address"), String8(device_address));
+ mpClientInterface->setParameters(mA2dpOutput, param.toString());
+ mA2dpDeviceAddress = String8(device_address, MAX_DEVICE_ADDRESS_LEN);
+
+ if (a2dpUsedForSonification()) {
+ // add duplicated output descriptor
+ AudioOutputDescriptor *dupOutputDesc = new AudioOutputDescriptor();
+ dupOutputDesc->mOutput1 = mOutputs.valueFor(mHardwareOutput);
+ dupOutputDesc->mOutput2 = mOutputs.valueFor(mA2dpOutput);
+ dupOutputDesc->mSamplingRate = outputDesc->mSamplingRate;
+ dupOutputDesc->mFormat = outputDesc->mFormat;
+ dupOutputDesc->mChannels = outputDesc->mChannels;
+ dupOutputDesc->mLatency = outputDesc->mLatency;
+ addOutput(mDuplicatedOutput, dupOutputDesc);
+ applyStreamVolumes(mDuplicatedOutput, device);
+ }
+ } else {
+ LOGW("getOutput() could not open duplicated output for %d and %d",
+ mHardwareOutput, mA2dpOutput);
+ mpClientInterface->closeOutput(mA2dpOutput);
+ mOutputs.removeItem(mA2dpOutput);
+ mA2dpOutput = 0;
+ delete outputDesc;
+ return NO_INIT;
+ }
+ } else {
+ LOGW("setDeviceConnectionState() could not open A2DP output for device %x", device);
+ delete outputDesc;
+ return NO_INIT;
+ }
+ AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput);
+
+ if (mA2dpDeviceAddress == mScoDeviceAddress) {
+ // It is normal to suspend twice if we are both in call,
+ // and have the hardware audio output routed to BT SCO
+ if (mPhoneState != AudioSystem::MODE_NORMAL) {
+ mpClientInterface->suspendOutput(mA2dpOutput);
+ }
+ if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)hwOutputDesc->device())) {
+ mpClientInterface->suspendOutput(mA2dpOutput);
+ }
+ }
+
+ if (!a2dpUsedForSonification()) {
+ // mute music on A2DP output if a notification or ringtone is playing
+ uint32_t refCount = hwOutputDesc->strategyRefCount(STRATEGY_SONIFICATION);
+ for (uint32_t i = 0; i < refCount; i++) {
+ setStrategyMute(STRATEGY_MEDIA, true, mA2dpOutput);
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t AudioPolicyManagerBase::handleA2dpDisconnection(AudioSystem::audio_devices device,
+ const char *device_address)
+{
+ if (mA2dpOutput == 0) {
+ LOGW("setDeviceConnectionState() disconnecting A2DP and no A2DP output!");
+ return INVALID_OPERATION;
+ }
+
+ if (mA2dpDeviceAddress != device_address) {
+ LOGW("setDeviceConnectionState() disconnecting unknow A2DP sink address %s", device_address);
+ return INVALID_OPERATION;
+ }
+
+ // mute media during 2 seconds to avoid outputing sound on hardware output while music stream
+ // is switched from A2DP output and before music is paused by music application
+ setStrategyMute(STRATEGY_MEDIA, true, mHardwareOutput);
+ setStrategyMute(STRATEGY_MEDIA, false, mHardwareOutput, 2000);
+
+ if (!a2dpUsedForSonification()) {
+ // unmute music on A2DP output if a notification or ringtone is playing
+ uint32_t refCount = mOutputs.valueFor(mHardwareOutput)->strategyRefCount(STRATEGY_SONIFICATION);
+ for (uint32_t i = 0; i < refCount; i++) {
+ setStrategyMute(STRATEGY_MEDIA, false, mA2dpOutput);
+ }
+ }
+ mA2dpDeviceAddress = "";
+ return NO_ERROR;
+}
+
+void AudioPolicyManagerBase::closeA2dpOutputs()
+{
+ LOGV("setDeviceConnectionState() closing A2DP and duplicated output!");
+
+ if (mDuplicatedOutput != 0) {
+ mpClientInterface->closeOutput(mDuplicatedOutput);
+ delete mOutputs.valueFor(mDuplicatedOutput);
+ mOutputs.removeItem(mDuplicatedOutput);
+ mDuplicatedOutput = 0;
+ }
+ if (mA2dpOutput != 0) {
+ AudioParameter param;
+ param.add(String8("closing"), String8("true"));
+ mpClientInterface->setParameters(mA2dpOutput, param.toString());
+ mpClientInterface->closeOutput(mA2dpOutput);
+ delete mOutputs.valueFor(mA2dpOutput);
+ mOutputs.removeItem(mA2dpOutput);
+ mA2dpOutput = 0;
+ }
+}
+
+void AudioPolicyManagerBase::checkOutputForStrategy(routing_strategy strategy, uint32_t &newDevice)
+{
+ uint32_t prevDevice = getDeviceForStrategy(strategy);
+ uint32_t curDevice = getDeviceForStrategy(strategy, false);
+ bool a2dpWasUsed = AudioSystem::isA2dpDevice((AudioSystem::audio_devices)(prevDevice & ~AudioSystem::DEVICE_OUT_SPEAKER));
+ bool a2dpIsUsed = AudioSystem::isA2dpDevice((AudioSystem::audio_devices)(curDevice & ~AudioSystem::DEVICE_OUT_SPEAKER));
+ AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput);
+ AudioOutputDescriptor *a2dpOutputDesc;
+
+ if (a2dpWasUsed && !a2dpIsUsed) {
+ bool dupUsed = a2dpUsedForSonification() && a2dpWasUsed && (AudioSystem::popCount(prevDevice) == 2);
+
+ if (dupUsed) {
+ LOGV("checkOutputForStrategy() moving strategy %d to duplicated", strategy);
+ a2dpOutputDesc = mOutputs.valueFor(mDuplicatedOutput);
+ } else {
+ LOGV("checkOutputForStrategy() moving strategy %d to a2dp", strategy);
+ a2dpOutputDesc = mOutputs.valueFor(mA2dpOutput);
+ }
+
+ for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
+ if (getStrategy((AudioSystem::stream_type)i) == strategy) {
+ mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mHardwareOutput);
+ int refCount = a2dpOutputDesc->mRefCount[i];
+ // in the case of duplicated output, the ref count is first incremented
+ // and then decremented on hardware output tus keeping its value
+ hwOutputDesc->changeRefCount((AudioSystem::stream_type)i, refCount);
+ a2dpOutputDesc->changeRefCount((AudioSystem::stream_type)i,-refCount);
+ }
+ }
+ // do not change newDevice is it was already set before this call by a previous call to
+ // getNewDevice() or checkOutputForStrategy() for a strategy with higher priority
+ if (newDevice == 0 && hwOutputDesc->isUsedByStrategy(strategy)) {
+ newDevice = getDeviceForStrategy(strategy, false);
+ }
+ }
+ if (a2dpIsUsed && !a2dpWasUsed) {
+ bool dupUsed = a2dpUsedForSonification() && a2dpIsUsed && (AudioSystem::popCount(curDevice) == 2);
+ audio_io_handle_t a2dpOutput;
+
+ if (dupUsed) {
+ LOGV("checkOutputForStrategy() moving strategy %d from duplicated", strategy);
+ a2dpOutputDesc = mOutputs.valueFor(mDuplicatedOutput);
+ a2dpOutput = mDuplicatedOutput;
+ } else {
+ LOGV("checkOutputForStrategy() moving strategy %d from a2dp", strategy);
+ a2dpOutputDesc = mOutputs.valueFor(mA2dpOutput);
+ a2dpOutput = mA2dpOutput;
+ }
+
+ for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
+ if (getStrategy((AudioSystem::stream_type)i) == strategy) {
+ mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, a2dpOutput);
+ int refCount = hwOutputDesc->mRefCount[i];
+ // in the case of duplicated output, the ref count is first incremented
+ // and then decremented on hardware output tus keeping its value
+ a2dpOutputDesc->changeRefCount((AudioSystem::stream_type)i, refCount);
+ hwOutputDesc->changeRefCount((AudioSystem::stream_type)i,-refCount);
+ }
+ }
+ }
+}
+
+void AudioPolicyManagerBase::checkOutputForAllStrategies(uint32_t &newDevice)
+{
+ // Check strategies in order of priority so that once newDevice is set
+ // for a given strategy it is not modified by subsequent calls to
+ // checkOutputForStrategy()
+ checkOutputForStrategy(STRATEGY_PHONE, newDevice);
+ checkOutputForStrategy(STRATEGY_SONIFICATION, newDevice);
+ checkOutputForStrategy(STRATEGY_MEDIA, newDevice);
+ checkOutputForStrategy(STRATEGY_DTMF, newDevice);
+}
+
+#endif
+
+uint32_t AudioPolicyManagerBase::getNewDevice(audio_io_handle_t output, bool fromCache)
+{
+ uint32_t device = 0;
+
+ AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output);
+ // check the following by order of priority to request a routing change if necessary:
+ // 1: we are in call or the strategy phone is active on the hardware output:
+ // use device for strategy phone
+ // 2: the strategy sonification is active on the hardware output:
+ // use device for strategy sonification
+ // 3: the strategy media is active on the hardware output:
+ // use device for strategy media
+ // 4: the strategy DTMF is active on the hardware output:
+ // use device for strategy DTMF
+ if (mPhoneState == AudioSystem::MODE_IN_CALL ||
+ outputDesc->isUsedByStrategy(STRATEGY_PHONE)) {
+ device = getDeviceForStrategy(STRATEGY_PHONE, fromCache);
+ } else if (outputDesc->isUsedByStrategy(STRATEGY_SONIFICATION)) {
+ device = getDeviceForStrategy(STRATEGY_SONIFICATION, fromCache);
+ } else if (outputDesc->isUsedByStrategy(STRATEGY_MEDIA)) {
+ device = getDeviceForStrategy(STRATEGY_MEDIA, fromCache);
+ } else if (outputDesc->isUsedByStrategy(STRATEGY_DTMF)) {
+ device = getDeviceForStrategy(STRATEGY_DTMF, fromCache);
+ }
+
+ LOGV("getNewDevice() selected device %x", device);
+ return device;
+}
+
+AudioPolicyManagerBase::routing_strategy AudioPolicyManagerBase::getStrategy(AudioSystem::stream_type stream)
+{
+ // stream to strategy mapping
+ switch (stream) {
+ case AudioSystem::VOICE_CALL:
+ case AudioSystem::BLUETOOTH_SCO:
+ return STRATEGY_PHONE;
+ case AudioSystem::RING:
+ case AudioSystem::NOTIFICATION:
+ case AudioSystem::ALARM:
+ case AudioSystem::ENFORCED_AUDIBLE:
+ return STRATEGY_SONIFICATION;
+ case AudioSystem::DTMF:
+ return STRATEGY_DTMF;
+ default:
+ LOGE("unknown stream type");
+ case AudioSystem::SYSTEM:
+ // NOTE: SYSTEM stream uses MEDIA strategy because muting music and switching outputs
+ // while key clicks are played produces a poor result
+ case AudioSystem::TTS:
+ case AudioSystem::MUSIC:
+ return STRATEGY_MEDIA;
+ }
+}
+
+uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, bool fromCache)
+{
+ uint32_t device = 0;
+
+ if (fromCache) {
+ LOGV("getDeviceForStrategy() from cache strategy %d, device %x", strategy, mDeviceForStrategy[strategy]);
+ return mDeviceForStrategy[strategy];
+ }
+
+ switch (strategy) {
+ case STRATEGY_DTMF:
+ if (mPhoneState != AudioSystem::MODE_IN_CALL) {
+ // when off call, DTMF strategy follows the same rules as MEDIA strategy
+ device = getDeviceForStrategy(STRATEGY_MEDIA, false);
+ break;
+ }
+ // when in call, DTMF and PHONE strategies follow the same rules
+ // FALL THROUGH
+
+ case STRATEGY_PHONE:
+ // for phone strategy, we first consider the forced use and then the available devices by order
+ // of priority
+ switch (mForceUse[AudioSystem::FOR_COMMUNICATION]) {
+ case AudioSystem::FORCE_BT_SCO:
+ if (mPhoneState != AudioSystem::MODE_IN_CALL || strategy != STRATEGY_DTMF) {
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
+ if (device) break;
+ }
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
+ if (device) break;
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO;
+ if (device) break;
+ // if SCO device is requested but no SCO device is available, fall back to default case
+ // FALL THROUGH
+
+ default: // FORCE_NONE
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE;
+ if (device) break;
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET;
+ if (device) break;
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_EARPIECE;
+ if (device == 0) {
+ LOGE("getDeviceForStrategy() earpiece device not found");
+ }
+ break;
+
+ case AudioSystem::FORCE_SPEAKER:
+ if (mPhoneState != AudioSystem::MODE_IN_CALL || strategy != STRATEGY_DTMF) {
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
+ if (device) break;
+ }
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER;
+ if (device == 0) {
+ LOGE("getDeviceForStrategy() speaker device not found");
+ }
+ break;
+ }
+ break;
+
+ case STRATEGY_SONIFICATION:
+
+ // If incall, just select the STRATEGY_PHONE device: The rest of the behavior is handled by
+ // handleIncallSonification().
+ if (mPhoneState == AudioSystem::MODE_IN_CALL) {
+ device = getDeviceForStrategy(STRATEGY_PHONE, false);
+ break;
+ }
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER;
+ if (device == 0) {
+ LOGE("getDeviceForStrategy() speaker device not found");
+ }
+ // The second device used for sonification is the same as the device used by media strategy
+ // FALL THROUGH
+
+ case STRATEGY_MEDIA: {
+ uint32_t device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL;
+#ifdef WITH_A2DP
+ if (mA2dpOutput != 0) {
+ if (strategy == STRATEGY_SONIFICATION && !a2dpUsedForSonification()) {
+ break;
+ }
+ if (device2 == 0) {
+ device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP;
+ }
+ if (device2 == 0) {
+ device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
+ }
+ if (device2 == 0) {
+ device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
+ }
+ }
+#endif
+ if (device2 == 0) {
+ device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE;
+ }
+ if (device2 == 0) {
+ device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET;
+ }
+ if (device2 == 0) {
+ device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER;
+ }
+
+ // device is DEVICE_OUT_SPEAKER if we come from case STRATEGY_SONIFICATION, 0 otherwise
+ device |= device2;
+ if (device == 0) {
+ LOGE("getDeviceForStrategy() speaker device not found");
+ }
+ } break;
+
+ default:
+ LOGW("getDeviceForStrategy() unknown strategy: %d", strategy);
+ break;
+ }
+
+ LOGV("getDeviceForStrategy() strategy %d, device %x", strategy, device);
+ return device;
+}
+
+void AudioPolicyManagerBase::updateDeviceForStrategy()
+{
+ for (int i = 0; i < NUM_STRATEGIES; i++) {
+ mDeviceForStrategy[i] = getDeviceForStrategy((routing_strategy)i, false);
+ }
+}
+
+void AudioPolicyManagerBase::setOutputDevice(audio_io_handle_t output, uint32_t device, bool force, int delayMs)
+{
+ LOGV("setOutputDevice() output %d device %x delayMs %d", output, device, delayMs);
+ AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output);
+
+
+ if (outputDesc->isDuplicated()) {
+ setOutputDevice(outputDesc->mOutput1->mId, device, force, delayMs);
+ setOutputDevice(outputDesc->mOutput2->mId, device, force, delayMs);
+ return;
+ }
+#ifdef WITH_A2DP
+ // filter devices according to output selected
+ if (output == mHardwareOutput) {
+ device &= ~AudioSystem::DEVICE_OUT_ALL_A2DP;
+ } else {
+ device &= AudioSystem::DEVICE_OUT_ALL_A2DP;
+ }
+#endif
+
+ uint32_t prevDevice = (uint32_t)outputDesc->device();
+ // Do not change the routing if:
+ // - the requestede device is 0
+ // - the requested device is the same as current device and force is not specified.
+ // Doing this check here allows the caller to call setOutputDevice() without conditions
+ if (device == 0 ||
+ (device == prevDevice && !force)) {
+ LOGV("setOutputDevice() setting same device %x or null device for output %d", device, output);
+ return;
+ }
+
+ outputDesc->mDevice = device;
+ // mute media streams if both speaker and headset are selected
+ if (output == mHardwareOutput && AudioSystem::popCount(device) == 2) {
+ setStrategyMute(STRATEGY_MEDIA, true, output);
+ // wait for the PCM output buffers to empty before proceeding with the rest of the command
+ usleep(outputDesc->mLatency*2*1000);
+ }
+#ifdef WITH_A2DP
+ // suspend A2D output if SCO device is selected
+ if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)device)) {
+ if (mA2dpOutput && mScoDeviceAddress == mA2dpDeviceAddress) {
+ mpClientInterface->suspendOutput(mA2dpOutput);
+ }
+ }
+#endif
+ // do the routing
+ AudioParameter param = AudioParameter();
+ param.addInt(String8(AudioParameter::keyRouting), (int)device);
+ mpClientInterface->setParameters(mHardwareOutput, param.toString(), delayMs);
+ // update stream volumes according to new device
+ applyStreamVolumes(output, device, delayMs);
+
+#ifdef WITH_A2DP
+ // if disconnecting SCO device, restore A2DP output
+ if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)prevDevice)) {
+ if (mA2dpOutput && mScoDeviceAddress == mA2dpDeviceAddress) {
+ LOGV("restore A2DP output");
+ mpClientInterface->restoreOutput(mA2dpOutput);
+ }
+ }
+#endif
+ // if changing from a combined headset + speaker route, unmute media streams
+ if (output == mHardwareOutput && AudioSystem::popCount(prevDevice) == 2) {
+ setStrategyMute(STRATEGY_MEDIA, false, output, delayMs);
+ }
+}
+
+uint32_t AudioPolicyManagerBase::getDeviceForInputSource(int inputSource)
+{
+ uint32_t device;
+
+ switch(inputSource) {
+ case AUDIO_SOURCE_DEFAULT:
+ case AUDIO_SOURCE_MIC:
+ case AUDIO_SOURCE_VOICE_RECOGNITION:
+ if (mForceUse[AudioSystem::FOR_RECORD] == AudioSystem::FORCE_BT_SCO &&
+ mAvailableInputDevices & AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET) {
+ device = AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+ } else if (mAvailableInputDevices & AudioSystem::DEVICE_IN_WIRED_HEADSET) {
+ device = AudioSystem::DEVICE_IN_WIRED_HEADSET;
+ } else {
+ device = AudioSystem::DEVICE_IN_BUILTIN_MIC;
+ }
+ break;
+ case AUDIO_SOURCE_CAMCORDER:
+ if (hasBackMicrophone()) {
+ device = AudioSystem::DEVICE_IN_BACK_MIC;
+ } else {
+ device = AudioSystem::DEVICE_IN_BUILTIN_MIC;
+ }
+ break;
+ case AUDIO_SOURCE_VOICE_UPLINK:
+ case AUDIO_SOURCE_VOICE_DOWNLINK:
+ case AUDIO_SOURCE_VOICE_CALL:
+ device = AudioSystem::DEVICE_IN_VOICE_CALL;
+ break;
+ default:
+ LOGW("getInput() invalid input source %d", inputSource);
+ device = 0;
+ break;
+ }
+ LOGV("getDeviceForInputSource()input source %d, device %08x", inputSource, device);
+ return device;
+}
+
+audio_io_handle_t AudioPolicyManagerBase::getActiveInput()
+{
+ for (size_t i = 0; i < mInputs.size(); i++) {
+ if (mInputs.valueAt(i)->mRefCount > 0) {
+ return mInputs.keyAt(i);
+ }
+ }
+ return 0;
+}
+
+float AudioPolicyManagerBase::computeVolume(int stream, int index, audio_io_handle_t output, uint32_t device)
+{
+ float volume = 1.0;
+ AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output);
+ StreamDescriptor &streamDesc = mStreams[stream];
+
+ if (device == 0) {
+ device = outputDesc->device();
+ }
+
+ int volInt = (100 * (index - streamDesc.mIndexMin)) / (streamDesc.mIndexMax - streamDesc.mIndexMin);
+ volume = AudioSystem::linearToLog(volInt);
+
+ // if a heaset is connected, apply the following rules to ring tones and notifications
+ // to avoid sound level bursts in user's ears:
+ // - always attenuate ring tones and notifications volume by 6dB
+ // - if music is playing, always limit the volume to current music volume,
+ // with a minimum threshold at -36dB so that notification is always perceived.
+ if ((device &
+ (AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP |
+ AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AudioSystem::DEVICE_OUT_WIRED_HEADSET |
+ AudioSystem::DEVICE_OUT_WIRED_HEADPHONE)) &&
+ (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) &&
+ streamDesc.mCanBeMuted) {
+ volume *= SONIFICATION_HEADSET_VOLUME_FACTOR;
+ // when the phone is ringing we must consider that music could have been paused just before
+ // by the music application and behave as if music was active if the last music track was
+ // just stopped
+ if (outputDesc->mRefCount[AudioSystem::MUSIC] || mLimitRingtoneVolume) {
+ float musicVol = computeVolume(AudioSystem::MUSIC, mStreams[AudioSystem::MUSIC].mIndexCur, output, device);
+ float minVol = (musicVol > SONIFICATION_HEADSET_VOLUME_MIN) ? musicVol : SONIFICATION_HEADSET_VOLUME_MIN;
+ if (volume > minVol) {
+ volume = minVol;
+ LOGV("computeVolume limiting volume to %f musicVol %f", minVol, musicVol);
+ }
+ }
+ }
+
+ return volume;
+}
+
+status_t AudioPolicyManagerBase::checkAndSetVolume(int stream, int index, audio_io_handle_t output, uint32_t device, int delayMs, bool force)
+{
+
+ // do not change actual stream volume if the stream is muted
+ if (mOutputs.valueFor(output)->mMuteCount[stream] != 0) {
+ LOGV("checkAndSetVolume() stream %d muted count %d", stream, mOutputs.valueFor(output)->mMuteCount[stream]);
+ return NO_ERROR;
+ }
+
+ // do not change in call volume if bluetooth is connected and vice versa
+ if ((stream == AudioSystem::VOICE_CALL && mForceUse[AudioSystem::FOR_COMMUNICATION] == AudioSystem::FORCE_BT_SCO) ||
+ (stream == AudioSystem::BLUETOOTH_SCO && mForceUse[AudioSystem::FOR_COMMUNICATION] != AudioSystem::FORCE_BT_SCO)) {
+ LOGV("checkAndSetVolume() cannot set stream %d volume with force use = %d for comm",
+ stream, mForceUse[AudioSystem::FOR_COMMUNICATION]);
+ return INVALID_OPERATION;
+ }
+
+ float volume = computeVolume(stream, index, output, device);
+ // do not set volume if the float value did not change
+ if (volume != mOutputs.valueFor(output)->mCurVolume[stream] || force) {
+ mOutputs.valueFor(output)->mCurVolume[stream] = volume;
+ LOGV("setStreamVolume() for output %d stream %d, volume %f, delay %d", output, stream, volume, delayMs);
+ if (stream == AudioSystem::VOICE_CALL ||
+ stream == AudioSystem::DTMF ||
+ stream == AudioSystem::BLUETOOTH_SCO) {
+ float voiceVolume = -1.0;
+ // offset value to reflect actual hardware volume that never reaches 0
+ // 1% corresponds roughly to first step in VOICE_CALL stream volume setting (see AudioService.java)
+ volume = 0.01 + 0.99 * volume;
+ if (stream == AudioSystem::VOICE_CALL) {
+ voiceVolume = (float)index/(float)mStreams[stream].mIndexMax;
+ } else if (stream == AudioSystem::BLUETOOTH_SCO) {
+ voiceVolume = 1.0;
+ }
+ if (voiceVolume >= 0 && output == mHardwareOutput) {
+ mpClientInterface->setVoiceVolume(voiceVolume, delayMs);
+ }
+ }
+ mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output, delayMs);
+ }
+
+ return NO_ERROR;
+}
+
+void AudioPolicyManagerBase::applyStreamVolumes(audio_io_handle_t output, uint32_t device, int delayMs)
+{
+ LOGV("applyStreamVolumes() for output %d and device %x", output, device);
+
+ for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
+ checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, device, delayMs);
+ }
+}
+
+void AudioPolicyManagerBase::setStrategyMute(routing_strategy strategy, bool on, audio_io_handle_t output, int delayMs)
+{
+ LOGV("setStrategyMute() strategy %d, mute %d, output %d", strategy, on, output);
+ for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
+ if (getStrategy((AudioSystem::stream_type)stream) == strategy) {
+ setStreamMute(stream, on, output, delayMs);
+ }
+ }
+}
+
+void AudioPolicyManagerBase::setStreamMute(int stream, bool on, audio_io_handle_t output, int delayMs)
+{
+ StreamDescriptor &streamDesc = mStreams[stream];
+ AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output);
+
+ LOGV("setStreamMute() stream %d, mute %d, output %d, mMuteCount %d", stream, on, output, outputDesc->mMuteCount[stream]);
+
+ if (on) {
+ if (outputDesc->mMuteCount[stream] == 0) {
+ if (streamDesc.mCanBeMuted) {
+ checkAndSetVolume(stream, 0, output, outputDesc->device(), delayMs);
+ }
+ }
+ // increment mMuteCount after calling checkAndSetVolume() so that volume change is not ignored
+ outputDesc->mMuteCount[stream]++;
+ } else {
+ if (outputDesc->mMuteCount[stream] == 0) {
+ LOGW("setStreamMute() unmuting non muted stream!");
+ return;
+ }
+ if (--outputDesc->mMuteCount[stream] == 0) {
+ checkAndSetVolume(stream, streamDesc.mIndexCur, output, outputDesc->device(), delayMs);
+ }
+ }
+}
+
+void AudioPolicyManagerBase::handleIncallSonification(int stream, bool starting, bool stateChange)
+{
+ // if the stream pertains to sonification strategy and we are in call we must
+ // mute the stream if it is low visibility. If it is high visibility, we must play a tone
+ // in the device used for phone strategy and play the tone if the selected device does not
+ // interfere with the device used for phone strategy
+ // if stateChange is true, we are called from setPhoneState() and we must mute or unmute as
+ // many times as there are active tracks on the output
+
+ if (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) {
+ AudioOutputDescriptor *outputDesc = mOutputs.valueFor(mHardwareOutput);
+ LOGV("handleIncallSonification() stream %d starting %d device %x stateChange %d",
+ stream, starting, outputDesc->mDevice, stateChange);
+ if (outputDesc->mRefCount[stream]) {
+ int muteCount = 1;
+ if (stateChange) {
+ muteCount = outputDesc->mRefCount[stream];
+ }
+ if (AudioSystem::isLowVisibility((AudioSystem::stream_type)stream)) {
+ LOGV("handleIncallSonification() low visibility, muteCount %d", muteCount);
+ for (int i = 0; i < muteCount; i++) {
+ setStreamMute(stream, starting, mHardwareOutput);
+ }
+ } else {
+ LOGV("handleIncallSonification() high visibility");
+ if (outputDesc->device() & getDeviceForStrategy(STRATEGY_PHONE)) {
+ LOGV("handleIncallSonification() high visibility muted, muteCount %d", muteCount);
+ for (int i = 0; i < muteCount; i++) {
+ setStreamMute(stream, starting, mHardwareOutput);
+ }
+ }
+ if (starting) {
+ mpClientInterface->startTone(ToneGenerator::TONE_SUP_CALL_WAITING, AudioSystem::VOICE_CALL);
+ } else {
+ mpClientInterface->stopTone();
+ }
+ }
+ }
+ }
+}
+
+// --- AudioOutputDescriptor class implementation
+
+AudioPolicyManagerBase::AudioOutputDescriptor::AudioOutputDescriptor()
+ : mId(0), mSamplingRate(0), mFormat(0), mChannels(0), mLatency(0),
+ mFlags((AudioSystem::output_flags)0), mDevice(0), mOutput1(0), mOutput2(0)
+{
+ // clear usage count for all stream types
+ for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
+ mRefCount[i] = 0;
+ mCurVolume[i] = -1.0;
+ mMuteCount[i] = 0;
+ }
+}
+
+uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::device()
+{
+ uint32_t device = 0;
+ if (isDuplicated()) {
+ device = mOutput1->mDevice | mOutput2->mDevice;
+ } else {
+ device = mDevice;
+ }
+ return device;
+}
+
+void AudioPolicyManagerBase::AudioOutputDescriptor::changeRefCount(AudioSystem::stream_type stream, int delta)
+{
+ // forward usage count change to attached outputs
+ if (isDuplicated()) {
+ mOutput1->changeRefCount(stream, delta);
+ mOutput2->changeRefCount(stream, delta);
+ }
+ if ((delta + (int)mRefCount[stream]) < 0) {
+ LOGW("changeRefCount() invalid delta %d for stream %d, refCount %d", delta, stream, mRefCount[stream]);
+ mRefCount[stream] = 0;
+ return;
+ }
+ mRefCount[stream] += delta;
+ LOGV("changeRefCount() stream %d, count %d", stream, mRefCount[stream]);
+}
+
+uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::refCount()
+{
+ uint32_t refcount = 0;
+ for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
+ refcount += mRefCount[i];
+ }
+ return refcount;
+}
+
+uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::strategyRefCount(routing_strategy strategy)
+{
+ uint32_t refCount = 0;
+ for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
+ if (getStrategy((AudioSystem::stream_type)i) == strategy) {
+ refCount += mRefCount[i];
+ }
+ }
+ return refCount;
+}
+
+
+status_t AudioPolicyManagerBase::AudioOutputDescriptor::dump(int fd)
+{
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+ String8 result;
+
+ snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Format: %d\n", mFormat);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Channels: %08x\n", mChannels);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Latency: %d\n", mLatency);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Flags %08x\n", mFlags);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Devices %08x\n", device());
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Stream volume refCount muteCount\n");
+ result.append(buffer);
+ for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
+ snprintf(buffer, SIZE, " %02d %.03f %02d %02d\n", i, mCurVolume[i], mRefCount[i], mMuteCount[i]);
+ result.append(buffer);
+ }
+ write(fd, result.string(), result.size());
+
+ return NO_ERROR;
+}
+
+// --- AudioInputDescriptor class implementation
+
+AudioPolicyManagerBase::AudioInputDescriptor::AudioInputDescriptor()
+ : mSamplingRate(0), mFormat(0), mChannels(0),
+ mAcoustics((AudioSystem::audio_in_acoustics)0), mDevice(0), mRefCount(0)
+{
+}
+
+status_t AudioPolicyManagerBase::AudioInputDescriptor::dump(int fd)
+{
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+ String8 result;
+
+ snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Format: %d\n", mFormat);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Channels: %08x\n", mChannels);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Acoustics %08x\n", mAcoustics);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Devices %08x\n", mDevice);
+ result.append(buffer);
+ snprintf(buffer, SIZE, " Ref Count %d\n", mRefCount);
+ result.append(buffer);
+ write(fd, result.string(), result.size());
+
+ return NO_ERROR;
+}
+
+// --- StreamDescriptor class implementation
+
+void AudioPolicyManagerBase::StreamDescriptor::dump(char* buffer, size_t size)
+{
+ snprintf(buffer, size, " %02d %02d %02d %d\n",
+ mIndexMin,
+ mIndexMax,
+ mIndexCur,
+ mCanBeMuted);
+}
+
+
+}; // namespace android
diff --git a/libs/audioflinger/AudioPolicyManagerGeneric.cpp b/libs/audioflinger/AudioPolicyManagerGeneric.cpp
deleted file mode 100644
index 8cfc204..0000000
--- a/libs/audioflinger/AudioPolicyManagerGeneric.cpp
+++ /dev/null
@@ -1,945 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "AudioPolicyManagerGeneric"
-//#define LOG_NDEBUG 0
-#include <utils/Log.h>
-#include "AudioPolicyManagerGeneric.h"
-#include <media/mediarecorder.h>
-
-namespace android {
-
-
-// ----------------------------------------------------------------------------
-// AudioPolicyInterface implementation
-// ----------------------------------------------------------------------------
-
-
-status_t AudioPolicyManagerGeneric::setDeviceConnectionState(AudioSystem::audio_devices device,
- AudioSystem::device_connection_state state,
- const char *device_address)
-{
-
- LOGV("setDeviceConnectionState() device: %x, state %d, address %s", device, state, device_address);
-
- // connect/disconnect only 1 device at a time
- if (AudioSystem::popCount(device) != 1) return BAD_VALUE;
-
- if (strlen(device_address) >= MAX_DEVICE_ADDRESS_LEN) {
- LOGE("setDeviceConnectionState() invalid address: %s", device_address);
- return BAD_VALUE;
- }
-
- // handle output devices
- if (AudioSystem::isOutputDevice(device)) {
- switch (state)
- {
- // handle output device connection
- case AudioSystem::DEVICE_STATE_AVAILABLE:
- if (mAvailableOutputDevices & device) {
- LOGW("setDeviceConnectionState() device already connected: %x", device);
- return INVALID_OPERATION;
- }
- LOGV("setDeviceConnectionState() connecting device %x", device);
-
- // register new device as available
- mAvailableOutputDevices |= device;
- break;
- // handle output device disconnection
- case AudioSystem::DEVICE_STATE_UNAVAILABLE:
- if (!(mAvailableOutputDevices & device)) {
- LOGW("setDeviceConnectionState() device not connected: %x", device);
- return INVALID_OPERATION;
- }
- LOGV("setDeviceConnectionState() disconnecting device %x", device);
- // remove device from available output devices
- mAvailableOutputDevices &= ~device;
- break;
-
- default:
- LOGE("setDeviceConnectionState() invalid state: %x", state);
- return BAD_VALUE;
- }
- return NO_ERROR;
- }
- // handle input devices
- if (AudioSystem::isInputDevice(device)) {
- switch (state)
- {
- // handle input device connection
- case AudioSystem::DEVICE_STATE_AVAILABLE:
- if (mAvailableInputDevices & device) {
- LOGW("setDeviceConnectionState() device already connected: %d", device);
- return INVALID_OPERATION;
- }
- mAvailableInputDevices |= device;
- break;
-
- // handle input device disconnection
- case AudioSystem::DEVICE_STATE_UNAVAILABLE:
- if (!(mAvailableInputDevices & device)) {
- LOGW("setDeviceConnectionState() device not connected: %d", device);
- return INVALID_OPERATION;
- }
- mAvailableInputDevices &= ~device;
- break;
-
- default:
- LOGE("setDeviceConnectionState() invalid state: %x", state);
- return BAD_VALUE;
- }
- return NO_ERROR;
- }
-
- LOGW("setDeviceConnectionState() invalid device: %x", device);
- return BAD_VALUE;
-}
-
-AudioSystem::device_connection_state AudioPolicyManagerGeneric::getDeviceConnectionState(AudioSystem::audio_devices device,
- const char *device_address)
-{
- AudioSystem::device_connection_state state = AudioSystem::DEVICE_STATE_UNAVAILABLE;
- String8 address = String8(device_address);
- if (AudioSystem::isOutputDevice(device)) {
- if (device & mAvailableOutputDevices) {
- state = AudioSystem::DEVICE_STATE_AVAILABLE;
- }
- } else if (AudioSystem::isInputDevice(device)) {
- if (device & mAvailableInputDevices) {
- state = AudioSystem::DEVICE_STATE_AVAILABLE;
- }
- }
-
- return state;
-}
-
-void AudioPolicyManagerGeneric::setPhoneState(int state)
-{
- LOGV("setPhoneState() state %d", state);
- uint32_t newDevice = 0;
- if (state < 0 || state >= AudioSystem::NUM_MODES) {
- LOGW("setPhoneState() invalid state %d", state);
- return;
- }
-
- if (state == mPhoneState ) {
- LOGW("setPhoneState() setting same state %d", state);
- return;
- }
- // store previous phone state for management of sonification strategy below
- int oldState = mPhoneState;
- mPhoneState = state;
-
- // if leaving or entering in call state, handle special case of active streams
- // pertaining to sonification strategy see handleIncallSonification()
- if (state == AudioSystem::MODE_IN_CALL ||
- oldState == AudioSystem::MODE_IN_CALL) {
- bool starting = (state == AudioSystem::MODE_IN_CALL) ? true : false;
- LOGV("setPhoneState() in call state management: new state is %d", state);
- for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) {
- handleIncallSonification(stream, starting);
- }
- }
-}
-
-void AudioPolicyManagerGeneric::setRingerMode(uint32_t mode, uint32_t mask)
-{
- LOGV("setRingerMode() mode %x, mask %x", mode, mask);
-
- mRingerMode = mode;
-}
-
-void AudioPolicyManagerGeneric::setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config)
-{
- LOGV("setForceUse) usage %d, config %d, mPhoneState %d", usage, config, mPhoneState);
- mForceUse[usage] = config;
-}
-
-AudioSystem::forced_config AudioPolicyManagerGeneric::getForceUse(AudioSystem::force_use usage)
-{
- return mForceUse[usage];
-}
-
-void AudioPolicyManagerGeneric::setSystemProperty(const char* property, const char* value)
-{
- LOGV("setSystemProperty() property %s, value %s", property, value);
- if (strcmp(property, "ro.camera.sound.forced") == 0) {
- if (atoi(value)) {
- LOGV("ENFORCED_AUDIBLE cannot be muted");
- mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = false;
- } else {
- LOGV("ENFORCED_AUDIBLE can be muted");
- mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = true;
- }
- }
-}
-
-audio_io_handle_t AudioPolicyManagerGeneric::getOutput(AudioSystem::stream_type stream,
- uint32_t samplingRate,
- uint32_t format,
- uint32_t channels,
- AudioSystem::output_flags flags)
-{
- LOGV("getOutput() stream %d, samplingRate %d, format %d, channels %x, flags %x", stream, samplingRate, format, channels, flags);
-
-#ifdef AUDIO_POLICY_TEST
- if (mCurOutput != 0) {
- LOGV("getOutput() test output mCurOutput %d, samplingRate %d, format %d, channels %x, mDirectOutput %d",
- mCurOutput, mTestSamplingRate, mTestFormat, mTestChannels, mDirectOutput);
-
- if (mTestOutputs[mCurOutput] == 0) {
- LOGV("getOutput() opening test output");
- AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
- outputDesc->mDevice = mTestDevice;
- outputDesc->mSamplingRate = mTestSamplingRate;
- outputDesc->mFormat = mTestFormat;
- outputDesc->mChannels = mTestChannels;
- outputDesc->mLatency = mTestLatencyMs;
- outputDesc->mFlags = (AudioSystem::output_flags)(mDirectOutput ? AudioSystem::OUTPUT_FLAG_DIRECT : 0);
- outputDesc->mRefCount[stream] = 0;
- mTestOutputs[mCurOutput] = mpClientInterface->openOutput(&outputDesc->mDevice,
- &outputDesc->mSamplingRate,
- &outputDesc->mFormat,
- &outputDesc->mChannels,
- &outputDesc->mLatency,
- outputDesc->mFlags);
- if (mTestOutputs[mCurOutput]) {
- AudioParameter outputCmd = AudioParameter();
- outputCmd.addInt(String8("set_id"),mCurOutput);
- mpClientInterface->setParameters(mTestOutputs[mCurOutput],outputCmd.toString());
- mOutputs.add(mTestOutputs[mCurOutput], outputDesc);
- }
- }
- return mTestOutputs[mCurOutput];
- }
-#endif //AUDIO_POLICY_TEST
- if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) ||
- (format != 0 && !AudioSystem::isLinearPCM(format)) ||
- (channels != 0 && channels != AudioSystem::CHANNEL_OUT_MONO && channels != AudioSystem::CHANNEL_OUT_STEREO)) {
- return 0;
- }
-
- return mHardwareOutput;
-}
-
-status_t AudioPolicyManagerGeneric::startOutput(audio_io_handle_t output, AudioSystem::stream_type stream)
-{
- LOGV("startOutput() output %d, stream %d", output, stream);
- ssize_t index = mOutputs.indexOfKey(output);
- if (index < 0) {
- LOGW("startOutput() unknow output %d", output);
- return BAD_VALUE;
- }
-
- AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
-
- // handle special case for sonification while in call
- if (mPhoneState == AudioSystem::MODE_IN_CALL) {
- handleIncallSonification(stream, true);
- }
-
- // incremenent usage count for this stream on the requested output:
- outputDesc->changeRefCount(stream, 1);
- return NO_ERROR;
-}
-
-status_t AudioPolicyManagerGeneric::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream)
-{
- LOGV("stopOutput() output %d, stream %d", output, stream);
- ssize_t index = mOutputs.indexOfKey(output);
- if (index < 0) {
- LOGW("stopOutput() unknow output %d", output);
- return BAD_VALUE;
- }
-
- AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
-
- // handle special case for sonification while in call
- if (mPhoneState == AudioSystem::MODE_IN_CALL) {
- handleIncallSonification(stream, false);
- }
-
- if (outputDesc->isUsedByStream(stream)) {
- // decrement usage count of this stream on the output
- outputDesc->changeRefCount(stream, -1);
- return NO_ERROR;
- } else {
- LOGW("stopOutput() refcount is already 0 for output %d", output);
- return INVALID_OPERATION;
- }
-}
-
-void AudioPolicyManagerGeneric::releaseOutput(audio_io_handle_t output)
-{
- LOGV("releaseOutput() %d", output);
- ssize_t index = mOutputs.indexOfKey(output);
- if (index < 0) {
- LOGW("releaseOutput() releasing unknown output %d", output);
- return;
- }
-
-#ifdef AUDIO_POLICY_TEST
- int testIndex = testOutputIndex(output);
- if (testIndex != 0) {
- AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index);
- if (outputDesc->refCount() == 0) {
- mpClientInterface->closeOutput(output);
- delete mOutputs.valueAt(index);
- mOutputs.removeItem(output);
- mTestOutputs[testIndex] = 0;
- }
- }
-#endif //AUDIO_POLICY_TEST
-}
-
-audio_io_handle_t AudioPolicyManagerGeneric::getInput(int inputSource,
- uint32_t samplingRate,
- uint32_t format,
- uint32_t channels,
- AudioSystem::audio_in_acoustics acoustics)
-{
- audio_io_handle_t input = 0;
- uint32_t device;
-
- LOGV("getInput() inputSource %d, samplingRate %d, format %d, channels %x, acoustics %x", inputSource, samplingRate, format, channels, acoustics);
-
- AudioInputDescriptor *inputDesc = new AudioInputDescriptor();
- inputDesc->mDevice = AudioSystem::DEVICE_IN_BUILTIN_MIC;
- inputDesc->mSamplingRate = samplingRate;
- inputDesc->mFormat = format;
- inputDesc->mChannels = channels;
- inputDesc->mAcoustics = acoustics;
- inputDesc->mRefCount = 0;
- input = mpClientInterface->openInput(&inputDesc->mDevice,
- &inputDesc->mSamplingRate,
- &inputDesc->mFormat,
- &inputDesc->mChannels,
- inputDesc->mAcoustics);
-
- // only accept input with the exact requested set of parameters
- if ((samplingRate != inputDesc->mSamplingRate) ||
- (format != inputDesc->mFormat) ||
- (channels != inputDesc->mChannels)) {
- LOGV("getOutput() failed opening input: samplingRate %d, format %d, channels %d",
- samplingRate, format, channels);
- mpClientInterface->closeInput(input);
- delete inputDesc;
- return 0;
- }
- mInputs.add(input, inputDesc);
- return input;
-}
-
-status_t AudioPolicyManagerGeneric::startInput(audio_io_handle_t input)
-{
- LOGV("startInput() input %d", input);
- ssize_t index = mInputs.indexOfKey(input);
- if (index < 0) {
- LOGW("startInput() unknow input %d", input);
- return BAD_VALUE;
- }
- AudioInputDescriptor *inputDesc = mInputs.valueAt(index);
-
-#ifdef AUDIO_POLICY_TEST
- if (mTestInput == 0)
-#endif //AUDIO_POLICY_TEST
- {
- // refuse 2 active AudioRecord clients at the same time
- for (size_t i = 0; i < mInputs.size(); i++) {
- if (mInputs.valueAt(i)->mRefCount > 0) {
- LOGW("startInput() input %d, other input %d already started", input, mInputs.keyAt(i));
- return INVALID_OPERATION;
- }
- }
- }
-
- inputDesc->mRefCount = 1;
- return NO_ERROR;
-}
-
-status_t AudioPolicyManagerGeneric::stopInput(audio_io_handle_t input)
-{
- LOGV("stopInput() input %d", input);
- ssize_t index = mInputs.indexOfKey(input);
- if (index < 0) {
- LOGW("stopInput() unknow input %d", input);
- return BAD_VALUE;
- }
- AudioInputDescriptor *inputDesc = mInputs.valueAt(index);
-
- if (inputDesc->mRefCount == 0) {
- LOGW("stopInput() input %d already stopped", input);
- return INVALID_OPERATION;
- } else {
- inputDesc->mRefCount = 0;
- return NO_ERROR;
- }
-}
-
-void AudioPolicyManagerGeneric::releaseInput(audio_io_handle_t input)
-{
- LOGV("releaseInput() %d", input);
- ssize_t index = mInputs.indexOfKey(input);
- if (index < 0) {
- LOGW("releaseInput() releasing unknown input %d", input);
- return;
- }
- mpClientInterface->closeInput(input);
- delete mInputs.valueAt(index);
- mInputs.removeItem(input);
-}
-
-
-
-void AudioPolicyManagerGeneric::initStreamVolume(AudioSystem::stream_type stream,
- int indexMin,
- int indexMax)
-{
- LOGV("initStreamVolume() stream %d, min %d, max %d", stream , indexMin, indexMax);
- mStreams[stream].mIndexMin = indexMin;
- mStreams[stream].mIndexMax = indexMax;
-}
-
-status_t AudioPolicyManagerGeneric::setStreamVolumeIndex(AudioSystem::stream_type stream, int index)
-{
-
- if ((index < mStreams[stream].mIndexMin) || (index > mStreams[stream].mIndexMax)) {
- return BAD_VALUE;
- }
-
- LOGV("setStreamVolumeIndex() stream %d, index %d", stream, index);
- mStreams[stream].mIndexCur = index;
-
- // do not change actual stream volume if the stream is muted
- if (mStreams[stream].mMuteCount != 0) {
- return NO_ERROR;
- }
-
- // Do not changed in call volume if bluetooth is connected and vice versa
- if ((stream == AudioSystem::VOICE_CALL && mForceUse[AudioSystem::FOR_COMMUNICATION] == AudioSystem::FORCE_BT_SCO) ||
- (stream == AudioSystem::BLUETOOTH_SCO && mForceUse[AudioSystem::FOR_COMMUNICATION] != AudioSystem::FORCE_BT_SCO)) {
- LOGV("setStreamVolumeIndex() cannot set stream %d volume with force use = %d for comm",
- stream, mForceUse[AudioSystem::FOR_COMMUNICATION]);
- return INVALID_OPERATION;
- }
-
- // compute and apply stream volume on all outputs according to connected device
- for (size_t i = 0; i < mOutputs.size(); i++) {
- AudioOutputDescriptor *outputDesc = mOutputs.valueAt(i);
- uint32_t device = outputDesc->device();
-
- float volume = computeVolume((int)stream, index, device);
-
- LOGV("setStreamVolume() for output %d stream %d, volume %f", mOutputs.keyAt(i), stream, volume);
- mpClientInterface->setStreamVolume(stream, volume, mOutputs.keyAt(i));
- }
- return NO_ERROR;
-}
-
-status_t AudioPolicyManagerGeneric::getStreamVolumeIndex(AudioSystem::stream_type stream, int *index)
-{
- if (index == 0) {
- return BAD_VALUE;
- }
- LOGV("getStreamVolumeIndex() stream %d", stream);
- *index = mStreams[stream].mIndexCur;
- return NO_ERROR;
-}
-
-status_t AudioPolicyManagerGeneric::dump(int fd)
-{
- const size_t SIZE = 256;
- char buffer[SIZE];
- String8 result;
-
- snprintf(buffer, SIZE, "\nAudioPolicyManager Dump: %p\n", this);
- result.append(buffer);
- snprintf(buffer, SIZE, " Hardware Output: %d\n", mHardwareOutput);
- result.append(buffer);
- snprintf(buffer, SIZE, " Output devices: %08x\n", mAvailableOutputDevices);
- result.append(buffer);
- snprintf(buffer, SIZE, " Input devices: %08x\n", mAvailableInputDevices);
- result.append(buffer);
- snprintf(buffer, SIZE, " Phone state: %d\n", mPhoneState);
- result.append(buffer);
- snprintf(buffer, SIZE, " Ringer mode: %d\n", mRingerMode);
- result.append(buffer);
- snprintf(buffer, SIZE, " Force use for communications %d\n", mForceUse[AudioSystem::FOR_COMMUNICATION]);
- result.append(buffer);
- snprintf(buffer, SIZE, " Force use for media %d\n", mForceUse[AudioSystem::FOR_MEDIA]);
- result.append(buffer);
- snprintf(buffer, SIZE, " Force use for record %d\n", mForceUse[AudioSystem::FOR_RECORD]);
- result.append(buffer);
- write(fd, result.string(), result.size());
-
- snprintf(buffer, SIZE, "\nOutputs dump:\n");
- write(fd, buffer, strlen(buffer));
- for (size_t i = 0; i < mOutputs.size(); i++) {
- snprintf(buffer, SIZE, "- Output %d dump:\n", mOutputs.keyAt(i));
- write(fd, buffer, strlen(buffer));
- mOutputs.valueAt(i)->dump(fd);
- }
-
- snprintf(buffer, SIZE, "\nInputs dump:\n");
- write(fd, buffer, strlen(buffer));
- for (size_t i = 0; i < mInputs.size(); i++) {
- snprintf(buffer, SIZE, "- Input %d dump:\n", mInputs.keyAt(i));
- write(fd, buffer, strlen(buffer));
- mInputs.valueAt(i)->dump(fd);
- }
-
- snprintf(buffer, SIZE, "\nStreams dump:\n");
- write(fd, buffer, strlen(buffer));
- snprintf(buffer, SIZE, " Stream Index Min Index Max Index Cur Mute Count Can be muted\n");
- write(fd, buffer, strlen(buffer));
- for (size_t i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
- snprintf(buffer, SIZE, " %02d", i);
- mStreams[i].dump(buffer + 3, SIZE);
- write(fd, buffer, strlen(buffer));
- }
-
- return NO_ERROR;
-}
-
-// ----------------------------------------------------------------------------
-// AudioPolicyManagerGeneric
-// ----------------------------------------------------------------------------
-
-// --- class factory
-
-AudioPolicyManagerGeneric::AudioPolicyManagerGeneric(AudioPolicyClientInterface *clientInterface)
- :
-#ifdef AUDIO_POLICY_TEST
- Thread(false),
-#endif //AUDIO_POLICY_TEST
- mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0)
-{
- mpClientInterface = clientInterface;
-
- for (int i = 0; i < AudioSystem::NUM_FORCE_USE; i++) {
- mForceUse[i] = AudioSystem::FORCE_NONE;
- }
-
- // devices available by default are speaker, ear piece and microphone
- mAvailableOutputDevices = AudioSystem::DEVICE_OUT_SPEAKER;
- mAvailableInputDevices = AudioSystem::DEVICE_IN_BUILTIN_MIC;
-
- // open hardware output
- AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
- outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER;
- mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
- &outputDesc->mSamplingRate,
- &outputDesc->mFormat,
- &outputDesc->mChannels,
- &outputDesc->mLatency,
- outputDesc->mFlags);
-
- if (mHardwareOutput == 0) {
- LOGE("Failed to initialize hardware output stream, samplingRate: %d, format %d, channels %d",
- outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels);
- } else {
- mOutputs.add(mHardwareOutput, outputDesc);
- }
-
-#ifdef AUDIO_POLICY_TEST
- AudioParameter outputCmd = AudioParameter();
- outputCmd.addInt(String8("set_id"), 0);
- mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString());
-
- mTestDevice = AudioSystem::DEVICE_OUT_SPEAKER;
- mTestSamplingRate = 44100;
- mTestFormat = AudioSystem::PCM_16_BIT;
- mTestChannels = AudioSystem::CHANNEL_OUT_STEREO;
- mTestLatencyMs = 0;
- mCurOutput = 0;
- mDirectOutput = false;
- for (int i = 0; i < NUM_TEST_OUTPUTS; i++) {
- mTestOutputs[i] = 0;
- }
-
- const size_t SIZE = 256;
- char buffer[SIZE];
- snprintf(buffer, SIZE, "AudioPolicyManagerTest");
- run(buffer, ANDROID_PRIORITY_AUDIO);
-#endif //AUDIO_POLICY_TEST
-}
-
-AudioPolicyManagerGeneric::~AudioPolicyManagerGeneric()
-{
-#ifdef AUDIO_POLICY_TEST
- exit();
-#endif //AUDIO_POLICY_TEST
-
- for (size_t i = 0; i < mOutputs.size(); i++) {
- mpClientInterface->closeOutput(mOutputs.keyAt(i));
- delete mOutputs.valueAt(i);
- }
- mOutputs.clear();
- for (size_t i = 0; i < mInputs.size(); i++) {
- mpClientInterface->closeInput(mInputs.keyAt(i));
- delete mInputs.valueAt(i);
- }
- mInputs.clear();
-}
-
-#ifdef AUDIO_POLICY_TEST
-bool AudioPolicyManagerGeneric::threadLoop()
-{
- LOGV("entering threadLoop()");
- while (!exitPending())
- {
- String8 command;
- int valueInt;
- String8 value;
-
- Mutex::Autolock _l(mLock);
- mWaitWorkCV.waitRelative(mLock, milliseconds(50));
-
- command = mpClientInterface->getParameters(0, String8("test_cmd_policy"));
- AudioParameter param = AudioParameter(command);
-
- if (param.getInt(String8("test_cmd_policy"), valueInt) == NO_ERROR &&
- valueInt != 0) {
- LOGV("Test command %s received", command.string());
- String8 target;
- if (param.get(String8("target"), target) != NO_ERROR) {
- target = "Manager";
- }
- if (param.getInt(String8("test_cmd_policy_output"), valueInt) == NO_ERROR) {
- param.remove(String8("test_cmd_policy_output"));
- mCurOutput = valueInt;
- }
- if (param.get(String8("test_cmd_policy_direct"), value) == NO_ERROR) {
- param.remove(String8("test_cmd_policy_direct"));
- if (value == "false") {
- mDirectOutput = false;
- } else if (value == "true") {
- mDirectOutput = true;
- }
- }
- if (param.getInt(String8("test_cmd_policy_input"), valueInt) == NO_ERROR) {
- param.remove(String8("test_cmd_policy_input"));
- mTestInput = valueInt;
- }
-
- if (param.get(String8("test_cmd_policy_format"), value) == NO_ERROR) {
- param.remove(String8("test_cmd_policy_format"));
- int format = AudioSystem::INVALID_FORMAT;
- if (value == "PCM 16 bits") {
- format = AudioSystem::PCM_16_BIT;
- } else if (value == "PCM 8 bits") {
- format = AudioSystem::PCM_8_BIT;
- } else if (value == "Compressed MP3") {
- format = AudioSystem::MP3;
- }
- if (format != AudioSystem::INVALID_FORMAT) {
- if (target == "Manager") {
- mTestFormat = format;
- } else if (mTestOutputs[mCurOutput] != 0) {
- AudioParameter outputParam = AudioParameter();
- outputParam.addInt(String8("format"), format);
- mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
- }
- }
- }
- if (param.get(String8("test_cmd_policy_channels"), value) == NO_ERROR) {
- param.remove(String8("test_cmd_policy_channels"));
- int channels = 0;
-
- if (value == "Channels Stereo") {
- channels = AudioSystem::CHANNEL_OUT_STEREO;
- } else if (value == "Channels Mono") {
- channels = AudioSystem::CHANNEL_OUT_MONO;
- }
- if (channels != 0) {
- if (target == "Manager") {
- mTestChannels = channels;
- } else if (mTestOutputs[mCurOutput] != 0) {
- AudioParameter outputParam = AudioParameter();
- outputParam.addInt(String8("channels"), channels);
- mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
- }
- }
- }
- if (param.getInt(String8("test_cmd_policy_sampleRate"), valueInt) == NO_ERROR) {
- param.remove(String8("test_cmd_policy_sampleRate"));
- if (valueInt >= 0 && valueInt <= 96000) {
- int samplingRate = valueInt;
- if (target == "Manager") {
- mTestSamplingRate = samplingRate;
- } else if (mTestOutputs[mCurOutput] != 0) {
- AudioParameter outputParam = AudioParameter();
- outputParam.addInt(String8("sampling_rate"), samplingRate);
- mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString());
- }
- }
- }
-
- if (param.get(String8("test_cmd_policy_reopen"), value) == NO_ERROR) {
- param.remove(String8("test_cmd_policy_reopen"));
-
- mpClientInterface->closeOutput(mHardwareOutput);
- delete mOutputs.valueFor(mHardwareOutput);
- mOutputs.removeItem(mHardwareOutput);
-
- AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor();
- outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER;
- mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice,
- &outputDesc->mSamplingRate,
- &outputDesc->mFormat,
- &outputDesc->mChannels,
- &outputDesc->mLatency,
- outputDesc->mFlags);
- if (mHardwareOutput == 0) {
- LOGE("Failed to reopen hardware output stream, samplingRate: %d, format %d, channels %d",
- outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels);
- } else {
- AudioParameter outputCmd = AudioParameter();
- outputCmd.addInt(String8("set_id"), 0);
- mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString());
- mOutputs.add(mHardwareOutput, outputDesc);
- }
- }
-
-
- mpClientInterface->setParameters(0, String8("test_cmd_policy="));
- }
- }
- return false;
-}
-
-void AudioPolicyManagerGeneric::exit()
-{
- {
- AutoMutex _l(mLock);
- requestExit();
- mWaitWorkCV.signal();
- }
- requestExitAndWait();
-}
-
-int AudioPolicyManagerGeneric::testOutputIndex(audio_io_handle_t output)
-{
- for (int i = 0; i < NUM_TEST_OUTPUTS; i++) {
- if (output == mTestOutputs[i]) return i;
- }
- return 0;
-}
-#endif //AUDIO_POLICY_TEST
-
-// ---
-
-AudioPolicyManagerGeneric::routing_strategy AudioPolicyManagerGeneric::getStrategy(AudioSystem::stream_type stream)
-{
- // stream to strategy mapping
- switch (stream) {
- case AudioSystem::VOICE_CALL:
- case AudioSystem::BLUETOOTH_SCO:
- return STRATEGY_PHONE;
- case AudioSystem::RING:
- case AudioSystem::NOTIFICATION:
- case AudioSystem::ALARM:
- case AudioSystem::ENFORCED_AUDIBLE:
- return STRATEGY_SONIFICATION;
- case AudioSystem::DTMF:
- return STRATEGY_DTMF;
- default:
- LOGE("unknown stream type");
- case AudioSystem::SYSTEM:
- // NOTE: SYSTEM stream uses MEDIA strategy because muting music and switching outputs
- // while key clicks are played produces a poor result
- case AudioSystem::TTS:
- case AudioSystem::MUSIC:
- return STRATEGY_MEDIA;
- }
-}
-
-
-float AudioPolicyManagerGeneric::computeVolume(int stream, int index, uint32_t device)
-{
- float volume = 1.0;
-
- StreamDescriptor &streamDesc = mStreams[stream];
-
- // Force max volume if stream cannot be muted
- if (!streamDesc.mCanBeMuted) index = streamDesc.mIndexMax;
-
- int volInt = (100 * (index - streamDesc.mIndexMin)) / (streamDesc.mIndexMax - streamDesc.mIndexMin);
- volume = AudioSystem::linearToLog(volInt);
-
- return volume;
-}
-
-void AudioPolicyManagerGeneric::setStreamMute(int stream, bool on, audio_io_handle_t output)
-{
- LOGV("setStreamMute() stream %d, mute %d, output %d", stream, on, output);
-
- StreamDescriptor &streamDesc = mStreams[stream];
-
- if (on) {
- if (streamDesc.mMuteCount++ == 0) {
- if (streamDesc.mCanBeMuted) {
- mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, 0, output);
- }
- }
- } else {
- if (streamDesc.mMuteCount == 0) {
- LOGW("setStreamMute() unmuting non muted stream!");
- return;
- }
- if (--streamDesc.mMuteCount == 0) {
- uint32_t device = mOutputs.valueFor(output)->mDevice;
- float volume = computeVolume(stream, streamDesc.mIndexCur, device);
- mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output);
- }
- }
-}
-
-void AudioPolicyManagerGeneric::handleIncallSonification(int stream, bool starting)
-{
- // if the stream pertains to sonification strategy and we are in call we must
- // mute the stream if it is low visibility. If it is high visibility, we must play a tone
- // in the device used for phone strategy and play the tone if the selected device does not
- // interfere with the device used for phone strategy
- if (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) {
- AudioOutputDescriptor *outputDesc = mOutputs.valueFor(mHardwareOutput);
- LOGV("handleIncallSonification() stream %d starting %d device %x", stream, starting, outputDesc->mDevice);
- if (outputDesc->isUsedByStream((AudioSystem::stream_type)stream)) {
- if (AudioSystem::isLowVisibility((AudioSystem::stream_type)stream)) {
- LOGV("handleIncallSonification() low visibility");
- setStreamMute(stream, starting, mHardwareOutput);
- } else {
- if (starting) {
- mpClientInterface->startTone(ToneGenerator::TONE_SUP_CALL_WAITING, AudioSystem::VOICE_CALL);
- } else {
- mpClientInterface->stopTone();
- }
- }
- }
- }
-}
-
-
-// --- AudioOutputDescriptor class implementation
-
-AudioPolicyManagerGeneric::AudioOutputDescriptor::AudioOutputDescriptor()
- : mSamplingRate(0), mFormat(0), mChannels(0), mLatency(0),
- mFlags((AudioSystem::output_flags)0), mDevice(0)
-{
- // clear usage count for all stream types
- for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
- mRefCount[i] = 0;
- }
-}
-
-uint32_t AudioPolicyManagerGeneric::AudioOutputDescriptor::device()
-{
- return mDevice;
-}
-
-void AudioPolicyManagerGeneric::AudioOutputDescriptor::changeRefCount(AudioSystem::stream_type stream, int delta)
-{
- if ((delta + (int)mRefCount[stream]) < 0) {
- LOGW("changeRefCount() invalid delta %d for stream %d, refCount %d", delta, stream, mRefCount[stream]);
- mRefCount[stream] = 0;
- return;
- }
- mRefCount[stream] += delta;
- LOGV("changeRefCount() stream %d, count %d", stream, mRefCount[stream]);
-}
-
-uint32_t AudioPolicyManagerGeneric::AudioOutputDescriptor::refCount()
-{
- uint32_t refcount = 0;
- for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) {
- refcount += mRefCount[i];
- }
- return refcount;
-}
-
-status_t AudioPolicyManagerGeneric::AudioOutputDescriptor::dump(int fd)
-{
- const size_t SIZE = 256;
- char buffer[SIZE];
- String8 result;
-
- snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate);
- result.append(buffer);
- snprintf(buffer, SIZE, " Format: %d\n", mFormat);
- result.append(buffer);
- snprintf(buffer, SIZE, " Channels: %08x\n", mChannels);
- result.append(buffer);
- snprintf(buffer, SIZE, " Latency: %d\n", mLatency);
- result.append(buffer);
- snprintf(buffer, SIZE, " Flags %08x\n", mFlags);
- result.append(buffer);
- snprintf(buffer, SIZE, " Devices %08x\n", mDevice);
- result.append(buffer);
- snprintf(buffer, SIZE, " Stream refCount\n");
- result.append(buffer);
- for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) {
- snprintf(buffer, SIZE, " %02d %d\n", i, mRefCount[i]);
- result.append(buffer);
- }
- write(fd, result.string(), result.size());
-
- return NO_ERROR;
-}
-
-// --- AudioInputDescriptor class implementation
-
-AudioPolicyManagerGeneric::AudioInputDescriptor::AudioInputDescriptor()
- : mSamplingRate(0), mFormat(0), mChannels(0),
- mAcoustics((AudioSystem::audio_in_acoustics)0), mDevice(0), mRefCount(0)
-{
-}
-
-status_t AudioPolicyManagerGeneric::AudioInputDescriptor::dump(int fd)
-{
- const size_t SIZE = 256;
- char buffer[SIZE];
- String8 result;
-
- snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate);
- result.append(buffer);
- snprintf(buffer, SIZE, " Format: %d\n", mFormat);
- result.append(buffer);
- snprintf(buffer, SIZE, " Channels: %08x\n", mChannels);
- result.append(buffer);
- snprintf(buffer, SIZE, " Acoustics %08x\n", mAcoustics);
- result.append(buffer);
- snprintf(buffer, SIZE, " Devices %08x\n", mDevice);
- result.append(buffer);
- snprintf(buffer, SIZE, " Ref Count %d\n", mRefCount);
- result.append(buffer);
- write(fd, result.string(), result.size());
-
- return NO_ERROR;
-}
-
-// --- StreamDescriptor class implementation
-
-void AudioPolicyManagerGeneric::StreamDescriptor::dump(char* buffer, size_t size)
-{
- snprintf(buffer, size, " %02d %02d %02d %02d %d\n",
- mIndexMin,
- mIndexMax,
- mIndexCur,
- mMuteCount,
- mCanBeMuted);
-}
-
-}; // namespace android
diff --git a/libs/audioflinger/AudioPolicyManagerGeneric.h b/libs/audioflinger/AudioPolicyManagerGeneric.h
deleted file mode 100644
index 4997cdf..0000000
--- a/libs/audioflinger/AudioPolicyManagerGeneric.h
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <hardware_legacy/AudioPolicyInterface.h>
-#include <utils/threads.h>
-
-
-namespace android {
-
-// ----------------------------------------------------------------------------
-
-#define MAX_DEVICE_ADDRESS_LEN 20
-#define NUM_TEST_OUTPUTS 5
-
-class AudioPolicyManagerGeneric: public AudioPolicyInterface
-#ifdef AUDIO_POLICY_TEST
- , public Thread
-#endif //AUDIO_POLICY_TEST
-{
-
-public:
- AudioPolicyManagerGeneric(AudioPolicyClientInterface *clientInterface);
- virtual ~AudioPolicyManagerGeneric();
-
- // AudioPolicyInterface
- virtual status_t setDeviceConnectionState(AudioSystem::audio_devices device,
- AudioSystem::device_connection_state state,
- const char *device_address);
- virtual AudioSystem::device_connection_state getDeviceConnectionState(AudioSystem::audio_devices device,
- const char *device_address);
- virtual void setPhoneState(int state);
- virtual void setRingerMode(uint32_t mode, uint32_t mask);
- virtual void setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config);
- virtual AudioSystem::forced_config getForceUse(AudioSystem::force_use usage);
- virtual void setSystemProperty(const char* property, const char* value);
- virtual audio_io_handle_t getOutput(AudioSystem::stream_type stream,
- uint32_t samplingRate,
- uint32_t format,
- uint32_t channels,
- AudioSystem::output_flags flags);
- virtual status_t startOutput(audio_io_handle_t output, AudioSystem::stream_type stream);
- virtual status_t stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream);
- virtual void releaseOutput(audio_io_handle_t output);
- virtual audio_io_handle_t getInput(int inputSource,
- uint32_t samplingRate,
- uint32_t format,
- uint32_t channels,
- AudioSystem::audio_in_acoustics acoustics);
- // indicates to the audio policy manager that the input starts being used.
- virtual status_t startInput(audio_io_handle_t input);
- // indicates to the audio policy manager that the input stops being used.
- virtual status_t stopInput(audio_io_handle_t input);
- virtual void releaseInput(audio_io_handle_t input);
- virtual void initStreamVolume(AudioSystem::stream_type stream,
- int indexMin,
- int indexMax);
- virtual status_t setStreamVolumeIndex(AudioSystem::stream_type stream, int index);
- virtual status_t getStreamVolumeIndex(AudioSystem::stream_type stream, int *index);
-
- virtual status_t dump(int fd);
-
-private:
-
- enum routing_strategy {
- STRATEGY_MEDIA,
- STRATEGY_PHONE,
- STRATEGY_SONIFICATION,
- STRATEGY_DTMF,
- NUM_STRATEGIES
- };
-
- // descriptor for audio outputs. Used to maintain current configuration of each opened audio output
- // and keep track of the usage of this output by each audio stream type.
- class AudioOutputDescriptor
- {
- public:
- AudioOutputDescriptor();
-
- status_t dump(int fd);
-
- uint32_t device();
- void changeRefCount(AudioSystem::stream_type, int delta);
- bool isUsedByStream(AudioSystem::stream_type stream) { return mRefCount[stream] > 0 ? true : false; }
- uint32_t refCount();
-
- uint32_t mSamplingRate; //
- uint32_t mFormat; //
- uint32_t mChannels; // output configuration
- uint32_t mLatency; //
- AudioSystem::output_flags mFlags; //
- uint32_t mDevice; // current device this output is routed to
- uint32_t mRefCount[AudioSystem::NUM_STREAM_TYPES]; // number of streams of each type using this output
- };
-
- // descriptor for audio inputs. Used to maintain current configuration of each opened audio input
- // and keep track of the usage of this input.
- class AudioInputDescriptor
- {
- public:
- AudioInputDescriptor();
-
- status_t dump(int fd);
-
- uint32_t mSamplingRate; //
- uint32_t mFormat; // input configuration
- uint32_t mChannels; //
- AudioSystem::audio_in_acoustics mAcoustics; //
- uint32_t mDevice; // current device this input is routed to
- uint32_t mRefCount; // number of AudioRecord clients using this output
- };
-
- // stream descriptor used for volume control
- class StreamDescriptor
- {
- public:
- StreamDescriptor()
- : mIndexMin(0), mIndexMax(1), mIndexCur(1), mMuteCount(0), mCanBeMuted(true) {}
-
- void dump(char* buffer, size_t size);
-
- int mIndexMin; // min volume index
- int mIndexMax; // max volume index
- int mIndexCur; // current volume index
- int mMuteCount; // mute request counter
- bool mCanBeMuted; // true is the stream can be muted
- };
-
- // return the strategy corresponding to a given stream type
- static routing_strategy getStrategy(AudioSystem::stream_type stream);
- // return the output handle of an output routed to the specified device, 0 if no output
- // is routed to the device
- float computeVolume(int stream, int index, uint32_t device);
- // Mute or unmute the stream on the specified output
- void setStreamMute(int stream, bool on, audio_io_handle_t output);
- // handle special cases for sonification strategy while in call: mute streams or replace by
- // a special tone in the device used for communication
- void handleIncallSonification(int stream, bool starting);
-
-
-#ifdef AUDIO_POLICY_TEST
- virtual bool threadLoop();
- void exit();
- int testOutputIndex(audio_io_handle_t output);
-#endif //AUDIO_POLICY_TEST
-
-
- AudioPolicyClientInterface *mpClientInterface; // audio policy client interface
- audio_io_handle_t mHardwareOutput; // hardware output handler
-
- KeyedVector<audio_io_handle_t, AudioOutputDescriptor *> mOutputs; // list ot output descritors
- KeyedVector<audio_io_handle_t, AudioInputDescriptor *> mInputs; // list of input descriptors
- uint32_t mAvailableOutputDevices; // bit field of all available output devices
- uint32_t mAvailableInputDevices; // bit field of all available input devices
- int mPhoneState; // current phone state
- uint32_t mRingerMode; // current ringer mode
- AudioSystem::forced_config mForceUse[AudioSystem::NUM_FORCE_USE]; // current forced use configuration
-
- StreamDescriptor mStreams[AudioSystem::NUM_STREAM_TYPES]; // stream descriptors for volume control
-
-#ifdef AUDIO_POLICY_TEST
- Mutex mLock;
- Condition mWaitWorkCV;
-
- int mCurOutput;
- bool mDirectOutput;
- audio_io_handle_t mTestOutputs[NUM_TEST_OUTPUTS];
- int mTestInput;
- uint32_t mTestDevice;
- uint32_t mTestSamplingRate;
- uint32_t mTestFormat;
- uint32_t mTestChannels;
- uint32_t mTestLatencyMs;
-#endif //AUDIO_POLICY_TEST
-
-};
-
-};
diff --git a/libs/audioflinger/AudioPolicyService.cpp b/libs/audioflinger/AudioPolicyService.cpp
index aa48019..bb3905c 100644
--- a/libs/audioflinger/AudioPolicyService.cpp
+++ b/libs/audioflinger/AudioPolicyService.cpp
@@ -30,9 +30,10 @@
#include <utils/String16.h>
#include <utils/threads.h>
#include "AudioPolicyService.h"
-#include "AudioPolicyManagerGeneric.h"
+#include <hardware_legacy/AudioPolicyManagerBase.h>
#include <cutils/properties.h>
#include <dlfcn.h>
+#include <hardware_legacy/power.h>
// ----------------------------------------------------------------------------
// the sim build doesn't have gettid
@@ -43,8 +44,9 @@
namespace android {
-static const char* kDeadlockedString = "AudioPolicyService may be deadlocked\n";
-static const char* kCmdDeadlockedString = "AudioPolicyService command thread may be deadlocked\n";
+
+static const char *kDeadlockedString = "AudioPolicyService may be deadlocked\n";
+static const char *kCmdDeadlockedString = "AudioPolicyService command thread may be deadlocked\n";
static const int kDumpLockRetries = 50;
static const int kDumpLockSleep = 20000;
@@ -67,18 +69,18 @@
char value[PROPERTY_VALUE_MAX];
// start tone playback thread
- mTonePlaybackThread = new AudioCommandThread();
+ mTonePlaybackThread = new AudioCommandThread(String8(""));
// start audio commands thread
- mAudioCommandThread = new AudioCommandThread();
+ mAudioCommandThread = new AudioCommandThread(String8("ApmCommandThread"));
#if (defined GENERIC_AUDIO) || (defined AUDIO_POLICY_TEST)
- mpPolicyManager = new AudioPolicyManagerGeneric(this);
+ mpPolicyManager = new AudioPolicyManagerBase(this);
LOGV("build for GENERIC_AUDIO - using generic audio policy");
#else
// if running in emulation - use the emulator driver
if (property_get("ro.kernel.qemu", value, 0)) {
LOGV("Running in emulation - using generic audio policy");
- mpPolicyManager = new AudioPolicyManagerGeneric(this);
+ mpPolicyManager = new AudioPolicyManagerBase(this);
}
else {
LOGV("Using hardware specific audio policy");
@@ -556,8 +558,8 @@
// ----------- AudioPolicyService::AudioCommandThread implementation ----------
-AudioPolicyService::AudioCommandThread::AudioCommandThread()
- : Thread(false)
+AudioPolicyService::AudioCommandThread::AudioCommandThread(String8 name)
+ : Thread(false), mName(name)
{
mpToneGenerator = NULL;
}
@@ -565,18 +567,20 @@
AudioPolicyService::AudioCommandThread::~AudioCommandThread()
{
+ if (mName != "" && !mAudioCommands.isEmpty()) {
+ release_wake_lock(mName.string());
+ }
mAudioCommands.clear();
if (mpToneGenerator != NULL) delete mpToneGenerator;
}
void AudioPolicyService::AudioCommandThread::onFirstRef()
{
- const size_t SIZE = 256;
- char buffer[SIZE];
-
- snprintf(buffer, SIZE, "AudioCommandThread");
-
- run(buffer, ANDROID_PRIORITY_AUDIO);
+ if (mName != "") {
+ run(mName.string(), ANDROID_PRIORITY_AUDIO);
+ } else {
+ run("AudioCommandThread", ANDROID_PRIORITY_AUDIO);
+ }
}
bool AudioPolicyService::AudioCommandThread::threadLoop()
@@ -657,6 +661,10 @@
break;
}
}
+ // release delayed commands wake lock
+ if (mName != "" && mAudioCommands.isEmpty()) {
+ release_wake_lock(mName.string());
+ }
LOGV("AudioCommandThread() going to sleep");
mWaitWorkCV.waitRelative(mLock, waitTime);
LOGV("AudioCommandThread() waking up");
@@ -815,6 +823,11 @@
command->mTime = systemTime() + milliseconds(delayMs);
+ // acquire wake lock to make sure delayed commands are processed
+ if (mName != "" && mAudioCommands.isEmpty()) {
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, mName.string());
+ }
+
// check same pending commands with later time stamps and eliminate them
for (i = mAudioCommands.size()-1; i >= 0; i--) {
AudioCommand *command2 = mAudioCommands[i];
@@ -883,7 +896,7 @@
removedCommands.clear();
// insert command at the right place according to its time stamp
- LOGV("inserting command: %d at index %ld, num commands %d", command->mCommand, i+1, mAudioCommands.size());
+ LOGV("inserting command: %d at index %d, num commands %d", command->mCommand, (int)i+1, mAudioCommands.size());
mAudioCommands.insertAt(command, i + 1);
}
diff --git a/libs/audioflinger/AudioPolicyService.h b/libs/audioflinger/AudioPolicyService.h
index b9234ec..a13d0bd 100644
--- a/libs/audioflinger/AudioPolicyService.h
+++ b/libs/audioflinger/AudioPolicyService.h
@@ -132,7 +132,7 @@
SET_VOICE_VOLUME
};
- AudioCommandThread ();
+ AudioCommandThread (String8 name);
virtual ~AudioCommandThread();
status_t dump(int fd);
@@ -195,7 +195,8 @@
Condition mWaitWorkCV;
Vector <AudioCommand *> mAudioCommands; // list of pending commands
ToneGenerator *mpToneGenerator; // the tone generator
- AudioCommand mLastCommand;
+ AudioCommand mLastCommand; // last processed command (used by dump)
+ String8 mName; // string used by wake lock fo delayed commands
};
// Internal dump utilities.
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 3080ab0..88521f6 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -110,9 +110,5 @@
include $(BUILD_SHARED_LIBRARY)
-# Include the subdirectories ====================
-include $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk,\
- java \
- ))
endif #simulator
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index b4ec1a2..1ae2317 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -366,6 +366,25 @@
}
}
+static void mip8(const Adapter2D &out, const Adapter2D &in)
+{
+ uint32_t w = out.getDimX();
+ uint32_t h = out.getDimY();
+
+ for (uint32_t y=0; y < h; y++) {
+ uint8_t *oPtr = static_cast<uint8_t *>(out.getElement(0, y));
+ const uint8_t *i1 = static_cast<uint8_t *>(in.getElement(0, y*2));
+ const uint8_t *i2 = static_cast<uint8_t *>(in.getElement(0, y*2+1));
+
+ for (uint32_t x=0; x < w; x++) {
+ *oPtr = (uint8_t)(((uint32_t)i1[0] + i1[1] + i2[0] + i2[1]) * 0.25f);
+ oPtr ++;
+ i1 += 2;
+ i2 += 2;
+ }
+ }
+}
+
static void mip(const Adapter2D &out, const Adapter2D &in)
{
switch(out.getBaseType()->getElement()->getSizeBits()) {
@@ -375,6 +394,9 @@
case 16:
mip565(out, in);
break;
+ case 8:
+ mip8(out, in);
+ break;
}
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index 3ba9cee..eeb9468 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -778,6 +778,17 @@
rsc->setVertex((ProgramVertex *)tmp.get());
}
+static void SC_drawSpriteScreenspaceCropped(float x, float y, float z, float w, float h,
+ float cx0, float cy0, float cx1, float cy1)
+{
+ GET_TLS();
+ rsc->setupCheck();
+
+ GLint crop[4] = {cx0, cy0, cx1, cy1};
+ glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
+ glDrawTexfOES(x, y, z, w, h);
+}
+
static void SC_drawSprite(float x, float y, float z, float w, float h)
{
GET_TLS();
@@ -1289,6 +1300,8 @@
"void", "(float x, float y, float z, float w, float h)" },
{ "drawSpriteScreenspace", (void *)&SC_drawSpriteScreenspace,
"void", "(float x, float y, float z, float w, float h)" },
+ { "drawSpriteScreenspaceCropped", (void *)&SC_drawSpriteScreenspaceCropped,
+ "void", "(float x, float y, float z, float w, float h, float cx0, float cy0, float cx1, float cy1)" },
{ "drawLine", (void *)&SC_drawLine,
"void", "(float x1, float y1, float z1, float x2, float y2, float z2)" },
{ "drawPoint", (void *)&SC_drawPoint,
diff --git a/libs/surfaceflinger/LayerBase.cpp b/libs/surfaceflinger/LayerBase.cpp
index 8003d22..4d7bef8 100644
--- a/libs/surfaceflinger/LayerBase.cpp
+++ b/libs/surfaceflinger/LayerBase.cpp
@@ -444,12 +444,21 @@
glLoadIdentity();
// the texture's source is rotated
- if (texture.transform == HAL_TRANSFORM_ROT_90) {
- // TODO: handle the other orientations
- glTranslatef(0, 1, 0);
- glRotatef(-90, 0, 0, 1);
+ switch (texture.transform) {
+ case HAL_TRANSFORM_ROT_90:
+ glTranslatef(0, 1, 0);
+ glRotatef(-90, 0, 0, 1);
+ break;
+ case HAL_TRANSFORM_ROT_180:
+ glTranslatef(1, 1, 0);
+ glRotatef(-180, 0, 0, 1);
+ break;
+ case HAL_TRANSFORM_ROT_270:
+ glTranslatef(1, 0, 0);
+ glRotatef(-270, 0, 0, 1);
+ break;
}
-
+
if (texture.NPOTAdjust) {
glScalef(texture.wScale, texture.hScale, 1.0f);
}
@@ -809,7 +818,7 @@
}
sp<OverlayRef> LayerBaseClient::Surface::createOverlay(
- uint32_t w, uint32_t h, int32_t format)
+ uint32_t w, uint32_t h, int32_t format, int32_t orientation)
{
return NULL;
};
diff --git a/libs/surfaceflinger/LayerBase.h b/libs/surfaceflinger/LayerBase.h
index ed07b3f..f73ea0c 100644
--- a/libs/surfaceflinger/LayerBase.h
+++ b/libs/surfaceflinger/LayerBase.h
@@ -351,7 +351,7 @@
virtual void postBuffer(ssize_t offset);
virtual void unregisterBuffers();
virtual sp<OverlayRef> createOverlay(uint32_t w, uint32_t h,
- int32_t format);
+ int32_t format, int32_t orientation);
protected:
friend class LayerBaseClient;
diff --git a/libs/surfaceflinger/LayerBuffer.cpp b/libs/surfaceflinger/LayerBuffer.cpp
index 2ff6167..2735aa2 100644
--- a/libs/surfaceflinger/LayerBuffer.cpp
+++ b/libs/surfaceflinger/LayerBuffer.cpp
@@ -182,14 +182,15 @@
/**
* This creates an "overlay" source for this surface
*/
-sp<OverlayRef> LayerBuffer::createOverlay(uint32_t w, uint32_t h, int32_t f)
+sp<OverlayRef> LayerBuffer::createOverlay(uint32_t w, uint32_t h, int32_t f,
+ int32_t orientation)
{
sp<OverlayRef> result;
Mutex::Autolock _l(mLock);
if (mSource != 0)
return result;
- sp<OverlaySource> source = new OverlaySource(*this, &result, w, h, f);
+ sp<OverlaySource> source = new OverlaySource(*this, &result, w, h, f, orientation);
if (result != 0) {
mSource = source;
}
@@ -248,11 +249,11 @@
}
sp<OverlayRef> LayerBuffer::SurfaceLayerBuffer::createOverlay(
- uint32_t w, uint32_t h, int32_t format) {
+ uint32_t w, uint32_t h, int32_t format, int32_t orientation) {
sp<OverlayRef> result;
sp<LayerBuffer> owner(getOwner());
if (owner != 0)
- result = owner->createOverlay(w, h, format);
+ result = owner->createOverlay(w, h, format, orientation);
return result;
}
@@ -261,7 +262,7 @@
// ============================================================================
LayerBuffer::Buffer::Buffer(const ISurface::BufferHeap& buffers, ssize_t offset)
- : mBufferHeap(buffers)
+ : mBufferHeap(buffers), mSupportsCopybit(false)
{
NativeBuffer& src(mNativeBuffer);
src.crop.l = 0;
@@ -283,10 +284,8 @@
offset, buffers.heap->base(),
&src.img.handle);
- LOGE_IF(err, "CREATE_HANDLE_FROM_BUFFER (heapId=%d, size=%d, "
- "offset=%ld, base=%p) failed (%s)",
- buffers.heap->heapID(), buffers.heap->getSize(),
- offset, buffers.heap->base(), strerror(-err));
+ // we can fail here is the passed buffer is purely software
+ mSupportsCopybit = (err == NO_ERROR);
}
}
@@ -372,8 +371,23 @@
LayerBuffer::BufferSource::~BufferSource()
{
+ class MessageDestroyTexture : public MessageBase {
+ SurfaceFlinger* flinger;
+ GLuint name;
+ public:
+ MessageDestroyTexture(
+ SurfaceFlinger* flinger, GLuint name)
+ : flinger(flinger), name(name) { }
+ virtual bool handler() {
+ glDeleteTextures(1, &name);
+ return true;
+ }
+ };
+
if (mTexture.name != -1U) {
- glDeleteTextures(1, &mTexture.name);
+ // GL textures can only be destroyed from the GL thread
+ mLayer.mFlinger->mEventQueue.postMessage(
+ new MessageDestroyTexture(mLayer.mFlinger.get(), mTexture.name) );
}
if (mTexture.image != EGL_NO_IMAGE_KHR) {
EGLDisplay dpy(mLayer.mFlinger->graphicPlane(0).getEGLDisplay());
@@ -453,7 +467,7 @@
#if defined(EGL_ANDROID_image_native_buffer)
if (mLayer.mFlags & DisplayHardware::DIRECT_TEXTURE) {
copybit_device_t* copybit = mLayer.mBlitEngine;
- if (copybit) {
+ if (copybit && ourBuffer->supportsCopybit()) {
// create our EGLImageKHR the first time
err = initTempBuffer();
if (err == NO_ERROR) {
@@ -465,7 +479,9 @@
copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE);
err = copybit->stretch(copybit, &dst.img, &src.img,
&dst.crop, &src.crop, &clip);
-
+ if (err != NO_ERROR) {
+ clearTempBufferImage();
+ }
}
} else {
err = INVALID_OPERATION;
@@ -504,16 +520,21 @@
int t = w; w = h; h = t;
}
+ // we're in the copybit case, so make sure we can handle this blit
+ // we don't have to keep the aspect ratio here
+ copybit_device_t* copybit = mLayer.mBlitEngine;
+ const int down = copybit->get(copybit, COPYBIT_MINIFICATION_LIMIT);
+ const int up = copybit->get(copybit, COPYBIT_MAGNIFICATION_LIMIT);
+ if (buffers.w > w*down) w = buffers.w / down;
+ else if (w > buffers.w*up) w = buffers.w*up;
+ if (buffers.h > h*down) h = buffers.h / down;
+ else if (h > buffers.h*up) h = buffers.h*up;
+
if (mTexture.image != EGL_NO_IMAGE_KHR) {
// we have an EGLImage, make sure the needed size didn't change
if (w!=mTexture.width || h!= mTexture.height) {
// delete the EGLImage and texture
- EGLDisplay dpy(mLayer.mFlinger->graphicPlane(0).getEGLDisplay());
- glDeleteTextures(1, &mTexture.name);
- eglDestroyImageKHR(dpy, mTexture.image);
- Texture defaultTexture;
- mTexture = defaultTexture;
- mTempGraphicBuffer.clear();
+ clearTempBufferImage();
} else {
// we're good, we have an EGLImageKHR and it's (still) the
// right size
@@ -560,13 +581,29 @@
return err;
}
+void LayerBuffer::BufferSource::clearTempBufferImage() const
+{
+ // delete the image
+ EGLDisplay dpy(mLayer.mFlinger->graphicPlane(0).getEGLDisplay());
+ eglDestroyImageKHR(dpy, mTexture.image);
+
+ // and the associated texture (recreate a name)
+ glDeleteTextures(1, &mTexture.name);
+ Texture defaultTexture;
+ mTexture = defaultTexture;
+ mTexture.name = mLayer.createTexture();
+
+ // and the associated buffer
+ mTempGraphicBuffer.clear();
+}
+
// ---------------------------------------------------------------------------
LayerBuffer::OverlaySource::OverlaySource(LayerBuffer& layer,
sp<OverlayRef>* overlayRef,
- uint32_t w, uint32_t h, int32_t format)
+ uint32_t w, uint32_t h, int32_t format, int32_t orientation)
: Source(layer), mVisibilityChanged(false),
- mOverlay(0), mOverlayHandle(0), mOverlayDevice(0)
+ mOverlay(0), mOverlayHandle(0), mOverlayDevice(0), mOrientation(orientation)
{
overlay_control_device_t* overlay_dev = mLayer.mFlinger->getOverlayEngine();
if (overlay_dev == NULL) {
@@ -648,8 +685,12 @@
if (mOverlay) {
overlay_control_device_t* overlay_dev = mOverlayDevice;
overlay_dev->setPosition(overlay_dev, mOverlay, x,y,w,h);
+ // we need to combine the layer orientation and the
+ // user-requested orientation.
+ Transform finalTransform = Transform(mOrientation) *
+ Transform(mLayer.getOrientation());
overlay_dev->setParameter(overlay_dev, mOverlay,
- OVERLAY_TRANSFORM, mLayer.getOrientation());
+ OVERLAY_TRANSFORM, finalTransform.getOrientation());
overlay_dev->commit(overlay_dev, mOverlay);
}
}
diff --git a/libs/surfaceflinger/LayerBuffer.h b/libs/surfaceflinger/LayerBuffer.h
index 2ca63ac..e03f92c 100644
--- a/libs/surfaceflinger/LayerBuffer.h
+++ b/libs/surfaceflinger/LayerBuffer.h
@@ -74,7 +74,8 @@
status_t registerBuffers(const ISurface::BufferHeap& buffers);
void postBuffer(ssize_t offset);
void unregisterBuffers();
- sp<OverlayRef> createOverlay(uint32_t w, uint32_t h, int32_t format);
+ sp<OverlayRef> createOverlay(uint32_t w, uint32_t h, int32_t format,
+ int32_t orientation);
sp<Source> getSource() const;
sp<Source> clearSource();
@@ -99,6 +100,9 @@
class Buffer : public LightRefBase<Buffer> {
public:
Buffer(const ISurface::BufferHeap& buffers, ssize_t offset);
+ inline bool supportsCopybit() const {
+ return mSupportsCopybit;
+ }
inline status_t getStatus() const {
return mBufferHeap.heap!=0 ? NO_ERROR : NO_INIT;
}
@@ -113,6 +117,7 @@
private:
ISurface::BufferHeap mBufferHeap;
NativeBuffer mNativeBuffer;
+ bool mSupportsCopybit;
};
class BufferSource : public Source {
@@ -131,6 +136,7 @@
virtual void destroy() { }
private:
status_t initTempBuffer() const;
+ void clearTempBufferImage() const;
mutable Mutex mBufferSourceLock;
sp<Buffer> mBuffer;
status_t mStatus;
@@ -145,7 +151,7 @@
public:
OverlaySource(LayerBuffer& layer,
sp<OverlayRef>* overlayRef,
- uint32_t w, uint32_t h, int32_t format);
+ uint32_t w, uint32_t h, int32_t format, int32_t orientation);
virtual ~OverlaySource();
virtual void onDraw(const Region& clip) const;
virtual void onTransaction(uint32_t flags);
@@ -178,6 +184,7 @@
int32_t mFormat;
int32_t mWidthStride;
int32_t mHeightStride;
+ int32_t mOrientation;
mutable Mutex mOverlaySourceLock;
bool mInitialized;
};
@@ -195,7 +202,7 @@
virtual void unregisterBuffers();
virtual sp<OverlayRef> createOverlay(
- uint32_t w, uint32_t h, int32_t format);
+ uint32_t w, uint32_t h, int32_t format, int32_t orientation);
private:
sp<LayerBuffer> getOwner() const {
return static_cast<LayerBuffer*>(Surface::getOwner().get());
diff --git a/libs/surfaceflinger/Transform.cpp b/libs/surfaceflinger/Transform.cpp
index 1501536..ab6f7ba 100644
--- a/libs/surfaceflinger/Transform.cpp
+++ b/libs/surfaceflinger/Transform.cpp
@@ -42,6 +42,17 @@
{
}
+Transform::Transform(int32_t flags) {
+ mTransform.reset();
+ int sx = (flags & FLIP_H) ? -1 : 1;
+ int sy = (flags & FLIP_V) ? -1 : 1;
+ if (flags & ROT_90) {
+ this->set(0, -sy, sx, 0);
+ } else {
+ this->set(sx, 0, 0, sy);
+ }
+}
+
Transform::~Transform() {
}
diff --git a/libs/surfaceflinger/Transform.h b/libs/surfaceflinger/Transform.h
index 78f5c19..ddab404 100644
--- a/libs/surfaceflinger/Transform.h
+++ b/libs/surfaceflinger/Transform.h
@@ -38,6 +38,7 @@
public:
Transform();
Transform(const Transform& other);
+ Transform(int32_t flags);
~Transform();
enum orientation_flags {
diff --git a/libs/ui/CameraParameters.cpp b/libs/ui/CameraParameters.cpp
index 8f1749d..2e0409b 100644
--- a/libs/ui/CameraParameters.cpp
+++ b/libs/ui/CameraParameters.cpp
@@ -36,7 +36,7 @@
const char CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS[] = "picture-format-values";
const char CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH[] = "jpeg-thumbnail-width";
const char CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT[] = "jpeg-thumbnail-height";
-const char CameraParameters::KEY_SUPPORTED_THUMBNAIL_SIZES[] = "jpeg-thumbnail-size-values";
+const char CameraParameters::KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES[] = "jpeg-thumbnail-size-values";
const char CameraParameters::KEY_JPEG_THUMBNAIL_QUALITY[] = "jpeg-thumbnail-quality";
const char CameraParameters::KEY_JPEG_QUALITY[] = "jpeg-quality";
const char CameraParameters::KEY_ROTATION[] = "rotation";
diff --git a/libs/ui/ISurface.cpp b/libs/ui/ISurface.cpp
index 4fb38ed..6f3d762 100644
--- a/libs/ui/ISurface.cpp
+++ b/libs/ui/ISurface.cpp
@@ -115,13 +115,14 @@
}
virtual sp<OverlayRef> createOverlay(
- uint32_t w, uint32_t h, int32_t format)
+ uint32_t w, uint32_t h, int32_t format, int32_t orientation)
{
Parcel data, reply;
data.writeInterfaceToken(ISurface::getInterfaceDescriptor());
data.writeInt32(w);
data.writeInt32(h);
data.writeInt32(format);
+ data.writeInt32(orientation);
remote()->transact(CREATE_OVERLAY, data, &reply);
return OverlayRef::readFromParcel(reply);
}
@@ -173,7 +174,8 @@
int w = data.readInt32();
int h = data.readInt32();
int f = data.readInt32();
- sp<OverlayRef> o = createOverlay(w, h, f);
+ int orientation = data.readInt32();
+ sp<OverlayRef> o = createOverlay(w, h, f, orientation);
return OverlayRef::writeToParcel(reply, o);
} break;
default:
diff --git a/libs/ui/Surface.cpp b/libs/ui/Surface.cpp
index 24ae27f..c7be05b 100644
--- a/libs/ui/Surface.cpp
+++ b/libs/ui/Surface.cpp
@@ -607,13 +607,21 @@
status_t Surface::lock(SurfaceInfo* other, Region* dirtyIn, bool blocking)
{
if (mApiLock.tryLock() != NO_ERROR) {
- LOGE("calling Surface::lock() from different threads!");
+ LOGE("calling Surface::lock from different threads!");
CallStack stack;
stack.update();
stack.dump("Surface::lock called from different threads");
return WOULD_BLOCK;
}
+
+ /* Here we're holding mApiLock */
+ if (mLockedBuffer != 0) {
+ LOGE("Surface::lock failed, already locked");
+ mApiLock.unlock();
+ return INVALID_OPERATION;
+ }
+
// we're intending to do software rendering from this point
setUsage(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
@@ -682,8 +690,8 @@
status_t Surface::unlockAndPost()
{
if (mLockedBuffer == 0) {
- LOGE("unlockAndPost failed, no locked buffer");
- return BAD_VALUE;
+ LOGE("Surface::unlockAndPost failed, no locked buffer");
+ return INVALID_OPERATION;
}
status_t err = mLockedBuffer->unlock();
diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java
index ee56621..9d67882 100755
--- a/location/java/com/android/internal/location/GpsLocationProvider.java
+++ b/location/java/com/android/internal/location/GpsLocationProvider.java
@@ -181,6 +181,9 @@
// true if GPS is navigating
private boolean mNavigating;
+
+ // true if GPS engine is on
+ private boolean mEngineOn;
// requested frequency of fixes, in seconds
private int mFixInterval = 1;
@@ -555,13 +558,17 @@
mNetworkThread = null;
}
+ // do this before releasing wakelock
+ native_cleanup();
+
// The GpsEventThread does not wait for the GPS to shutdown
// so we need to report the GPS_STATUS_ENGINE_OFF event here
if (mNavigating) {
+ reportStatus(GPS_STATUS_SESSION_END);
+ }
+ if (mEngineOn) {
reportStatus(GPS_STATUS_ENGINE_OFF);
}
-
- native_cleanup();
}
public boolean isEnabled() {
@@ -873,9 +880,24 @@
synchronized(mListeners) {
boolean wasNavigating = mNavigating;
- mNavigating = (status == GPS_STATUS_SESSION_BEGIN);
- if (mNavigating && !mWakeLock.isHeld()) {
+ switch (status) {
+ case GPS_STATUS_SESSION_BEGIN:
+ mNavigating = true;
+ break;
+ case GPS_STATUS_SESSION_END:
+ mNavigating = false;
+ break;
+ case GPS_STATUS_ENGINE_ON:
+ mEngineOn = true;
+ break;
+ case GPS_STATUS_ENGINE_OFF:
+ mEngineOn = false;
+ break;
+ }
+
+ // beware, the events can come out of order
+ if ((mNavigating || mEngineOn) && !mWakeLock.isHeld()) {
if (DEBUG) Log.d(TAG, "Acquiring wakelock");
mWakeLock.acquire();
}
@@ -918,7 +940,8 @@
mContext.sendBroadcast(intent);
}
- if (status == GPS_STATUS_ENGINE_OFF && mWakeLock.isHeld()) {
+ // beware, the events can come out of order
+ if (!mNavigating && !mEngineOn && mWakeLock.isHeld()) {
if (DEBUG) Log.d(TAG, "Releasing wakelock");
mWakeLock.release();
}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 3a2f47a..fcb02f4 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -920,12 +920,17 @@
String path = c.getString(PATH_AUDIO_COLUMN_INDEX);
long lastModified = c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX);
- String key = path;
- if (mCaseInsensitivePaths) {
- key = path.toLowerCase();
+ // Only consider entries with absolute path names.
+ // This allows storing URIs in the database without the
+ // media scanner removing them.
+ if (path.startsWith("/")) {
+ String key = path;
+ if (mCaseInsensitivePaths) {
+ key = path.toLowerCase();
+ }
+ mFileCache.put(key, new FileCacheEntry(mAudioUri, rowId, path,
+ lastModified));
}
- mFileCache.put(key, new FileCacheEntry(mAudioUri, rowId, path,
- lastModified));
}
} finally {
c.close();
@@ -948,12 +953,17 @@
String path = c.getString(PATH_VIDEO_COLUMN_INDEX);
long lastModified = c.getLong(DATE_MODIFIED_VIDEO_COLUMN_INDEX);
- String key = path;
- if (mCaseInsensitivePaths) {
- key = path.toLowerCase();
+ // Only consider entries with absolute path names.
+ // This allows storing URIs in the database without the
+ // media scanner removing them.
+ if (path.startsWith("/")) {
+ String key = path;
+ if (mCaseInsensitivePaths) {
+ key = path.toLowerCase();
+ }
+ mFileCache.put(key, new FileCacheEntry(mVideoUri, rowId, path,
+ lastModified));
}
- mFileCache.put(key, new FileCacheEntry(mVideoUri, rowId, path,
- lastModified));
}
} finally {
c.close();
@@ -978,12 +988,17 @@
String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
long lastModified = c.getLong(DATE_MODIFIED_IMAGES_COLUMN_INDEX);
- String key = path;
- if (mCaseInsensitivePaths) {
- key = path.toLowerCase();
- }
- mFileCache.put(key, new FileCacheEntry(mImagesUri, rowId, path,
- lastModified));
+ // Only consider entries with absolute path names.
+ // This allows storing URIs in the database without the
+ // media scanner removing them.
+ if (path.startsWith("/")) {
+ String key = path;
+ if (mCaseInsensitivePaths) {
+ key = path.toLowerCase();
+ }
+ mFileCache.put(key, new FileCacheEntry(mImagesUri, rowId, path,
+ lastModified));
+ }
}
} finally {
c.close();
@@ -1003,7 +1018,7 @@
if (c != null) {
try {
while (c.moveToNext()) {
- String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
+ String path = c.getString(PATH_PLAYLISTS_COLUMN_INDEX);
if (path != null && path.length() > 0) {
long rowId = c.getLong(ID_PLAYLISTS_COLUMN_INDEX);
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index fc234ee..4ae4ec9b 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -24,7 +24,8 @@
IAudioPolicyService.cpp \
MediaScanner.cpp \
MediaScannerClient.cpp \
- autodetect.cpp
+ autodetect.cpp \
+ IMediaDeathNotifier.cpp
LOCAL_SHARED_LIBRARIES := \
libui libcutils libutils libbinder libsonivox libicuuc
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index 5352234..e1b1776 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -348,6 +348,9 @@
Mutex::Autolock _l(AudioSystem::gLock);
AudioSystem::gAudioFlinger.clear();
+ // clear output handles and stream to output map caches
+ AudioSystem::gStreamOutputMap.clear();
+ AudioSystem::gOutputs.clear();
if (gAudioErrorCallback) {
gAudioErrorCallback(DEAD_OBJECT);
diff --git a/media/libmedia/IMediaDeathNotifier.cpp b/media/libmedia/IMediaDeathNotifier.cpp
new file mode 100644
index 0000000..39ac076
--- /dev/null
+++ b/media/libmedia/IMediaDeathNotifier.cpp
@@ -0,0 +1,111 @@
+/*
+** Copyright 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.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "IMediaDeathNotifier"
+#include <utils/Log.h>
+
+#include <binder/IServiceManager.h>
+#include <binder/IPCThreadState.h>
+#include <media/IMediaDeathNotifier.h>
+
+namespace android {
+
+// client singleton for binder interface to services
+Mutex IMediaDeathNotifier::sServiceLock;
+sp<IMediaPlayerService> IMediaDeathNotifier::sMediaPlayerService;
+sp<IMediaDeathNotifier::DeathNotifier> IMediaDeathNotifier::sDeathNotifier;
+SortedVector< wp<IMediaDeathNotifier> > IMediaDeathNotifier::sObitRecipients;
+
+// establish binder interface to MediaPlayerService
+/*static*/const sp<IMediaPlayerService>&
+IMediaDeathNotifier::getMediaPlayerService()
+{
+ LOGV("getMediaPlayerService");
+ Mutex::Autolock _l(sServiceLock);
+ if (sMediaPlayerService.get() == 0) {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder;
+ do {
+ binder = sm->getService(String16("media.player"));
+ if (binder != 0) {
+ break;
+ }
+ LOGW("Media player service not published, waiting...");
+ usleep(500000); // 0.5 s
+ } while(true);
+
+ if (sDeathNotifier == NULL) {
+ sDeathNotifier = new DeathNotifier();
+ }
+ binder->linkToDeath(sDeathNotifier);
+ sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
+ }
+ LOGE_IF(sMediaPlayerService == 0, "no media player service!?");
+ return sMediaPlayerService;
+}
+
+/*static*/ void
+IMediaDeathNotifier::addObitRecipient(const wp<IMediaDeathNotifier>& recipient)
+{
+ LOGV("addObitRecipient");
+ Mutex::Autolock _l(sServiceLock);
+ sObitRecipients.add(recipient);
+}
+
+/*static*/ void
+IMediaDeathNotifier::removeObitRecipient(const wp<IMediaDeathNotifier>& recipient)
+{
+ LOGV("removeObitRecipient");
+ Mutex::Autolock _l(sServiceLock);
+ sObitRecipients.remove(recipient);
+}
+
+void
+IMediaDeathNotifier::DeathNotifier::binderDied(const wp<IBinder>& who) {
+ LOGW("media server died");
+
+ // Need to do this with the lock held
+ SortedVector< wp<IMediaDeathNotifier> > list;
+ {
+ Mutex::Autolock _l(sServiceLock);
+ sMediaPlayerService.clear();
+ list = sObitRecipients;
+ }
+
+ // Notify application when media server dies.
+ // Don't hold the static lock during callback in case app
+ // makes a call that needs the lock.
+ size_t count = list.size();
+ for (size_t iter = 0; iter < count; ++iter) {
+ sp<IMediaDeathNotifier> notifier = list[iter].promote();
+ if (notifier != 0) {
+ notifier->died();
+ }
+ }
+}
+
+IMediaDeathNotifier::DeathNotifier::~DeathNotifier()
+{
+ LOGV("DeathNotifier::~DeathNotifier");
+ Mutex::Autolock _l(sServiceLock);
+ sObitRecipients.clear();
+ if (sMediaPlayerService != 0) {
+ sMediaPlayerService->asBinder()->unlinkToDeath(this);
+ }
+}
+
+}; // namespace android
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp
index b43e48f..0469fd5 100644
--- a/media/libmedia/IOMX.cpp
+++ b/media/libmedia/IOMX.cpp
@@ -12,6 +12,7 @@
enum {
CONNECT = IBinder::FIRST_CALL_TRANSACTION,
+ LIVES_LOCALLY,
LIST_NODES,
ALLOCATE_NODE,
FREE_NODE,
@@ -75,6 +76,15 @@
: BpInterface<IOMX>(impl) {
}
+ virtual bool livesLocally(pid_t pid) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ data.writeInt32(pid);
+ remote()->transact(LIVES_LOCALLY, data, &reply);
+
+ return reply.readInt32() != 0;
+ }
+
virtual status_t listNodes(List<ComponentInfo> *list) {
list->clear();
@@ -230,7 +240,7 @@
virtual status_t allocateBuffer(
node_id node, OMX_U32 port_index, size_t size,
- buffer_id *buffer) {
+ buffer_id *buffer, void **buffer_data) {
Parcel data, reply;
data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
data.writeIntPtr((intptr_t)node);
@@ -245,7 +255,8 @@
return err;
}
- *buffer = (void*)reply.readIntPtr();
+ *buffer = (void *)reply.readIntPtr();
+ *buffer_data = (void *)reply.readIntPtr();
return err;
}
@@ -369,6 +380,14 @@
status_t BnOMX::onTransact(
uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
switch (code) {
+ case LIVES_LOCALLY:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+ reply->writeInt32(livesLocally((pid_t)data.readInt32()));
+
+ return OK;
+ }
+
case LIST_NODES:
{
CHECK_INTERFACE(IOMX, data, reply);
@@ -408,7 +427,7 @@
if (err == OK) {
reply->writeIntPtr((intptr_t)node);
}
-
+
return NO_ERROR;
}
@@ -419,7 +438,7 @@
node_id node = (void*)data.readIntPtr();
reply->writeInt32(freeNode(node));
-
+
return NO_ERROR;
}
@@ -551,11 +570,14 @@
size_t size = data.readInt32();
buffer_id buffer;
- status_t err = allocateBuffer(node, port_index, size, &buffer);
+ void *buffer_data;
+ status_t err = allocateBuffer(
+ node, port_index, size, &buffer, &buffer_data);
reply->writeInt32(err);
if (err == OK) {
reply->writeIntPtr((intptr_t)buffer);
+ reply->writeIntPtr((intptr_t)buffer_data);
}
return NO_ERROR;
@@ -631,7 +653,7 @@
node_id node = (void*)data.readIntPtr();
const char *parameter_name = data.readCString();
-
+
OMX_INDEXTYPE index;
status_t err = getExtensionIndex(node, parameter_name, &index);
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index 040366b..c0664f3 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -34,48 +34,6 @@
namespace android {
-// client singleton for binder interface to service
-Mutex MediaPlayer::sServiceLock;
-sp<IMediaPlayerService> MediaPlayer::sMediaPlayerService;
-sp<MediaPlayer::DeathNotifier> MediaPlayer::sDeathNotifier;
-SortedVector< wp<MediaPlayer> > MediaPlayer::sObitRecipients;
-
-// establish binder interface to service
-const sp<IMediaPlayerService>& MediaPlayer::getMediaPlayerService()
-{
- Mutex::Autolock _l(sServiceLock);
- if (sMediaPlayerService.get() == 0) {
- sp<IServiceManager> sm = defaultServiceManager();
- sp<IBinder> binder;
- do {
- binder = sm->getService(String16("media.player"));
- if (binder != 0)
- break;
- LOGW("MediaPlayerService not published, waiting...");
- usleep(500000); // 0.5 s
- } while(true);
- if (sDeathNotifier == NULL) {
- sDeathNotifier = new DeathNotifier();
- }
- binder->linkToDeath(sDeathNotifier);
- sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
- }
- LOGE_IF(sMediaPlayerService==0, "no MediaPlayerService!?");
- return sMediaPlayerService;
-}
-
-void MediaPlayer::addObitRecipient(const wp<MediaPlayer>& recipient)
-{
- Mutex::Autolock _l(sServiceLock);
- sObitRecipients.add(recipient);
-}
-
-void MediaPlayer::removeObitRecipient(const wp<MediaPlayer>& recipient)
-{
- Mutex::Autolock _l(sServiceLock);
- sObitRecipients.remove(recipient);
-}
-
MediaPlayer::MediaPlayer()
{
LOGV("constructor");
@@ -94,15 +52,9 @@
mLockThreadId = 0;
}
-void MediaPlayer::onFirstRef()
-{
- addObitRecipient(this);
-}
-
MediaPlayer::~MediaPlayer()
{
LOGV("destructor");
- removeObitRecipient(this);
disconnect();
IPCThreadState::self()->flushCommands();
}
@@ -630,45 +582,13 @@
}
}
-void MediaPlayer::DeathNotifier::binderDied(const wp<IBinder>& who) {
- LOGW("MediaPlayer server died!");
-
- // Need to do this with the lock held
- SortedVector< wp<MediaPlayer> > list;
- {
- Mutex::Autolock _l(MediaPlayer::sServiceLock);
- MediaPlayer::sMediaPlayerService.clear();
- list = sObitRecipients;
- }
-
- // Notify application when media server dies.
- // Don't hold the static lock during callback in case app
- // makes a call that needs the lock.
- size_t count = list.size();
- for (size_t iter = 0; iter < count; ++iter) {
- sp<MediaPlayer> player = list[iter].promote();
- if ((player != 0) && (player->mPlayer != 0)) {
- player->notify(MEDIA_ERROR, MEDIA_ERROR_SERVER_DIED, 0);
- }
- }
-}
-
-MediaPlayer::DeathNotifier::~DeathNotifier()
-{
- Mutex::Autolock _l(sServiceLock);
- sObitRecipients.clear();
- if (sMediaPlayerService != 0) {
- sMediaPlayerService->asBinder()->unlinkToDeath(this);
- }
-}
-
/*static*/ sp<IMemory> MediaPlayer::decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat)
{
LOGV("decode(%s)", url);
sp<IMemory> p;
const sp<IMediaPlayerService>& service = getMediaPlayerService();
if (service != 0) {
- p = sMediaPlayerService->decode(url, pSampleRate, pNumChannels, pFormat);
+ p = service->decode(url, pSampleRate, pNumChannels, pFormat);
} else {
LOGE("Unable to locate media service");
}
@@ -676,13 +596,19 @@
}
+void MediaPlayer::died()
+{
+ LOGV("died");
+ notify(MEDIA_ERROR, MEDIA_ERROR_SERVER_DIED, 0);
+}
+
/*static*/ sp<IMemory> MediaPlayer::decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat)
{
LOGV("decode(%d, %lld, %lld)", fd, offset, length);
sp<IMemory> p;
const sp<IMediaPlayerService>& service = getMediaPlayerService();
if (service != 0) {
- p = sMediaPlayerService->decode(fd, offset, length, pSampleRate, pNumChannels, pFormat);
+ p = service->decode(fd, offset, length, pSampleRate, pNumChannels, pFormat);
} else {
LOGE("Unable to locate media service");
}
diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp
index 6b63931..7b5dabb 100644
--- a/media/libmedia/mediarecorder.cpp
+++ b/media/libmedia/mediarecorder.cpp
@@ -24,6 +24,7 @@
#include <utils/String8.h>
#include <media/IMediaPlayerService.h>
#include <media/IMediaRecorder.h>
+#include <media/mediaplayer.h> // for MEDIA_ERROR_SERVER_DIED
namespace android {
@@ -576,19 +577,8 @@
MediaRecorder::MediaRecorder()
{
LOGV("constructor");
- sp<IServiceManager> sm = defaultServiceManager();
- sp<IBinder> binder;
- do {
- binder = sm->getService(String16("media.player"));
- if (binder != NULL) {
- break;
- }
- LOGW("MediaPlayerService not published, waiting...");
- usleep(500000); // 0.5 s
- } while(true);
-
- sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);
+ const sp<IMediaPlayerService>& service(getMediaPlayerService());
if (service != NULL) {
mMediaRecorder = service->createMediaRecorder(getpid());
}
@@ -637,5 +627,11 @@
}
}
+void MediaRecorder::died()
+{
+ LOGV("died");
+ notify(MEDIA_RECORDER_EVENT_ERROR, MEDIA_ERROR_SERVER_DIED, 0);
+}
+
}; // namespace android
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 6563caa..af8d1b5 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -199,6 +199,11 @@
{".ota", SONIVOX_PLAYER},
{".ogg", VORBIS_PLAYER},
{".oga", VORBIS_PLAYER},
+#ifndef NO_OPENCORE
+ {".wma", PV_PLAYER},
+ {".wmv", PV_PLAYER},
+ {".asf", PV_PLAYER},
+#endif
};
// TODO: Find real cause of Audio/Video delay in PV framework and remove this workaround
@@ -682,6 +687,14 @@
if (ident == 0x5367674f) // 'OggS'
return VORBIS_PLAYER;
+#ifndef NO_OPENCORE
+ if (ident == 0x75b22630) {
+ // The magic number for .asf files, i.e. wmv and wma content.
+ // These are not currently supported through stagefright.
+ return PV_PLAYER;
+ }
+#endif
+
// Some kind of MIDI?
EAS_DATA_HANDLE easdata;
if (EAS_Init(&easdata) == EAS_SUCCESS) {
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index e36e78c..26b9357 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -29,6 +29,7 @@
MPEG4Extractor.cpp \
MPEG4Writer.cpp \
MediaExtractor.cpp \
+ SampleIterator.cpp \
SampleTable.cpp \
ShoutcastSource.cpp \
StagefrightMediaScanner.cpp \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 6bcdfba..f6cd46a 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -139,7 +139,7 @@
mStreamDoneEventPending = false;
}
-void AwesomePlayer::setListener(const sp<MediaPlayerBase> &listener) {
+void AwesomePlayer::setListener(const wp<MediaPlayerBase> &listener) {
Mutex::Autolock autoLock(mLock);
mListener = listener;
}
@@ -218,6 +218,8 @@
void AwesomePlayer::reset_l() {
cancelPlayerEvents();
+ mVideoRenderer.clear();
+
if (mLastVideoBuffer) {
mLastVideoBuffer->release();
mLastVideoBuffer = NULL;
@@ -243,8 +245,6 @@
delete mAudioPlayer;
mAudioPlayer = NULL;
- mVideoRenderer.clear();
-
mDurationUs = -1;
mFlags = 0;
mVideoWidth = mVideoHeight = -1;
@@ -268,10 +268,7 @@
case AudioPlayer::SEEK_COMPLETE:
{
- if (me->mListener != NULL) {
- me->mListener->sendEvent(MEDIA_SEEK_COMPLETE);
- }
-
+ me->notifyListener_l(MEDIA_SEEK_COMPLETE);
break;
}
@@ -281,6 +278,16 @@
}
}
+void AwesomePlayer::notifyListener_l(int msg) {
+ if (mListener != NULL) {
+ sp<MediaPlayerBase> listener = mListener.promote();
+
+ if (listener != NULL) {
+ listener->sendEvent(msg);
+ }
+ }
+}
+
void AwesomePlayer::onStreamDone() {
// Posted whenever any stream finishes playing.
@@ -294,9 +301,7 @@
postVideoEvent_l();
}
} else {
- if (mListener != NULL) {
- mListener->sendEvent(MEDIA_PLAYBACK_COMPLETE);
- }
+ notifyListener_l(MEDIA_PLAYBACK_COMPLETE);
pause_l();
}
@@ -643,9 +648,7 @@
} else {
// If we're playing video only, report seek complete now,
// otherwise audio player will notify us later.
- if (mListener != NULL) {
- mListener->sendEvent(MEDIA_SEEK_COMPLETE);
- }
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
}
mFlags |= FIRST_FRAME;
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index bd862e0..8cd572e 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -56,7 +56,7 @@
virtual void unregisterBuffers() {}
virtual sp<OverlayRef> createOverlay(
- uint32_t w, uint32_t h, int32_t format) {
+ uint32_t w, uint32_t h, int32_t format, int32_t orientation) {
return NULL;
}
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 07a5a82..ed12b6d 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -154,7 +154,8 @@
mHaveMetadata(false),
mHasVideo(false),
mFirstTrack(NULL),
- mLastTrack(NULL) {
+ mLastTrack(NULL),
+ mFileMetaData(new MetaData) {
}
MPEG4Extractor::~MPEG4Extractor() {
@@ -169,20 +170,12 @@
}
sp<MetaData> MPEG4Extractor::getMetaData() {
- sp<MetaData> meta = new MetaData;
-
status_t err;
if ((err = readMetaData()) != OK) {
- return meta;
+ return new MetaData;
}
- if (mHasVideo) {
- meta->setCString(kKeyMIMEType, "video/mp4");
- } else {
- meta->setCString(kKeyMIMEType, "audio/mp4");
- }
-
- return meta;
+ return mFileMetaData;
}
size_t MPEG4Extractor::countTracks() {
@@ -232,8 +225,9 @@
uint32_t sampleIndex;
uint32_t sampleTime;
if (track->sampleTable->findThumbnailSample(&sampleIndex) == OK
- && track->sampleTable->getDecodingTime(
- sampleIndex, &sampleTime) == OK) {
+ && track->sampleTable->getMetaDataForSample(
+ sampleIndex, NULL /* offset */, NULL /* size */,
+ &sampleTime) == OK) {
track->meta->setInt64(
kKeyThumbnailTime,
((int64_t)sampleTime * 1000000) / track->timescale);
@@ -255,6 +249,12 @@
}
if (mHaveMetadata) {
+ if (mHasVideo) {
+ mFileMetaData->setCString(kKeyMIMEType, "video/mp4");
+ } else {
+ mFileMetaData->setCString(kKeyMIMEType, "audio/mp4");
+ }
+
return OK;
}
@@ -269,6 +269,41 @@
s[4] = '\0';
}
+struct PathAdder {
+ PathAdder(Vector<uint32_t> *path, uint32_t chunkType)
+ : mPath(path) {
+ mPath->push(chunkType);
+ }
+
+ ~PathAdder() {
+ mPath->pop();
+ }
+
+private:
+ Vector<uint32_t> *mPath;
+
+ PathAdder(const PathAdder &);
+ PathAdder &operator=(const PathAdder &);
+};
+
+static bool underMetaDataPath(const Vector<uint32_t> &path) {
+ return path.size() >= 5
+ && path[0] == FOURCC('m', 'o', 'o', 'v')
+ && path[1] == FOURCC('u', 'd', 't', 'a')
+ && path[2] == FOURCC('m', 'e', 't', 'a')
+ && path[3] == FOURCC('i', 'l', 's', 't');
+}
+
+// Given a time in seconds since Jan 1 1904, produce a human-readable string.
+static void convertTimeToDate(int64_t time_1904, String8 *s) {
+ time_t time_1970 = time_1904 - (((66 * 365 + 17) * 24) * 3600);
+
+ char tmp[32];
+ strftime(tmp, sizeof(tmp), "%Y%m%dT%H%M%S.000Z", gmtime(&time_1970));
+
+ s->setTo(tmp);
+}
+
status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) {
uint32_t hdr[2];
if (mDataSource->readAt(*offset, hdr, 8) < 8) {
@@ -296,7 +331,8 @@
char buffer[256];
if (chunk_size <= sizeof(buffer)) {
- if (mDataSource->readAt(*offset, buffer, chunk_size) < chunk_size) {
+ if (mDataSource->readAt(*offset, buffer, chunk_size)
+ < (ssize_t)chunk_size) {
return ERROR_IO;
}
@@ -304,8 +340,25 @@
}
#endif
+ PathAdder autoAdder(&mPath, chunk_type);
+
off_t chunk_data_size = *offset + chunk_size - data_offset;
+ if (chunk_type != FOURCC('c', 'p', 'r', 't')
+ && mPath.size() == 5 && underMetaDataPath(mPath)) {
+ off_t stop_offset = *offset + chunk_size;
+ *offset = data_offset;
+ while (*offset < stop_offset) {
+ status_t err = parseChunk(offset, depth + 1);
+ if (err != OK) {
+ return err;
+ }
+ }
+ CHECK_EQ(*offset, stop_offset);
+
+ return OK;
+ }
+
switch(chunk_type) {
case FOURCC('m', 'o', 'o', 'v'):
case FOURCC('t', 'r', 'a', 'k'):
@@ -318,6 +371,8 @@
case FOURCC('t', 'r', 'a', 'f'):
case FOURCC('m', 'f', 'r', 'a'):
case FOURCC('s', 'k', 'i' ,'p'):
+ case FOURCC('u', 'd', 't', 'a'):
+ case FOURCC('i', 'l', 's', 't'):
{
off_t stop_offset = *offset + chunk_size;
*offset = data_offset;
@@ -747,6 +802,76 @@
break;
}
+ case FOURCC('m', 'e', 't', 'a'):
+ {
+ uint8_t buffer[4];
+ CHECK(chunk_data_size >= (off_t)sizeof(buffer));
+ if (mDataSource->readAt(
+ data_offset, buffer, 4) < 4) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(buffer) != 0) {
+ // Should be version 0, flags 0.
+ return ERROR_MALFORMED;
+ }
+
+ off_t stop_offset = *offset + chunk_size;
+ *offset = data_offset + sizeof(buffer);
+ while (*offset < stop_offset) {
+ status_t err = parseChunk(offset, depth + 1);
+ if (err != OK) {
+ return err;
+ }
+ }
+ CHECK_EQ(*offset, stop_offset);
+ break;
+ }
+
+ case FOURCC('d', 'a', 't', 'a'):
+ {
+ if (mPath.size() == 6 && underMetaDataPath(mPath)) {
+ status_t err = parseMetaData(data_offset, chunk_data_size);
+
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('m', 'v', 'h', 'd'):
+ {
+ if (chunk_data_size < 12) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[12];
+ if (mDataSource->readAt(
+ data_offset, header, sizeof(header))
+ < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ int64_t creationTime;
+ if (header[0] == 1) {
+ creationTime = U64_AT(&header[4]);
+ } else {
+ CHECK_EQ(header[0], 0);
+ creationTime = U32_AT(&header[4]);
+ }
+
+ String8 s;
+ convertTimeToDate(creationTime, &s);
+
+ mFileMetaData->setCString(kKeyDate, s.string());
+
+ *offset += chunk_size;
+ break;
+ }
+
default:
{
*offset += chunk_size;
@@ -757,6 +882,102 @@
return OK;
}
+status_t MPEG4Extractor::parseMetaData(off_t offset, size_t size) {
+ if (size < 4) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t *buffer = new uint8_t[size + 1];
+ if (mDataSource->readAt(
+ offset, buffer, size) != (ssize_t)size) {
+ delete[] buffer;
+ buffer = NULL;
+
+ return ERROR_IO;
+ }
+
+ uint32_t flags = U32_AT(buffer);
+
+ uint32_t metadataKey = 0;
+ switch (mPath[4]) {
+ case FOURCC(0xa9, 'a', 'l', 'b'):
+ {
+ metadataKey = kKeyAlbum;
+ break;
+ }
+ case FOURCC(0xa9, 'A', 'R', 'T'):
+ {
+ metadataKey = kKeyArtist;
+ break;
+ }
+ case FOURCC(0xa9, 'd', 'a', 'y'):
+ {
+ metadataKey = kKeyYear;
+ break;
+ }
+ case FOURCC(0xa9, 'n', 'a', 'm'):
+ {
+ metadataKey = kKeyTitle;
+ break;
+ }
+ case FOURCC(0xa9, 'w', 'r', 't'):
+ {
+ metadataKey = kKeyWriter;
+ break;
+ }
+ case FOURCC('c', 'o', 'v', 'r'):
+ {
+ metadataKey = kKeyAlbumArt;
+ break;
+ }
+ case FOURCC('g', 'n', 'r', 'e'):
+ {
+ metadataKey = kKeyGenre;
+ break;
+ }
+ case FOURCC('t', 'r', 'k', 'n'):
+ {
+ if (size == 16 && flags == 0) {
+ char tmp[16];
+ sprintf(tmp, "%d/%d",
+ (int)buffer[size - 5], (int)buffer[size - 3]);
+
+ printf("track: %s\n", tmp);
+ mFileMetaData->setCString(kKeyCDTrackNumber, tmp);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (size >= 8 && metadataKey) {
+ if (metadataKey == kKeyAlbumArt) {
+ mFileMetaData->setData(
+ kKeyAlbumArt, MetaData::TYPE_NONE,
+ buffer + 8, size - 8);
+ } else if (metadataKey == kKeyGenre) {
+ if (flags == 0) {
+ // uint8_t
+ char genre[10];
+ sprintf(genre, "%d", (int)buffer[size - 1]);
+
+ mFileMetaData->setCString(metadataKey, genre);
+ }
+ } else {
+ buffer[size] = '\0';
+
+ mFileMetaData->setCString(
+ metadataKey, (const char *)buffer + 8);
+ }
+ }
+
+ delete[] buffer;
+ buffer = NULL;
+
+ return OK;
+}
+
sp<MediaSource> MPEG4Extractor::getTrack(size_t index) {
status_t err;
if ((err = readMetaData()) != OK) {
@@ -929,20 +1150,16 @@
if (mBuffer == NULL) {
newBuffer = true;
- status_t err = mSampleTable->getSampleOffsetAndSize(
- mCurrentSampleIndex, &offset, &size);
-
- if (err != OK) {
- return err;
- }
-
- err = mSampleTable->getDecodingTime(mCurrentSampleIndex, &dts);
+ status_t err =
+ mSampleTable->getMetaDataForSample(
+ mCurrentSampleIndex, &offset, &size, &dts);
if (err != OK) {
return err;
}
err = mGroup->acquire_buffer(&mBuffer);
+
if (err != OK) {
CHECK_EQ(mBuffer, NULL);
return err;
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index c4d3b5d..986dcb2 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -301,8 +301,8 @@
quirks |= kRequiresAllocateBufferOnOutputPorts;
}
if (!strncmp(componentName, "OMX.qcom.video.decoder.", 23)) {
- // XXX Required on P....on only.
quirks |= kRequiresAllocateBufferOnOutputPorts;
+ quirks |= kDefersOutputBufferAllocation;
}
if (!strncmp(componentName, "OMX.TI.", 7)) {
@@ -1016,6 +1016,7 @@
const char *componentName,
const sp<MediaSource> &source)
: mOMX(omx),
+ mOMXLivesLocally(omx->livesLocally(getpid())),
mNode(node),
mQuirks(quirks),
mIsEncoder(isEncoder),
@@ -1188,15 +1189,35 @@
sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize);
CHECK(mem.get() != NULL);
+ BufferInfo info;
+ info.mData = NULL;
+ info.mSize = def.nBufferSize;
+
IOMX::buffer_id buffer;
if (portIndex == kPortIndexInput
&& (mQuirks & kRequiresAllocateBufferOnInputPorts)) {
- err = mOMX->allocateBufferWithBackup(
- mNode, portIndex, mem, &buffer);
+ if (mOMXLivesLocally) {
+ mem.clear();
+
+ err = mOMX->allocateBuffer(
+ mNode, portIndex, def.nBufferSize, &buffer,
+ &info.mData);
+ } else {
+ err = mOMX->allocateBufferWithBackup(
+ mNode, portIndex, mem, &buffer);
+ }
} else if (portIndex == kPortIndexOutput
&& (mQuirks & kRequiresAllocateBufferOnOutputPorts)) {
- err = mOMX->allocateBufferWithBackup(
- mNode, portIndex, mem, &buffer);
+ if (mOMXLivesLocally) {
+ mem.clear();
+
+ err = mOMX->allocateBuffer(
+ mNode, portIndex, def.nBufferSize, &buffer,
+ &info.mData);
+ } else {
+ err = mOMX->allocateBufferWithBackup(
+ mNode, portIndex, mem, &buffer);
+ }
} else {
err = mOMX->useBuffer(mNode, portIndex, mem, &buffer);
}
@@ -1206,15 +1227,25 @@
return err;
}
- BufferInfo info;
+ if (mem != NULL) {
+ info.mData = mem->pointer();
+ }
+
info.mBuffer = buffer;
info.mOwnedByComponent = false;
info.mMem = mem;
info.mMediaBuffer = NULL;
if (portIndex == kPortIndexOutput) {
- info.mMediaBuffer = new MediaBuffer(mem->pointer(), mem->size());
- info.mMediaBuffer->setObserver(this);
+ if (!(mOMXLivesLocally
+ && (mQuirks & kRequiresAllocateBufferOnOutputPorts)
+ && (mQuirks & kDefersOutputBufferAllocation))) {
+ // If the node does not fill in the buffer ptr at this time,
+ // we will defer creating the MediaBuffer until receiving
+ // the first FILL_BUFFER_DONE notification instead.
+ info.mMediaBuffer = new MediaBuffer(info.mData, info.mSize);
+ info.mMediaBuffer->setObserver(this);
+ }
}
mPortBuffers[portIndex].push(info);
@@ -1322,6 +1353,22 @@
} else if (mPortStatus[kPortIndexOutput] != SHUTTING_DOWN) {
CHECK_EQ(mPortStatus[kPortIndexOutput], ENABLED);
+ if (info->mMediaBuffer == NULL) {
+ CHECK(mOMXLivesLocally);
+ CHECK(mQuirks & kRequiresAllocateBufferOnOutputPorts);
+ CHECK(mQuirks & kDefersOutputBufferAllocation);
+
+ // The qcom video decoders on Nexus don't actually allocate
+ // output buffer memory on a call to OMX_AllocateBuffer
+ // the "pBuffer" member of the OMX_BUFFERHEADERTYPE
+ // structure is only filled in later.
+
+ info->mMediaBuffer = new MediaBuffer(
+ msg.u.extended_buffer_data.data_ptr,
+ info->mSize);
+ info->mMediaBuffer->setObserver(this);
+ }
+
MediaBuffer *buffer = info->mMediaBuffer;
buffer->set_range(
@@ -1588,6 +1635,8 @@
}
void OMXCodec::onStateChange(OMX_STATETYPE newState) {
+ CODEC_LOGV("onStateChange %d", newState);
+
switch (newState) {
case OMX_StateIdle:
{
@@ -1655,6 +1704,12 @@
break;
}
+ case OMX_StateInvalid:
+ {
+ setState(ERROR);
+ break;
+ }
+
default:
{
CHECK(!"should not be here.");
@@ -1834,16 +1889,16 @@
static const uint8_t kNALStartCode[4] =
{ 0x00, 0x00, 0x00, 0x01 };
- CHECK(info->mMem->size() >= specific->mSize + 4);
+ CHECK(info->mSize >= specific->mSize + 4);
size += 4;
- memcpy(info->mMem->pointer(), kNALStartCode, 4);
- memcpy((uint8_t *)info->mMem->pointer() + 4,
+ memcpy(info->mData, kNALStartCode, 4);
+ memcpy((uint8_t *)info->mData + 4,
specific->mData, specific->mSize);
} else {
- CHECK(info->mMem->size() >= specific->mSize);
- memcpy(info->mMem->pointer(), specific->mData, specific->mSize);
+ CHECK(info->mSize >= specific->mSize);
+ memcpy(info->mData, specific->mData, specific->mSize);
}
mNoMoreOutputData = false;
@@ -1890,12 +1945,12 @@
srcLength = srcBuffer->range_length();
- if (info->mMem->size() < srcLength) {
- LOGE("info->mMem->size() = %d, srcLength = %d",
- info->mMem->size(), srcLength);
+ if (info->mSize < srcLength) {
+ LOGE("info->mSize = %d, srcLength = %d",
+ info->mSize, srcLength);
}
- CHECK(info->mMem->size() >= srcLength);
- memcpy(info->mMem->pointer(),
+ CHECK(info->mSize >= srcLength);
+ memcpy(info->mData,
(const uint8_t *)srcBuffer->data() + srcBuffer->range_offset(),
srcLength);
diff --git a/media/libstagefright/SampleIterator.cpp b/media/libstagefright/SampleIterator.cpp
new file mode 100644
index 0000000..7155c61
--- /dev/null
+++ b/media/libstagefright/SampleIterator.cpp
@@ -0,0 +1,314 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "SampleIterator"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include "include/SampleIterator.h"
+
+#include <arpa/inet.h>
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/Utils.h>
+
+#include "include/SampleTable.h"
+
+namespace android {
+
+SampleIterator::SampleIterator(SampleTable *table)
+ : mTable(table),
+ mInitialized(false),
+ mTimeToSampleIndex(0),
+ mTTSSampleIndex(0),
+ mTTSSampleTime(0),
+ mTTSCount(0),
+ mTTSDuration(0) {
+ reset();
+}
+
+void SampleIterator::reset() {
+ mSampleToChunkIndex = 0;
+ mFirstChunk = 0;
+ mFirstChunkSampleIndex = 0;
+ mStopChunk = 0;
+ mStopChunkSampleIndex = 0;
+ mSamplesPerChunk = 0;
+ mChunkDesc = 0;
+}
+
+status_t SampleIterator::seekTo(uint32_t sampleIndex) {
+ LOGV("seekTo(%d)", sampleIndex);
+
+ if (sampleIndex >= mTable->mNumSampleSizes) {
+ return ERROR_END_OF_STREAM;
+ }
+
+ if (mTable->mSampleToChunkOffset < 0
+ || mTable->mChunkOffsetOffset < 0
+ || mTable->mSampleSizeOffset < 0
+ || mTable->mTimeToSampleCount == 0) {
+
+ return ERROR_MALFORMED;
+ }
+
+ if (mInitialized && mCurrentSampleIndex == sampleIndex) {
+ return OK;
+ }
+
+ if (!mInitialized || sampleIndex < mFirstChunkSampleIndex) {
+ reset();
+ }
+
+ if (sampleIndex >= mStopChunkSampleIndex) {
+ status_t err;
+ if ((err = findChunkRange(sampleIndex)) != OK) {
+ LOGE("findChunkRange failed");
+ return err;
+ }
+ }
+
+ CHECK(sampleIndex < mStopChunkSampleIndex);
+
+ uint32_t chunk =
+ (sampleIndex - mFirstChunkSampleIndex) / mSamplesPerChunk
+ + mFirstChunk;
+
+ if (!mInitialized || chunk != mCurrentChunkIndex) {
+ mCurrentChunkIndex = chunk;
+
+ status_t err;
+ if ((err = getChunkOffset(chunk, &mCurrentChunkOffset)) != OK) {
+ LOGE("getChunkOffset return error");
+ return err;
+ }
+
+ mCurrentChunkSampleSizes.clear();
+
+ uint32_t firstChunkSampleIndex =
+ mFirstChunkSampleIndex
+ + mSamplesPerChunk * (mCurrentChunkIndex - mFirstChunk);
+
+ for (uint32_t i = 0; i < mSamplesPerChunk; ++i) {
+ size_t sampleSize;
+ if ((err = getSampleSizeDirect(
+ firstChunkSampleIndex + i, &sampleSize)) != OK) {
+ LOGE("getSampleSizeDirect return error");
+ return err;
+ }
+
+ mCurrentChunkSampleSizes.push(sampleSize);
+ }
+ }
+
+ uint32_t chunkRelativeSampleIndex =
+ (sampleIndex - mFirstChunkSampleIndex) % mSamplesPerChunk;
+
+ mCurrentSampleOffset = mCurrentChunkOffset;
+ for (uint32_t i = 0; i < chunkRelativeSampleIndex; ++i) {
+ mCurrentSampleOffset += mCurrentChunkSampleSizes[i];
+ }
+
+ mCurrentSampleSize = mCurrentChunkSampleSizes[chunkRelativeSampleIndex];
+ if (sampleIndex < mTTSSampleIndex) {
+ mTimeToSampleIndex = 0;
+ mTTSSampleIndex = 0;
+ mTTSSampleTime = 0;
+ mTTSCount = 0;
+ mTTSDuration = 0;
+ }
+
+ status_t err;
+ if ((err = findSampleTime(sampleIndex, &mCurrentSampleTime)) != OK) {
+ LOGE("findSampleTime return error");
+ return err;
+ }
+
+ mCurrentSampleIndex = sampleIndex;
+
+ mInitialized = true;
+
+ return OK;
+}
+
+status_t SampleIterator::findChunkRange(uint32_t sampleIndex) {
+ CHECK(sampleIndex >= mFirstChunkSampleIndex);
+
+ while (sampleIndex >= mStopChunkSampleIndex) {
+ if (mSampleToChunkIndex == mTable->mNumSampleToChunkOffsets) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ mFirstChunkSampleIndex = mStopChunkSampleIndex;
+
+ const SampleTable::SampleToChunkEntry *entry =
+ &mTable->mSampleToChunkEntries[mSampleToChunkIndex];
+
+ mFirstChunk = entry->startChunk;
+ mSamplesPerChunk = entry->samplesPerChunk;
+ mChunkDesc = entry->chunkDesc;
+
+ if (mSampleToChunkIndex + 1 < mTable->mNumSampleToChunkOffsets) {
+ mStopChunk = entry[1].startChunk;
+
+ mStopChunkSampleIndex =
+ mFirstChunkSampleIndex
+ + (mStopChunk - mFirstChunk) * mSamplesPerChunk;
+ } else {
+ mStopChunk = 0xffffffff;
+ mStopChunkSampleIndex = 0xffffffff;
+ }
+
+ ++mSampleToChunkIndex;
+ }
+
+ return OK;
+}
+
+status_t SampleIterator::getChunkOffset(uint32_t chunk, off_t *offset) {
+ *offset = 0;
+
+ if (chunk >= mTable->mNumChunkOffsets) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ if (mTable->mChunkOffsetType == SampleTable::kChunkOffsetType32) {
+ uint32_t offset32;
+
+ if (mTable->mDataSource->readAt(
+ mTable->mChunkOffsetOffset + 8 + 4 * chunk,
+ &offset32,
+ sizeof(offset32)) < (ssize_t)sizeof(offset32)) {
+ return ERROR_IO;
+ }
+
+ *offset = ntohl(offset32);
+ } else {
+ CHECK_EQ(mTable->mChunkOffsetType, SampleTable::kChunkOffsetType64);
+
+ uint64_t offset64;
+ if (mTable->mDataSource->readAt(
+ mTable->mChunkOffsetOffset + 8 + 8 * chunk,
+ &offset64,
+ sizeof(offset64)) < (ssize_t)sizeof(offset64)) {
+ return ERROR_IO;
+ }
+
+ *offset = ntoh64(offset64);
+ }
+
+ return OK;
+}
+
+status_t SampleIterator::getSampleSizeDirect(
+ uint32_t sampleIndex, size_t *size) {
+ *size = 0;
+
+ if (sampleIndex >= mTable->mNumSampleSizes) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ if (mTable->mDefaultSampleSize > 0) {
+ *size = mTable->mDefaultSampleSize;
+ return OK;
+ }
+
+ switch (mTable->mSampleSizeFieldSize) {
+ case 32:
+ {
+ if (mTable->mDataSource->readAt(
+ mTable->mSampleSizeOffset + 12 + 4 * sampleIndex,
+ size, sizeof(*size)) < (ssize_t)sizeof(*size)) {
+ return ERROR_IO;
+ }
+
+ *size = ntohl(*size);
+ break;
+ }
+
+ case 16:
+ {
+ uint16_t x;
+ if (mTable->mDataSource->readAt(
+ mTable->mSampleSizeOffset + 12 + 2 * sampleIndex,
+ &x, sizeof(x)) < (ssize_t)sizeof(x)) {
+ return ERROR_IO;
+ }
+
+ *size = ntohs(x);
+ break;
+ }
+
+ case 8:
+ {
+ uint8_t x;
+ if (mTable->mDataSource->readAt(
+ mTable->mSampleSizeOffset + 12 + sampleIndex,
+ &x, sizeof(x)) < (ssize_t)sizeof(x)) {
+ return ERROR_IO;
+ }
+
+ *size = x;
+ break;
+ }
+
+ default:
+ {
+ CHECK_EQ(mTable->mSampleSizeFieldSize, 4);
+
+ uint8_t x;
+ if (mTable->mDataSource->readAt(
+ mTable->mSampleSizeOffset + 12 + sampleIndex / 2,
+ &x, sizeof(x)) < (ssize_t)sizeof(x)) {
+ return ERROR_IO;
+ }
+
+ *size = (sampleIndex & 1) ? x & 0x0f : x >> 4;
+ break;
+ }
+ }
+
+ return OK;
+}
+
+status_t SampleIterator::findSampleTime(
+ uint32_t sampleIndex, uint32_t *time) {
+ if (sampleIndex >= mTable->mNumSampleSizes) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ while (sampleIndex >= mTTSSampleIndex + mTTSCount) {
+ if (mTimeToSampleIndex == mTable->mTimeToSampleCount) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ mTTSSampleIndex += mTTSCount;
+ mTTSSampleTime += mTTSCount * mTTSDuration;
+
+ mTTSCount = mTable->mTimeToSample[2 * mTimeToSampleIndex];
+ mTTSDuration = mTable->mTimeToSample[2 * mTimeToSampleIndex + 1];
+
+ ++mTimeToSampleIndex;
+ }
+
+ *time = mTTSSampleTime + mTTSDuration * (sampleIndex - mTTSSampleIndex);
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
index 2de96d4..89a522e 100644
--- a/media/libstagefright/SampleTable.cpp
+++ b/media/libstagefright/SampleTable.cpp
@@ -15,9 +15,11 @@
*/
#define LOG_TAG "SampleTable"
+//#define LOG_NDEBUG 0
#include <utils/Log.h>
#include "include/SampleTable.h"
+#include "include/SampleIterator.h"
#include <arpa/inet.h>
@@ -27,10 +29,16 @@
namespace android {
-static const uint32_t kChunkOffsetType32 = FOURCC('s', 't', 'c', 'o');
-static const uint32_t kChunkOffsetType64 = FOURCC('c', 'o', '6', '4');
-static const uint32_t kSampleSizeType32 = FOURCC('s', 't', 's', 'z');
-static const uint32_t kSampleSizeTypeCompact = FOURCC('s', 't', 'z', '2');
+// static
+const uint32_t SampleTable::kChunkOffsetType32 = FOURCC('s', 't', 'c', 'o');
+// static
+const uint32_t SampleTable::kChunkOffsetType64 = FOURCC('c', 'o', '6', '4');
+// static
+const uint32_t SampleTable::kSampleSizeType32 = FOURCC('s', 't', 's', 'z');
+// static
+const uint32_t SampleTable::kSampleSizeTypeCompact = FOURCC('s', 't', 'z', '2');
+
+////////////////////////////////////////////////////////////////////////////////
SampleTable::SampleTable(const sp<DataSource> &source)
: mDataSource(source),
@@ -46,12 +54,20 @@
mTimeToSampleCount(0),
mTimeToSample(NULL),
mSyncSampleOffset(-1),
- mNumSyncSamples(0) {
+ mNumSyncSamples(0),
+ mSampleToChunkEntries(NULL) {
+ mSampleIterator = new SampleIterator(this);
}
SampleTable::~SampleTable() {
+ delete[] mSampleToChunkEntries;
+ mSampleToChunkEntries = NULL;
+
delete[] mTimeToSample;
mTimeToSample = NULL;
+
+ delete mSampleIterator;
+ mSampleIterator = NULL;
}
status_t SampleTable::setChunkOffsetParams(
@@ -124,6 +140,25 @@
return ERROR_MALFORMED;
}
+ mSampleToChunkEntries =
+ new SampleToChunkEntry[mNumSampleToChunkOffsets];
+
+ for (uint32_t i = 0; i < mNumSampleToChunkOffsets; ++i) {
+ uint8_t buffer[12];
+ if (mDataSource->readAt(
+ mSampleToChunkOffset + 8 + i * 12, buffer, sizeof(buffer))
+ != (ssize_t)sizeof(buffer)) {
+ return ERROR_IO;
+ }
+
+ CHECK(U32_AT(buffer) >= 1); // chunk index is 1 based in the spec.
+
+ // We want the chunk index to be 0-based.
+ mSampleToChunkEntries[i].startChunk = U32_AT(buffer) - 1;
+ mSampleToChunkEntries[i].samplesPerChunk = U32_AT(&buffer[4]);
+ mSampleToChunkEntries[i].chunkDesc = U32_AT(&buffer[8]);
+ }
+
return OK;
}
@@ -250,217 +285,10 @@
return mNumChunkOffsets;
}
-status_t SampleTable::getChunkOffset(uint32_t chunk_index, off_t *offset) {
- *offset = 0;
-
- if (mChunkOffsetOffset < 0) {
- return ERROR_MALFORMED;
- }
-
- if (chunk_index >= mNumChunkOffsets) {
- return ERROR_OUT_OF_RANGE;
- }
-
- if (mChunkOffsetType == kChunkOffsetType32) {
- uint32_t offset32;
-
- if (mDataSource->readAt(
- mChunkOffsetOffset + 8 + 4 * chunk_index,
- &offset32,
- sizeof(offset32)) < (ssize_t)sizeof(offset32)) {
- return ERROR_IO;
- }
-
- *offset = ntohl(offset32);
- } else {
- CHECK_EQ(mChunkOffsetType, kChunkOffsetType64);
-
- uint64_t offset64;
- if (mDataSource->readAt(
- mChunkOffsetOffset + 8 + 8 * chunk_index,
- &offset64,
- sizeof(offset64)) < (ssize_t)sizeof(offset64)) {
- return ERROR_IO;
- }
-
- *offset = ntoh64(offset64);
- }
-
- return OK;
-}
-
-status_t SampleTable::getChunkForSample(
- uint32_t sample_index,
- uint32_t *chunk_index,
- uint32_t *chunk_relative_sample_index,
- uint32_t *desc_index) {
- *chunk_index = 0;
- *chunk_relative_sample_index = 0;
- *desc_index = 0;
-
- if (mSampleToChunkOffset < 0) {
- return ERROR_MALFORMED;
- }
-
- if (sample_index >= countSamples()) {
- return ERROR_END_OF_STREAM;
- }
-
- uint32_t first_chunk = 0;
- uint32_t samples_per_chunk = 0;
- uint32_t chunk_desc_index = 0;
-
- uint32_t index = 0;
- while (index < mNumSampleToChunkOffsets) {
- uint8_t buffer[12];
- if (mDataSource->readAt(mSampleToChunkOffset + 8 + index * 12,
- buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
- return ERROR_IO;
- }
-
- uint32_t stop_chunk = U32_AT(buffer);
- if (sample_index < (stop_chunk - first_chunk) * samples_per_chunk) {
- break;
- }
-
- sample_index -= (stop_chunk - first_chunk) * samples_per_chunk;
- first_chunk = stop_chunk;
- samples_per_chunk = U32_AT(&buffer[4]);
- chunk_desc_index = U32_AT(&buffer[8]);
-
- ++index;
- }
-
- *chunk_index = sample_index / samples_per_chunk + first_chunk - 1;
- *chunk_relative_sample_index = sample_index % samples_per_chunk;
- *desc_index = chunk_desc_index;
-
- return OK;
-}
-
uint32_t SampleTable::countSamples() const {
return mNumSampleSizes;
}
-status_t SampleTable::getSampleSize(
- uint32_t sample_index, size_t *sample_size) {
- *sample_size = 0;
-
- if (mSampleSizeOffset < 0) {
- return ERROR_MALFORMED;
- }
-
- if (sample_index >= mNumSampleSizes) {
- return ERROR_OUT_OF_RANGE;
- }
-
- if (mDefaultSampleSize > 0) {
- *sample_size = mDefaultSampleSize;
- return OK;
- }
-
- switch (mSampleSizeFieldSize) {
- case 32:
- {
- if (mDataSource->readAt(
- mSampleSizeOffset + 12 + 4 * sample_index,
- sample_size, sizeof(*sample_size)) < (ssize_t)sizeof(*sample_size)) {
- return ERROR_IO;
- }
-
- *sample_size = ntohl(*sample_size);
- break;
- }
-
- case 16:
- {
- uint16_t x;
- if (mDataSource->readAt(
- mSampleSizeOffset + 12 + 2 * sample_index,
- &x, sizeof(x)) < (ssize_t)sizeof(x)) {
- return ERROR_IO;
- }
-
- *sample_size = ntohs(x);
- break;
- }
-
- case 8:
- {
- uint8_t x;
- if (mDataSource->readAt(
- mSampleSizeOffset + 12 + sample_index,
- &x, sizeof(x)) < (ssize_t)sizeof(x)) {
- return ERROR_IO;
- }
-
- *sample_size = x;
- break;
- }
-
- default:
- {
- CHECK_EQ(mSampleSizeFieldSize, 4);
-
- uint8_t x;
- if (mDataSource->readAt(
- mSampleSizeOffset + 12 + sample_index / 2,
- &x, sizeof(x)) < (ssize_t)sizeof(x)) {
- return ERROR_IO;
- }
-
- *sample_size = (sample_index & 1) ? x & 0x0f : x >> 4;
- break;
- }
- }
-
- return OK;
-}
-
-status_t SampleTable::getSampleOffsetAndSize(
- uint32_t sample_index, off_t *offset, size_t *size) {
- Mutex::Autolock autoLock(mLock);
-
- *offset = 0;
- *size = 0;
-
- uint32_t chunk_index;
- uint32_t chunk_relative_sample_index;
- uint32_t desc_index;
- status_t err = getChunkForSample(
- sample_index, &chunk_index, &chunk_relative_sample_index,
- &desc_index);
-
- if (err != OK) {
- return err;
- }
-
- err = getChunkOffset(chunk_index, offset);
-
- if (err != OK) {
- return err;
- }
-
- for (uint32_t j = 0; j < chunk_relative_sample_index; ++j) {
- size_t sample_size;
- err = getSampleSize(sample_index - j - 1, &sample_size);
-
- if (err != OK) {
- return err;
- }
-
- *offset += sample_size;
- }
-
- err = getSampleSize(sample_index, size);
-
- if (err != OK) {
- return err;
- }
-
- return OK;
-}
-
status_t SampleTable::getMaxSampleSize(size_t *max_size) {
Mutex::Autolock autoLock(mLock);
@@ -468,7 +296,7 @@
for (uint32_t i = 0; i < mNumSampleSizes; ++i) {
size_t sample_size;
- status_t err = getSampleSize(i, &sample_size);
+ status_t err = getSampleSize_l(i, &sample_size);
if (err != OK) {
return err;
@@ -482,34 +310,6 @@
return OK;
}
-status_t SampleTable::getDecodingTime(uint32_t sample_index, uint32_t *time) {
- // XXX FIXME idiotic (for the common use-case) O(n) algorithm below...
-
- Mutex::Autolock autoLock(mLock);
-
- if (sample_index >= mNumSampleSizes) {
- return ERROR_OUT_OF_RANGE;
- }
-
- uint32_t cur_sample = 0;
- *time = 0;
- for (uint32_t i = 0; i < mTimeToSampleCount; ++i) {
- uint32_t n = mTimeToSample[2 * i];
- uint32_t delta = mTimeToSample[2 * i + 1];
-
- if (sample_index < cur_sample + n) {
- *time += delta * (sample_index - cur_sample);
-
- return OK;
- }
-
- *time += delta * n;
- cur_sample += n;
- }
-
- return ERROR_OUT_OF_RANGE;
-}
-
uint32_t abs_difference(uint32_t time1, uint32_t time2) {
return time1 > time2 ? time1 - time2 : time2 - time1;
}
@@ -539,7 +339,7 @@
}
if (flags & kSyncSample_Flag) {
- return findClosestSyncSample(*sample_index, sample_index);
+ return findClosestSyncSample_l(*sample_index, sample_index);
}
return OK;
@@ -552,7 +352,7 @@
return ERROR_OUT_OF_RANGE;
}
-status_t SampleTable::findClosestSyncSample(
+status_t SampleTable::findClosestSyncSample_l(
uint32_t start_sample_index, uint32_t *sample_index) {
*sample_index = 0;
@@ -590,6 +390,8 @@
}
status_t SampleTable::findThumbnailSample(uint32_t *sample_index) {
+ Mutex::Autolock autoLock(mLock);
+
if (mSyncSampleOffset < 0) {
// All samples are sync-samples.
*sample_index = 0;
@@ -620,7 +422,7 @@
// Now x is a sample index.
size_t sampleSize;
- status_t err = getSampleSize(x, &sampleSize);
+ status_t err = getSampleSize_l(x, &sampleSize);
if (err != OK) {
return err;
}
@@ -636,5 +438,38 @@
return OK;
}
+status_t SampleTable::getSampleSize_l(
+ uint32_t sampleIndex, size_t *sampleSize) {
+ return mSampleIterator->getSampleSizeDirect(
+ sampleIndex, sampleSize);
+}
+
+status_t SampleTable::getMetaDataForSample(
+ uint32_t sampleIndex,
+ off_t *offset,
+ size_t *size,
+ uint32_t *decodingTime) {
+ Mutex::Autolock autoLock(mLock);
+
+ status_t err;
+ if ((err = mSampleIterator->seekTo(sampleIndex)) != OK) {
+ return err;
+ }
+
+ if (offset) {
+ *offset = mSampleIterator->getSampleOffset();
+ }
+
+ if (size) {
+ *size = mSampleIterator->getSampleSize();
+ }
+
+ if (decodingTime) {
+ *decodingTime = mSampleIterator->getSampleTime();
+ }
+
+ return OK;
+}
+
} // namespace android
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index 020887c..dfba74f 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -276,8 +276,6 @@
}
const char *StagefrightMetadataRetriever::extractMetadata(int keyCode) {
- LOGV("extractMetadata %d", keyCode);
-
if (mExtractor == NULL) {
return NULL;
}
@@ -315,6 +313,7 @@
{ kKeyGenre, METADATA_KEY_GENRE },
{ kKeyTitle, METADATA_KEY_TITLE },
{ kKeyYear, METADATA_KEY_YEAR },
+ { kKeyWriter, METADATA_KEY_WRITER },
};
static const size_t kNumMapEntries = sizeof(kMap) / sizeof(kMap[0]);
@@ -357,7 +356,6 @@
// The duration value is a string representing the duration in ms.
sprintf(tmp, "%lld", (maxDurationUs + 500) / 1000);
-
mMetaData.add(METADATA_KEY_DURATION, String8(tmp));
}
diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp
index 959a767..da8fe79 100644
--- a/media/libstagefright/WAVExtractor.cpp
+++ b/media/libstagefright/WAVExtractor.cpp
@@ -44,7 +44,8 @@
struct WAVSource : public MediaSource {
WAVSource(
const sp<DataSource> &dataSource,
- int32_t sampleRate, int32_t numChannels,
+ const sp<MetaData> &meta,
+ int32_t bitsPerSample,
off_t offset, size_t size);
virtual status_t start(MetaData *params = NULL);
@@ -61,8 +62,10 @@
static const size_t kMaxFrameSize;
sp<DataSource> mDataSource;
+ sp<MetaData> mMeta;
int32_t mSampleRate;
int32_t mNumChannels;
+ int32_t mBitsPerSample;
off_t mOffset;
size_t mSize;
bool mStarted;
@@ -104,7 +107,8 @@
}
return new WAVSource(
- mDataSource, mSampleRate, mNumChannels, mDataOffset, mDataSize);
+ mDataSource, mTrackMeta,
+ mBitsPerSample, mDataOffset, mDataSize);
}
sp<MetaData> WAVExtractor::getTrackMetaData(
@@ -113,17 +117,7 @@
return NULL;
}
- sp<MetaData> meta = new MetaData;
- meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
- meta->setInt32(kKeyChannelCount, mNumChannels);
- meta->setInt32(kKeySampleRate, mSampleRate);
-
- int64_t durationUs =
- 1000000LL * (mDataSize / (mNumChannels * 2)) / mSampleRate;
-
- meta->setInt64(kKeyDuration, durationUs);
-
- return meta;
+ return mTrackMeta;
}
status_t WAVExtractor::init() {
@@ -149,7 +143,7 @@
remainingSize -= 8;
offset += 8;
-
+
uint32_t chunkSize = U32_LE_AT(&chunkHeader[4]);
if (chunkSize > remainingSize) {
@@ -178,7 +172,13 @@
mSampleRate = U32_LE_AT(&formatSpec[4]);
- if (U16_LE_AT(&formatSpec[14]) != 16) {
+ if (mSampleRate == 0) {
+ return ERROR_MALFORMED;
+ }
+
+ mBitsPerSample = U16_LE_AT(&formatSpec[14]);
+
+ if (mBitsPerSample != 8 && mBitsPerSample != 16) {
return ERROR_UNSUPPORTED;
}
@@ -188,6 +188,19 @@
mDataOffset = offset;
mDataSize = chunkSize;
+ mTrackMeta = new MetaData;
+ mTrackMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+ mTrackMeta->setInt32(kKeyChannelCount, mNumChannels);
+ mTrackMeta->setInt32(kKeySampleRate, mSampleRate);
+
+ size_t bytesPerSample = mBitsPerSample >> 3;
+
+ int64_t durationUs =
+ 1000000LL * (mDataSize / (mNumChannels * bytesPerSample))
+ / mSampleRate;
+
+ mTrackMeta->setInt64(kKeyDuration, durationUs);
+
return OK;
}
}
@@ -202,15 +215,20 @@
WAVSource::WAVSource(
const sp<DataSource> &dataSource,
- int32_t sampleRate, int32_t numChannels,
+ const sp<MetaData> &meta,
+ int32_t bitsPerSample,
off_t offset, size_t size)
: mDataSource(dataSource),
- mSampleRate(sampleRate),
- mNumChannels(numChannels),
+ mMeta(meta),
+ mSampleRate(0),
+ mNumChannels(0),
+ mBitsPerSample(bitsPerSample),
mOffset(offset),
mSize(size),
mStarted(false),
mGroup(NULL) {
+ CHECK(mMeta->findInt32(kKeySampleRate, &mSampleRate));
+ CHECK(mMeta->findInt32(kKeyChannelCount, &mNumChannels));
}
WAVSource::~WAVSource() {
@@ -227,6 +245,11 @@
mGroup = new MediaBufferGroup;
mGroup->add_buffer(new MediaBuffer(kMaxFrameSize));
+ if (mBitsPerSample == 8) {
+ // As a temporary buffer for 8->16 bit conversion.
+ mGroup->add_buffer(new MediaBuffer(kMaxFrameSize));
+ }
+
mCurrentPos = mOffset;
mStarted = true;
@@ -250,17 +273,7 @@
sp<MetaData> WAVSource::getFormat() {
LOGV("WAVSource::getFormat");
- sp<MetaData> meta = new MetaData;
- meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
- meta->setInt32(kKeyChannelCount, mNumChannels);
- meta->setInt32(kKeySampleRate, mSampleRate);
-
- int64_t durationUs =
- 1000000LL * (mSize / (mNumChannels * 2)) / mSampleRate;
-
- meta->setInt64(kKeyDuration, durationUs);
-
- return meta;
+ return mMeta;
}
status_t WAVSource::read(
@@ -283,7 +296,8 @@
}
ssize_t n = mDataSource->readAt(
- mCurrentPos, buffer->data(), kMaxFrameSize);
+ mCurrentPos, buffer->data(),
+ mBitsPerSample == 8 ? kMaxFrameSize / 2 : kMaxFrameSize);
if (n <= 0) {
buffer->release();
@@ -295,10 +309,34 @@
mCurrentPos += n;
buffer->set_range(0, n);
+
+ if (mBitsPerSample == 8) {
+ // Convert 8-bit unsigned samples to 16-bit signed.
+
+ MediaBuffer *tmp;
+ CHECK_EQ(mGroup->acquire_buffer(&tmp), OK);
+
+ // The new buffer holds the sample number of samples, but each
+ // one is 2 bytes wide.
+ tmp->set_range(0, 2 * n);
+
+ int16_t *dst = (int16_t *)tmp->data();
+ const uint8_t *src = (const uint8_t *)buffer->data();
+ while (n-- > 0) {
+ *dst++ = ((int16_t)(*src) - 128) * 256;
+ ++src;
+ }
+
+ buffer->release();
+ buffer = tmp;
+ }
+
+ size_t bytesPerSample = mBitsPerSample >> 3;
+
buffer->meta_data()->setInt64(
kKeyTime,
1000000LL * (mCurrentPos - mOffset)
- / (mNumChannels * 2) / mSampleRate);
+ / (mNumChannels * bytesPerSample) / mSampleRate);
*out = buffer;
diff --git a/media/libstagefright/id3/ID3.cpp b/media/libstagefright/id3/ID3.cpp
index 2b3ef1a..6d64717 100644
--- a/media/libstagefright/id3/ID3.cpp
+++ b/media/libstagefright/id3/ID3.cpp
@@ -33,7 +33,11 @@
mSize(0),
mFirstFrameOffset(0),
mVersion(ID3_UNKNOWN) {
- mIsValid = parse(source);
+ mIsValid = parseV2(source);
+
+ if (!mIsValid) {
+ mIsValid = parseV1(source);
+ }
}
ID3::~ID3() {
@@ -51,7 +55,7 @@
return mVersion;
}
-bool ID3::parse(const sp<DataSource> &source) {
+bool ID3::parseV2(const sp<DataSource> &source) {
struct id3_header {
char id[3];
uint8_t version_major;
@@ -119,7 +123,7 @@
}
if (header.flags & 0x80) {
- LOGI("removing unsynchronization");
+ LOGV("removing unsynchronization");
removeUnsynchronization();
}
@@ -128,12 +132,18 @@
// Version 2.3 has an optional extended header.
if (mSize < 4) {
+ free(mData);
+ mData = NULL;
+
return false;
}
size_t extendedHeaderSize = U32_AT(&mData[0]) + 4;
if (extendedHeaderSize > mSize) {
+ free(mData);
+ mData = NULL;
+
return false;
}
@@ -147,6 +157,9 @@
size_t paddingSize = U32_AT(&mData[6]);
if (mFirstFrameOffset + paddingSize > mSize) {
+ free(mData);
+ mData = NULL;
+
return false;
}
@@ -154,7 +167,7 @@
}
if (extendedFlags & 0x8000) {
- LOGI("have crc");
+ LOGV("have crc");
}
}
}
@@ -221,9 +234,37 @@
if (mParent.mVersion == ID3_V2_2) {
id->setTo((const char *)&mParent.mData[mOffset], 3);
- } else {
- CHECK_EQ(mParent.mVersion, ID3_V2_3);
+ } else if (mParent.mVersion == ID3_V2_3) {
id->setTo((const char *)&mParent.mData[mOffset], 4);
+ } else {
+ CHECK(mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1);
+
+ switch (mOffset) {
+ case 3:
+ id->setTo("TT2");
+ break;
+ case 33:
+ id->setTo("TP1");
+ break;
+ case 63:
+ id->setTo("TAL");
+ break;
+ case 93:
+ id->setTo("TYE");
+ break;
+ case 97:
+ id->setTo("COM");
+ break;
+ case 126:
+ id->setTo("TRK");
+ break;
+ case 127:
+ id->setTo("TCO");
+ break;
+ default:
+ CHECK(!"should not be here.");
+ break;
+ }
}
}
@@ -273,6 +314,20 @@
return;
}
+ if (mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1) {
+ if (mOffset == 126 || mOffset == 127) {
+ // Special treatment for the track number and genre.
+ char tmp[16];
+ sprintf(tmp, "%d", (int)*mFrameData);
+
+ id->setTo(tmp);
+ return;
+ }
+
+ id->setTo((const char *)mFrameData, mFrameSize);
+ return;
+ }
+
size_t n = mFrameSize - getHeaderLength() - 1;
if (*mFrameData == 0x00) {
@@ -280,7 +335,8 @@
convertISO8859ToString8(mFrameData + 1, n, id);
} else {
// UCS-2
- id->setTo((const char16_t *)(mFrameData + 1), n);
+ // API wants number of characters, not number of bytes...
+ id->setTo((const char16_t *)(mFrameData + 1), n / 2);
}
}
@@ -299,9 +355,11 @@
size_t ID3::Iterator::getHeaderLength() const {
if (mParent.mVersion == ID3_V2_2) {
return 6;
- } else {
- CHECK_EQ(mParent.mVersion, ID3_V2_3);
+ } else if (mParent.mVersion == ID3_V2_3) {
return 10;
+ } else {
+ CHECK(mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1);
+ return 0;
}
}
@@ -345,9 +403,7 @@
if (!strcmp(id, mID)) {
break;
}
- } else {
- CHECK_EQ(mParent.mVersion, ID3_V2_3);
-
+ } else if (mParent.mVersion == ID3_V2_3) {
if (mOffset + 10 > mParent.mSize) {
return;
}
@@ -377,6 +433,52 @@
if (!strcmp(id, mID)) {
break;
}
+ } else {
+ CHECK(mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1);
+
+ if (mOffset >= mParent.mSize) {
+ return;
+ }
+
+ mFrameData = &mParent.mData[mOffset];
+
+ switch (mOffset) {
+ case 3:
+ case 33:
+ case 63:
+ mFrameSize = 30;
+ break;
+ case 93:
+ mFrameSize = 4;
+ break;
+ case 97:
+ if (mParent.mVersion == ID3_V1) {
+ mFrameSize = 30;
+ } else {
+ mFrameSize = 29;
+ }
+ break;
+ case 126:
+ mFrameSize = 1;
+ break;
+ case 127:
+ mFrameSize = 1;
+ break;
+ default:
+ CHECK(!"Should not be here, invalid offset.");
+ break;
+ }
+
+ if (!mID) {
+ break;
+ }
+
+ String8 id;
+ getID(&id);
+
+ if (id == mID) {
+ break;
+ }
}
mOffset += mFrameSize;
@@ -461,5 +563,40 @@
return NULL;
}
+bool ID3::parseV1(const sp<DataSource> &source) {
+ const size_t V1_TAG_SIZE = 128;
+
+ off_t size;
+ if (source->getSize(&size) != OK || size < (off_t)V1_TAG_SIZE) {
+ return false;
+ }
+
+ mData = (uint8_t *)malloc(V1_TAG_SIZE);
+ if (source->readAt(size - V1_TAG_SIZE, mData, V1_TAG_SIZE)
+ != (ssize_t)V1_TAG_SIZE) {
+ free(mData);
+ mData = NULL;
+
+ return false;
+ }
+
+ if (memcmp("TAG", mData, 3)) {
+ free(mData);
+ mData = NULL;
+
+ return false;
+ }
+
+ mSize = V1_TAG_SIZE;
+ mFirstFrameOffset = 3;
+
+ if (mData[V1_TAG_SIZE - 3] != 0) {
+ mVersion = ID3_V1;
+ } else {
+ mVersion = ID3_V1_1;
+ }
+
+ return true;
+}
} // namespace android
diff --git a/media/libstagefright/id3/testid3.cpp b/media/libstagefright/id3/testid3.cpp
index 305b065..0741045 100644
--- a/media/libstagefright/id3/testid3.cpp
+++ b/media/libstagefright/id3/testid3.cpp
@@ -16,6 +16,8 @@
#include "../include/ID3.h"
+#include <sys/stat.h>
+
#include <ctype.h>
#include <dirent.h>
@@ -108,6 +110,12 @@
}
void scan(const char *path) {
+ struct stat st;
+ if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) {
+ scanFile(path);
+ return;
+ }
+
DIR *dir = opendir(path);
if (dir == NULL) {
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 37b14eb..b28a12c 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -46,7 +46,7 @@
AwesomePlayer();
~AwesomePlayer();
- void setListener(const sp<MediaPlayerBase> &listener);
+ void setListener(const wp<MediaPlayerBase> &listener);
status_t setDataSource(const char *uri);
status_t setDataSource(int fd, int64_t offset, int64_t length);
@@ -82,7 +82,7 @@
OMXClient mClient;
TimedEventQueue mQueue;
- sp<MediaPlayerBase> mListener;
+ wp<MediaPlayerBase> mListener;
sp<ISurface> mISurface;
sp<MediaPlayerBase::AudioSink> mAudioSink;
@@ -133,6 +133,8 @@
static void AudioNotify(void *me, int what);
void onStreamDone();
+ void notifyListener_l(int msg);
+
AwesomePlayer(const AwesomePlayer &);
AwesomePlayer &operator=(const AwesomePlayer &);
};
diff --git a/media/libstagefright/include/ID3.h b/media/libstagefright/include/ID3.h
index 79931ac..da042a3 100644
--- a/media/libstagefright/include/ID3.h
+++ b/media/libstagefright/include/ID3.h
@@ -28,6 +28,8 @@
struct ID3 {
enum Version {
ID3_UNKNOWN,
+ ID3_V1,
+ ID3_V1_1,
ID3_V2_2,
ID3_V2_3
};
@@ -74,7 +76,8 @@
size_t mFirstFrameOffset;
Version mVersion;
- bool parse(const sp<DataSource> &source);
+ bool parseV1(const sp<DataSource> &source);
+ bool parseV2(const sp<DataSource> &source);
void removeUnsynchronization();
ID3(const ID3 &);
diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h
index 0e360e8..1a13446 100644
--- a/media/libstagefright/include/MPEG4Extractor.h
+++ b/media/libstagefright/include/MPEG4Extractor.h
@@ -19,6 +19,7 @@
#define MPEG4_EXTRACTOR_H_
#include <media/stagefright/MediaExtractor.h>
+#include <utils/Vector.h>
namespace android {
@@ -55,10 +56,14 @@
Track *mFirstTrack, *mLastTrack;
+ sp<MetaData> mFileMetaData;
+
uint32_t mHandlerType;
+ Vector<uint32_t> mPath;
status_t readMetaData();
status_t parseChunk(off_t *offset, int depth);
+ status_t parseMetaData(off_t offset, size_t size);
MPEG4Extractor(const MPEG4Extractor &);
MPEG4Extractor &operator=(const MPEG4Extractor &);
diff --git a/media/libstagefright/include/OMX.h b/media/libstagefright/include/OMX.h
index ce0b0d55aa..ea131e8 100644
--- a/media/libstagefright/include/OMX.h
+++ b/media/libstagefright/include/OMX.h
@@ -31,6 +31,8 @@
public:
OMX();
+ virtual bool livesLocally(pid_t pid);
+
virtual status_t listNodes(List<ComponentInfo> *list);
virtual status_t allocateNode(
@@ -63,7 +65,7 @@
virtual status_t allocateBuffer(
node_id node, OMX_U32 port_index, size_t size,
- buffer_id *buffer);
+ buffer_id *buffer, void **buffer_data);
virtual status_t allocateBufferWithBackup(
node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms,
diff --git a/media/libstagefright/include/OMXNodeInstance.h b/media/libstagefright/include/OMXNodeInstance.h
index 923b801..b5b31ac 100644
--- a/media/libstagefright/include/OMXNodeInstance.h
+++ b/media/libstagefright/include/OMXNodeInstance.h
@@ -54,7 +54,8 @@
OMX::buffer_id *buffer);
status_t allocateBuffer(
- OMX_U32 portIndex, size_t size, OMX::buffer_id *buffer);
+ OMX_U32 portIndex, size_t size, OMX::buffer_id *buffer,
+ void **buffer_data);
status_t allocateBufferWithBackup(
OMX_U32 portIndex, const sp<IMemory> ¶ms,
diff --git a/media/libstagefright/include/SampleIterator.h b/media/libstagefright/include/SampleIterator.h
new file mode 100644
index 0000000..a5eaed9
--- /dev/null
+++ b/media/libstagefright/include/SampleIterator.h
@@ -0,0 +1,75 @@
+/*
+ * 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 <utils/Vector.h>
+
+namespace android {
+
+struct SampleTable;
+
+struct SampleIterator {
+ SampleIterator(SampleTable *table);
+
+ status_t seekTo(uint32_t sampleIndex);
+
+ uint32_t getChunkIndex() const { return mCurrentChunkIndex; }
+ uint32_t getDescIndex() const { return mChunkDesc; }
+ off_t getSampleOffset() const { return mCurrentSampleOffset; }
+ size_t getSampleSize() const { return mCurrentSampleSize; }
+ uint32_t getSampleTime() const { return mCurrentSampleTime; }
+
+ status_t getSampleSizeDirect(
+ uint32_t sampleIndex, size_t *size);
+
+private:
+ SampleTable *mTable;
+
+ bool mInitialized;
+
+ uint32_t mSampleToChunkIndex;
+ uint32_t mFirstChunk;
+ uint32_t mFirstChunkSampleIndex;
+ uint32_t mStopChunk;
+ uint32_t mStopChunkSampleIndex;
+ uint32_t mSamplesPerChunk;
+ uint32_t mChunkDesc;
+
+ uint32_t mCurrentChunkIndex;
+ off_t mCurrentChunkOffset;
+ Vector<size_t> mCurrentChunkSampleSizes;
+
+ uint32_t mTimeToSampleIndex;
+ uint32_t mTTSSampleIndex;
+ uint32_t mTTSSampleTime;
+ uint32_t mTTSCount;
+ uint32_t mTTSDuration;
+
+ uint32_t mCurrentSampleIndex;
+ off_t mCurrentSampleOffset;
+ size_t mCurrentSampleSize;
+ uint32_t mCurrentSampleTime;
+
+ void reset();
+ status_t findChunkRange(uint32_t sampleIndex);
+ status_t getChunkOffset(uint32_t chunk, off_t *offset);
+ status_t findSampleTime(uint32_t sampleIndex, uint32_t *time);
+
+ SampleIterator(const SampleIterator &);
+ SampleIterator &operator=(const SampleIterator &);
+};
+
+} // namespace android
+
diff --git a/media/libstagefright/include/SampleTable.h b/media/libstagefright/include/SampleTable.h
index ead3431..533ce84 100644
--- a/media/libstagefright/include/SampleTable.h
+++ b/media/libstagefright/include/SampleTable.h
@@ -28,6 +28,7 @@
namespace android {
class DataSource;
+struct SampleIterator;
class SampleTable : public RefBase {
public:
@@ -50,21 +51,16 @@
////////////////////////////////////////////////////////////////////////////
uint32_t countChunkOffsets() const;
- status_t getChunkOffset(uint32_t chunk_index, off_t *offset);
-
- status_t getChunkForSample(
- uint32_t sample_index, uint32_t *chunk_index,
- uint32_t *chunk_relative_sample_index, uint32_t *desc_index);
uint32_t countSamples() const;
- status_t getSampleSize(uint32_t sample_index, size_t *sample_size);
-
- status_t getSampleOffsetAndSize(
- uint32_t sample_index, off_t *offset, size_t *size);
status_t getMaxSampleSize(size_t *size);
- status_t getDecodingTime(uint32_t sample_index, uint32_t *time);
+ status_t getMetaDataForSample(
+ uint32_t sampleIndex,
+ off_t *offset,
+ size_t *size,
+ uint32_t *decodingTime);
enum {
kSyncSample_Flag = 1
@@ -72,15 +68,17 @@
status_t findClosestSample(
uint32_t req_time, uint32_t *sample_index, uint32_t flags);
- status_t findClosestSyncSample(
- uint32_t start_sample_index, uint32_t *sample_index);
-
status_t findThumbnailSample(uint32_t *sample_index);
protected:
~SampleTable();
private:
+ static const uint32_t kChunkOffsetType32;
+ static const uint32_t kChunkOffsetType64;
+ static const uint32_t kSampleSizeType32;
+ static const uint32_t kSampleSizeTypeCompact;
+
sp<DataSource> mDataSource;
Mutex mLock;
@@ -102,6 +100,22 @@
off_t mSyncSampleOffset;
uint32_t mNumSyncSamples;
+ SampleIterator *mSampleIterator;
+
+ struct SampleToChunkEntry {
+ uint32_t startChunk;
+ uint32_t samplesPerChunk;
+ uint32_t chunkDesc;
+ };
+ SampleToChunkEntry *mSampleToChunkEntries;
+
+ friend struct SampleIterator;
+
+ status_t findClosestSyncSample_l(
+ uint32_t start_sample_index, uint32_t *sample_index);
+
+ status_t getSampleSize_l(uint32_t sample_index, size_t *sample_size);
+
SampleTable(const SampleTable &);
SampleTable &operator=(const SampleTable &);
};
diff --git a/media/libstagefright/include/WAVExtractor.h b/media/libstagefright/include/WAVExtractor.h
index 8545efc..9384942 100644
--- a/media/libstagefright/include/WAVExtractor.h
+++ b/media/libstagefright/include/WAVExtractor.h
@@ -45,8 +45,10 @@
bool mValidFormat;
uint16_t mNumChannels;
uint32_t mSampleRate;
+ uint16_t mBitsPerSample;
off_t mDataOffset;
size_t mDataSize;
+ sp<MetaData> mTrackMeta;
status_t init();
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index 8c3f252..9ca060d 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -135,50 +135,6 @@
////////////////////////////////////////////////////////////////////////////////
-class BufferMeta {
-public:
- BufferMeta(OMX *owner, const sp<IMemory> &mem, bool is_backup = false)
- : mOwner(owner),
- mMem(mem),
- mIsBackup(is_backup) {
- }
-
- BufferMeta(OMX *owner, size_t size)
- : mOwner(owner),
- mSize(size),
- mIsBackup(false) {
- }
-
- void CopyFromOMX(const OMX_BUFFERHEADERTYPE *header) {
- if (!mIsBackup) {
- return;
- }
-
- memcpy((OMX_U8 *)mMem->pointer() + header->nOffset,
- header->pBuffer + header->nOffset,
- header->nFilledLen);
- }
-
- void CopyToOMX(const OMX_BUFFERHEADERTYPE *header) {
- if (!mIsBackup) {
- return;
- }
-
- memcpy(header->pBuffer + header->nOffset,
- (const OMX_U8 *)mMem->pointer() + header->nOffset,
- header->nFilledLen);
- }
-
-private:
- OMX *mOwner;
- sp<IMemory> mMem;
- size_t mSize;
- bool mIsBackup;
-
- BufferMeta(const BufferMeta &);
- BufferMeta &operator=(const BufferMeta &);
-};
-
OMX::OMX()
: mMaster(new OMXMaster),
mDispatcher(new CallbackDispatcher(this)),
@@ -208,6 +164,10 @@
instance->onObserverDied(mMaster);
}
+bool OMX::livesLocally(pid_t pid) {
+ return pid == getpid();
+}
+
status_t OMX::listNodes(List<ComponentInfo> *list) {
list->clear();
@@ -320,9 +280,9 @@
status_t OMX::allocateBuffer(
node_id node, OMX_U32 port_index, size_t size,
- buffer_id *buffer) {
+ buffer_id *buffer, void **buffer_data) {
return findInstance(node)->allocateBuffer(
- port_index, size, buffer);
+ port_index, size, buffer, buffer_data);
}
status_t OMX::allocateBufferWithBackup(
@@ -405,6 +365,7 @@
msg.u.extended_buffer_data.flags = pBuffer->nFlags;
msg.u.extended_buffer_data.timestamp = pBuffer->nTimeStamp;
msg.u.extended_buffer_data.platform_private = pBuffer->pPlatformPrivate;
+ msg.u.extended_buffer_data.data_ptr = pBuffer->pBuffer;
mDispatcher->post(msg);
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index 4eb6417..70c6a58 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -258,7 +258,8 @@
}
status_t OMXNodeInstance::allocateBuffer(
- OMX_U32 portIndex, size_t size, OMX::buffer_id *buffer) {
+ OMX_U32 portIndex, size_t size, OMX::buffer_id *buffer,
+ void **buffer_data) {
Mutex::Autolock autoLock(mLock);
BufferMeta *buffer_meta = new BufferMeta(size);
@@ -280,6 +281,7 @@
}
*buffer = header;
+ *buffer_data = header->pBuffer;
addActiveBuffer(portIndex, *buffer);
diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp
index 2e23899..5b45c1c 100644
--- a/media/libstagefright/omx/tests/OMXHarness.cpp
+++ b/media/libstagefright/omx/tests/OMXHarness.cpp
@@ -586,14 +586,14 @@
double r = uniform_rand();
- if (r < 0.5) {
+ if (i > 0 && r < 0.5) {
// 50% chance of just continuing to decode from last position.
requestedSeekTimeUs = -1;
LOGI("requesting linear read");
} else {
- if (r < 0.55) {
+ if (i > 0 && r < 0.55) {
// 5% chance of seeking beyond end of stream.
requestedSeekTimeUs = durationUs;
diff --git a/media/sdutils/sdutil.cpp b/media/sdutils/sdutil.cpp
index a30e59c..322f743 100644
--- a/media/sdutils/sdutil.cpp
+++ b/media/sdutils/sdutil.cpp
@@ -129,6 +129,11 @@
return 0;
}
+static void asec_unmount(const char *id) {
+ String16 sId(id);
+ gMountService->unmountSecureContainer(sId);
+}
+
static int asec_path(const char *id) {
String16 sId(id);
gMountService->getSecureContainerPath(sId);
@@ -208,6 +213,9 @@
return android::asec_destroy(id);
} else if (!strcmp(argument, "mount")) {
return android::asec_mount(id, argv[4], atoi(argv[5]));
+ } else if (!strcmp(argument, "unmount")) {
+ android::asec_unmount(id);
+ return 0;
} else if (!strcmp(argument, "path")) {
return android::asec_path(id);
}
@@ -224,6 +232,7 @@
" sdutil asec finalize <id>\n"
" sdutil asec destroy <id>\n"
" sdutil asec mount <id> <key> <ownerUid>\n"
+ " sdutil asec unmount <id>\n"
" sdutil asec path <id>\n"
);
return -1;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java
index ca60e8c..95dbb97 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaMetadataTest.java
@@ -250,7 +250,11 @@
value = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
Log.v(TAG, "Expected = " + meta_data_file[fileIndex][meta.DURATION.ordinal()] + "reult = " + value);
- assertEquals(TAG, meta_data_file[fileIndex][meta.DURATION.ordinal()], value);
+ // Only require that the returned duration is within 100ms of the expected
+ // one as PV and stagefright differ slightly in their implementation.
+ assertTrue(TAG, Math.abs(Integer.parseInt(
+ meta_data_file[fileIndex][meta.DURATION.ordinal()])
+ - Integer.parseInt(value)) < 100);
//METADATA_KEY_NUM_TRACKS should return the total number of tracks in the media
//include the video and audio
diff --git a/media/tests/omxjpegdecoder/Android.mk b/media/tests/omxjpegdecoder/Android.mk
index f679f19..b7c18bc 100644
--- a/media/tests/omxjpegdecoder/Android.mk
+++ b/media/tests/omxjpegdecoder/Android.mk
@@ -33,7 +33,8 @@
libskia \
libstagefright \
libbinder \
- libutils
+ libutils \
+ libjpeg
LOCAL_C_INCLUDES := \
$(JNI_H_INCLUDE) \
diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java
index 471625e..a186e58 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -157,6 +157,7 @@
*
* @see #getRenderMode()
* @see #setRenderMode(int)
+ * @see #requestRender()
*/
public final static int RENDERMODE_WHEN_DIRTY = 0;
/**
@@ -165,7 +166,6 @@
*
* @see #getRenderMode()
* @see #setRenderMode(int)
- * @see #requestRender()
*/
public final static int RENDERMODE_CONTINUOUSLY = 1;
@@ -210,6 +210,9 @@
// underlying surface is created and destroyed
SurfaceHolder holder = getHolder();
holder.addCallback(this);
+ // setType is not needed for SDK 2.0 or newer. Uncomment this
+ // statement if back-porting this code to older SDKs.
+ // holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
}
/**
@@ -579,7 +582,7 @@
* Called when the surface is created or recreated.
* <p>
* Called when the rendering thread
- * starts and whenever the EGL context is lost. The context will typically
+ * starts and whenever the EGL context is lost. The EGL context will typically
* be lost when the Android device awakes after going to sleep.
* <p>
* Since this method is called at the beginning of rendering, as well as
@@ -871,7 +874,7 @@
* Initialize EGL for a given configuration spec.
* @param configSpec
*/
- public void start(){
+ public void start() {
/*
* Get an EGL instance
*/
@@ -896,8 +899,8 @@
mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
/*
- * Create an OpenGL ES context. This must be done only once, an
- * OpenGL context is a somewhat heavy object.
+ * Create an EGL context. We want to do this as rarely as we can, because an
+ * EGL context is a somewhat heavy object.
*/
mEglContext = mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
@@ -1010,6 +1013,7 @@
EGLSurface mEglSurface;
EGLConfig mEglConfig;
EGLContext mEglContext;
+
}
/**
@@ -1031,6 +1035,7 @@
mRenderer = renderer;
}
+
@Override
public void run() {
setName("GLThread " + getId());
@@ -1051,20 +1056,33 @@
* This private method should only be called inside a
* synchronized(sGLThreadManager) block.
*/
- private void stopEglLocked() {
- if (mHaveEgl) {
- mHaveEgl = false;
+ private void stopEglSurfaceLocked() {
+ if (mHaveEglSurface) {
+ mHaveEglSurface = false;
mEglHelper.destroySurface();
- mEglHelper.finish();
- sGLThreadManager.releaseEglSurfaceLocked(this);
}
}
+ /*
+ * This private method should only be called inside a
+ * synchronized(sGLThreadManager) block.
+ */
+ private void stopEglContextLocked() {
+ if (mHaveEglContext) {
+ mEglHelper.finish();
+ mHaveEglContext = false;
+ sGLThreadManager.releaseEglContextLocked(this);
+ }
+ }
private void guardedRun() throws InterruptedException {
mEglHelper = new EglHelper();
+ mHaveEglContext = false;
+ mHaveEglSurface = false;
try {
GL10 gl = null;
+ boolean createEglContext = false;
boolean createEglSurface = false;
+ boolean lostEglContext = false;
boolean sizeChanged = false;
boolean wantRenderNotification = false;
boolean doRenderNotification = false;
@@ -1084,12 +1102,25 @@
break;
}
+ // Have we lost the EGL context?
+ if (lostEglContext) {
+ stopEglSurfaceLocked();
+ stopEglContextLocked();
+ lostEglContext = false;
+ }
+
// Do we need to release the EGL surface?
- if (mHaveEgl && mPaused) {
+ if (mHaveEglSurface && mPaused) {
if (LOG_SURFACE) {
Log.i("GLThread", "releasing EGL surface because paused tid=" + getId());
}
- stopEglLocked();
+ stopEglSurfaceLocked();
+ if (sGLThreadManager.shouldReleaseEGLContextWhenPausing()) {
+ stopEglContextLocked();
+ if (LOG_SURFACE) {
+ Log.i("GLThread", "releasing EGL context because paused tid=" + getId());
+ }
+ }
}
// Have we lost the surface view surface?
@@ -1097,8 +1128,8 @@
if (LOG_SURFACE) {
Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId());
}
- if (mHaveEgl) {
- stopEglLocked();
+ if (mHaveEglSurface) {
+ stopEglSurfaceLocked();
}
mWaitingForSurface = true;
sGLThreadManager.notifyAll();
@@ -1125,16 +1156,22 @@
&& (mWidth > 0) && (mHeight > 0)
&& (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY))) {
- // If we don't have an egl surface, try to acquire one.
- if ((! mHaveEgl) && sGLThreadManager.tryAcquireEglSurfaceLocked(this)) {
- mHaveEgl = true;
+ // If we don't have an EGL context, try to acquire one.
+ if ((! mHaveEglContext) && sGLThreadManager.tryAcquireEglContextLocked(this)) {
+ mHaveEglContext = true;
+ createEglContext = true;
mEglHelper.start();
- createEglSurface = true;
- sizeChanged = true;
+
sGLThreadManager.notifyAll();
}
- if (mHaveEgl) {
+ if (mHaveEglContext && !mHaveEglSurface) {
+ mHaveEglSurface = true;
+ createEglSurface = true;
+ sizeChanged = true;
+ }
+
+ if (mHaveEglSurface) {
if (mSizeChanged) {
sizeChanged = true;
w = mWidth;
@@ -1177,10 +1214,14 @@
if (LOG_RENDERER) {
Log.w("GLThread", "onSurfaceCreated");
}
- mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
createEglSurface = false;
}
+ if (createEglContext) {
+ mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
+ createEglContext = false;
+ }
+
if (sizeChanged) {
if (LOG_RENDERER) {
Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")");
@@ -1193,22 +1234,25 @@
Log.w("GLThread", "onDrawFrame");
}
mRenderer.onDrawFrame(gl);
- if(!mEglHelper.swap()) {
+ if (!mEglHelper.swap()) {
if (LOG_SURFACE) {
- Log.i("GLThread", "egl surface lost tid=" + getId());
+ Log.i("GLThread", "egl context lost tid=" + getId());
}
+ lostEglContext = true;
}
if (wantRenderNotification) {
doRenderNotification = true;
}
}
+
} finally {
/*
* clean-up everything...
*/
synchronized (sGLThreadManager) {
- stopEglLocked();
+ stopEglSurfaceLocked();
+ stopEglContextLocked();
}
}
}
@@ -1338,13 +1382,15 @@
private boolean mPaused;
private boolean mHasSurface;
private boolean mWaitingForSurface;
- private boolean mHaveEgl;
+ private boolean mHaveEglContext;
+ private boolean mHaveEglSurface;
private int mWidth;
private int mHeight;
private int mRenderMode;
private boolean mRequestRender;
private boolean mRenderComplete;
private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();
+
// End of member variables protected by the sGLThreadManager monitor.
private Renderer mRenderer;
@@ -1406,12 +1452,12 @@
/*
* Tries once to acquire the right to use an EGL
- * surface. Does not block. Requires that we are already
+ * context. Does not block. Requires that we are already
* in the sGLThreadManager monitor when this is called.
*
- * @return true if the right to use an EGL surface was acquired.
+ * @return true if the right to use an EGL context was acquired.
*/
- public boolean tryAcquireEglSurfaceLocked(GLThread thread) {
+ public boolean tryAcquireEglContextLocked(GLThread thread) {
if (mEglOwner == thread || mEglOwner == null) {
mEglOwner = thread;
notifyAll();
@@ -1423,17 +1469,23 @@
}
return false;
}
+
/*
- * Releases the EGL surface. Requires that we are already in the
+ * Releases the EGL context. Requires that we are already in the
* sGLThreadManager monitor when this is called.
*/
- public void releaseEglSurfaceLocked(GLThread thread) {
+ public void releaseEglContextLocked(GLThread thread) {
if (mEglOwner == thread) {
mEglOwner = null;
}
notifyAll();
}
+ public synchronized boolean shouldReleaseEGLContextWhenPausing() {
+ checkGLESVersion();
+ return mMultipleGLESContextsAllowed;
+ }
+
public synchronized void checkGLDriver(GL10 gl) {
if (! mGLESDriverCheckComplete) {
checkGLESVersion();
@@ -1457,14 +1509,12 @@
}
mGLESVersionCheckComplete = true;
}
-
}
private boolean mGLESVersionCheckComplete;
private int mGLESVersion;
private boolean mGLESDriverCheckComplete;
private boolean mMultipleGLESContextsAllowed;
- private int mGLContextCount;
private static final int kGLES_20 = 0x20000;
private static final String kMSM7K_RENDERER_PREFIX =
"Q3Dimension MSM7500 ";
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index c22c21b..d2f8ced 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -1111,10 +1111,10 @@
if (cur_c == NULL) {
// no current context
if (draw != EGL_NO_SURFACE || read != EGL_NO_SURFACE) {
- // calling eglMakeCurrent( ..., EGL_NO_CONTEXT, !=0, !=0);
- return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+ // calling eglMakeCurrent( ..., !=0, !=0, EGL_NO_CONTEXT);
+ return setError(EGL_BAD_MATCH, EGL_FALSE);
}
- // not an error, there is just not current context.
+ // not an error, there is just no current context.
return EGL_TRUE;
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index a1dca7e..979955c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -71,7 +71,7 @@
// database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
// is properly propagated through your change. Not doing so will result in a loss of user
// settings.
- private static final int DATABASE_VERSION = 46;
+ private static final int DATABASE_VERSION = 47;
private Context mContext;
@@ -580,7 +580,22 @@
upgradeVersion = 46;
}
-
+ if (upgradeVersion == 46) {
+ /*
+ * The password mode constants have changed; reset back to no
+ * password.
+ */
+ db.beginTransaction();
+ try {
+ db.execSQL("DELETE FROM system WHERE name='lockscreen.password_type';");
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ upgradeVersion = 47;
+ }
+
+
if (upgradeVersion != currentVersion) {
Log.w(TAG, "Got stuck trying to upgrade from version " + upgradeVersion
+ ", must wipe the settings provider");
@@ -608,7 +623,7 @@
if (!TextUtils.isEmpty(lockPattern)) {
// Convert lock pattern
try {
- LockPatternUtils lpu = new LockPatternUtils(mContext.getContentResolver());
+ LockPatternUtils lpu = new LockPatternUtils(mContext);
List<LockPatternView.Cell> cellPattern =
LockPatternUtils.stringToPattern(lockPattern);
lpu.saveLockPattern(cellPattern);
@@ -928,11 +943,11 @@
private void loadSecure35Settings(SQLiteStatement stmt) {
loadBooleanSetting(stmt, Settings.Secure.BACKUP_ENABLED,
R.bool.def_backup_enabled);
-
+
loadStringSetting(stmt, Settings.Secure.BACKUP_TRANSPORT,
R.string.def_backup_transport);
}
-
+
private void loadSetting(SQLiteStatement stmt, String key, Object value) {
stmt.bindString(1, key);
stmt.bindString(2, value.toString());
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 153a5ea..bc95b21 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -41,7 +41,6 @@
private AudioManager mAudioManager;
private IContentService mContentService;
private IPowerManager mPowerManager;
- private static final String[] PROVIDERS = { "gmail-ls", "calendar", "contacts" };
private boolean mSilent;
private boolean mVibrate;
diff --git a/preloaded-classes b/preloaded-classes
index ec4d74c..90bbb37 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -753,10 +753,8 @@
java.math.Multiplication
java.net.ContentHandler
java.net.InetAddress
-java.net.InetAddress$CacheElement
java.net.InetAddress$WaitReachable
java.net.JarURLConnection
-java.net.NegativeCache
java.net.NetPermission
java.net.ProxySelectorImpl
java.net.Socket$ConnectLock
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
new file mode 100644
index 0000000..36da4eb
--- /dev/null
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -0,0 +1,543 @@
+/*
+ * 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.server;
+
+import com.android.common.FastXmlSerializer;
+import com.android.internal.widget.LockPatternUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.app.DeviceAdmin;
+import android.app.DeviceAdminInfo;
+import android.app.DevicePolicyManager;
+import android.app.IDevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.RecoverySystem;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.Xml;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Implementation of the device policy APIs.
+ */
+public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ private static final String TAG = "DevicePolicyManagerService";
+
+ private final Context mContext;
+
+ IPowerManager mIPowerManager;
+
+ int mActivePasswordMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED;
+ int mActivePasswordLength = 0;
+ int mFailedPasswordAttempts = 0;
+
+ ActiveAdmin mActiveAdmin;
+
+ static class ActiveAdmin {
+ ActiveAdmin(DeviceAdminInfo _info) {
+ info = _info;
+ }
+
+ final DeviceAdminInfo info;
+ int getUid() { return info.getActivityInfo().applicationInfo.uid; }
+
+ int passwordMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED;
+ int minimumPasswordLength = 0;
+ long maximumTimeToUnlock = 0;
+ }
+
+ /**
+ * Instantiates the service.
+ */
+ public DevicePolicyManagerService(Context context) {
+ mContext = context;
+ }
+
+ private IPowerManager getIPowerManager() {
+ if (mIPowerManager == null) {
+ IBinder b = ServiceManager.getService(Context.POWER_SERVICE);
+ mIPowerManager = IPowerManager.Stub.asInterface(b);
+ }
+ return mIPowerManager;
+ }
+
+ ActiveAdmin getActiveAdminForCallerLocked(ComponentName who) throws SecurityException {
+ if (mActiveAdmin != null && mActiveAdmin.getUid() == Binder.getCallingUid()) {
+ if (who != null) {
+ if (!who.getPackageName().equals(mActiveAdmin.info.getActivityInfo().packageName)
+ || !who.getClassName().equals(mActiveAdmin.info.getActivityInfo().name)) {
+ throw new SecurityException("Current admin is not " + who);
+ }
+ }
+ return mActiveAdmin;
+ }
+ throw new SecurityException("Current admin is not owned by uid " + Binder.getCallingUid());
+ }
+
+
+ void sendAdminCommandLocked(ActiveAdmin policy, String action) {
+ Intent intent = new Intent(action);
+ intent.setComponent(policy.info.getComponent());
+ mContext.sendBroadcast(intent);
+ }
+
+ void sendAdminCommandLocked(String action) {
+ if (mActiveAdmin != null) {
+ sendAdminCommandLocked(mActiveAdmin, action);
+ }
+ }
+
+ ComponentName getActiveAdminLocked() {
+ if (mActiveAdmin != null) {
+ return mActiveAdmin.info.getComponent();
+ }
+ return null;
+ }
+
+ void removeActiveAdminLocked(ComponentName adminReceiver) {
+ ComponentName cur = getActiveAdminLocked();
+ if (cur != null && cur.equals(adminReceiver)) {
+ sendAdminCommandLocked(mActiveAdmin,
+ DeviceAdmin.ACTION_DEVICE_ADMIN_DISABLED);
+ // XXX need to wait for it to complete.
+ mActiveAdmin = null;
+ }
+ }
+
+ public DeviceAdminInfo findAdmin(ComponentName adminName) {
+ Intent resolveIntent = new Intent();
+ resolveIntent.setComponent(adminName);
+ List<ResolveInfo> infos = mContext.getPackageManager().queryBroadcastReceivers(
+ resolveIntent, PackageManager.GET_META_DATA);
+ if (infos == null || infos.size() <= 0) {
+ throw new IllegalArgumentException("Unknown admin: " + adminName);
+ }
+
+ try {
+ return new DeviceAdminInfo(mContext, infos.get(0));
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Bad device admin requested: " + adminName, e);
+ return null;
+ } catch (IOException e) {
+ Log.w(TAG, "Bad device admin requested: " + adminName, e);
+ return null;
+ }
+ }
+
+ private static JournaledFile makeJournaledFile() {
+ final String base = "/data/system/device_policies.xml";
+ return new JournaledFile(new File(base), new File(base + ".tmp"));
+ }
+
+ private void saveSettingsLocked() {
+ JournaledFile journal = makeJournaledFile();
+ FileOutputStream stream = null;
+ try {
+ stream = new FileOutputStream(journal.chooseForWrite(), false);
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, "utf-8");
+ out.startDocument(null, true);
+
+ out.startTag(null, "policies");
+
+ ActiveAdmin ap = mActiveAdmin;
+ if (ap != null) {
+ out.startTag(null, "admin");
+ out.attribute(null, "name", ap.info.getComponent().flattenToString());
+ if (ap.passwordMode != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) {
+ out.startTag(null, "password-mode");
+ out.attribute(null, "value", Integer.toString(ap.passwordMode));
+ out.endTag(null, "password-mode");
+ if (ap.minimumPasswordLength > 0) {
+ out.startTag(null, "min-password-length");
+ out.attribute(null, "value", Integer.toString(ap.minimumPasswordLength));
+ out.endTag(null, "mn-password-length");
+ }
+ }
+ if (ap.maximumTimeToUnlock != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) {
+ out.startTag(null, "max-time-to-unlock");
+ out.attribute(null, "value", Long.toString(ap.maximumTimeToUnlock));
+ out.endTag(null, "max-time-to-unlock");
+ }
+ out.endTag(null, "admin");
+ }
+ out.endTag(null, "policies");
+
+ out.endDocument();
+ stream.close();
+ journal.commit();
+ } catch (IOException e) {
+ try {
+ if (stream != null) {
+ stream.close();
+ }
+ } catch (IOException ex) {
+ // Ignore
+ }
+ journal.rollback();
+ }
+ }
+
+ private void loadSettingsLocked() {
+ JournaledFile journal = makeJournaledFile();
+ FileInputStream stream = null;
+ File file = journal.chooseForRead();
+ boolean success = false;
+ try {
+ stream = new FileInputStream(file);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+
+ int type = parser.next();
+ while (type != XmlPullParser.START_TAG) {
+ type = parser.next();
+ }
+ String tag = parser.getName();
+ if ("policies".equals(tag)) {
+ ActiveAdmin ap = null;
+ do {
+ type = parser.next();
+ if (type == XmlPullParser.START_TAG) {
+ tag = parser.getName();
+ if (ap == null) {
+ if ("admin".equals(tag)) {
+ DeviceAdminInfo dai = findAdmin(
+ ComponentName.unflattenFromString(
+ parser.getAttributeValue(null, "name")));
+ if (dai != null) {
+ ap = new ActiveAdmin(dai);
+ }
+ }
+ } else if ("password-mode".equals(tag)) {
+ ap.passwordMode = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ } else if ("min-password-length".equals(tag)) {
+ ap.minimumPasswordLength = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ } else if ("max-time-to-unlock".equals(tag)) {
+ ap.maximumTimeToUnlock = Long.parseLong(
+ parser.getAttributeValue(null, "value"));
+ }
+ } else if (type == XmlPullParser.END_TAG) {
+ tag = parser.getName();
+ if (ap != null && "admin".equals(tag)) {
+ mActiveAdmin = ap;
+ ap = null;
+ }
+ }
+ } while (type != XmlPullParser.END_DOCUMENT);
+ success = true;
+ }
+ } catch (NullPointerException e) {
+ Log.w(TAG, "failed parsing " + file + " " + e);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "failed parsing " + file + " " + e);
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "failed parsing " + file + " " + e);
+ } catch (IOException e) {
+ Log.w(TAG, "failed parsing " + file + " " + e);
+ } catch (IndexOutOfBoundsException e) {
+ Log.w(TAG, "failed parsing " + file + " " + e);
+ }
+ try {
+ if (stream != null) {
+ stream.close();
+ }
+ } catch (IOException e) {
+ // Ignore
+ }
+
+ if (!success) {
+ Log.w(TAG, "No valid start tag found in policies file");
+ }
+
+ long timeMs = getMaximumTimeToLock();
+ if (timeMs <= 0) {
+ timeMs = Integer.MAX_VALUE;
+ }
+ try {
+ getIPowerManager().setMaximumScreenOffTimeount((int)timeMs);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure talking with power manager", e);
+ }
+
+ }
+
+ public void systemReady() {
+ synchronized (this) {
+ loadSettingsLocked();
+ }
+ }
+
+ public void setActiveAdmin(ComponentName adminReceiver) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+
+ DeviceAdminInfo info = findAdmin(adminReceiver);
+ if (info == null) {
+ throw new IllegalArgumentException("Bad admin: " + adminReceiver);
+ }
+ synchronized (this) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ ComponentName cur = getActiveAdminLocked();
+ if (cur != null && cur.equals(adminReceiver)) {
+ throw new IllegalStateException("An admin is already set");
+ }
+ if (cur != null) {
+ removeActiveAdminLocked(adminReceiver);
+ }
+ mActiveAdmin = new ActiveAdmin(info);
+ saveSettingsLocked();
+ sendAdminCommandLocked(mActiveAdmin,
+ DeviceAdmin.ACTION_DEVICE_ADMIN_ENABLED);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ public ComponentName getActiveAdmin() {
+ synchronized (this) {
+ return getActiveAdminLocked();
+ }
+ }
+
+ public void removeActiveAdmin(ComponentName adminReceiver) {
+ synchronized (this) {
+ if (mActiveAdmin == null || mActiveAdmin.getUid() != Binder.getCallingUid()) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
+ removeActiveAdminLocked(adminReceiver);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ public void setPasswordMode(ComponentName who, int mode) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who);
+ if (ap.passwordMode != mode) {
+ ap.passwordMode = mode;
+ saveSettingsLocked();
+ }
+ }
+ }
+
+ public int getPasswordMode() {
+ synchronized (this) {
+ return mActiveAdmin != null ? mActiveAdmin.passwordMode
+ : DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED;
+ }
+ }
+
+ public void setMinimumPasswordLength(ComponentName who, int length) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who);
+ if (ap.minimumPasswordLength != length) {
+ ap.minimumPasswordLength = length;
+ saveSettingsLocked();
+ }
+ }
+ }
+
+ public int getMinimumPasswordLength() {
+ synchronized (this) {
+ return mActiveAdmin != null ? mActiveAdmin.minimumPasswordLength : 0;
+ }
+ }
+
+ public boolean isActivePasswordSufficient() {
+ synchronized (this) {
+ // This API can only be called by an active device admin,
+ // so try to retrieve it to check that the caller is one.
+ getActiveAdminForCallerLocked(null);
+ return mActivePasswordMode >= getPasswordMode()
+ && mActivePasswordLength >= getMinimumPasswordLength();
+ }
+ }
+
+ public int getCurrentFailedPasswordAttempts() {
+ synchronized (this) {
+ // This API can only be called by an active device admin,
+ // so try to retrieve it to check that the caller is one.
+ getActiveAdminForCallerLocked(null);
+ return mFailedPasswordAttempts;
+ }
+ }
+
+ public boolean resetPassword(String password) {
+ int mode;
+ synchronized (this) {
+ // This API can only be called by an active device admin,
+ // so try to retrieve it to check that the caller is one.
+ getActiveAdminForCallerLocked(null);
+ mode = getPasswordMode();
+ if (password.length() < getMinimumPasswordLength()) {
+ return false;
+ }
+ }
+
+ // Don't do this with the lock held, because it is going to call
+ // back in to the service.
+ long ident = Binder.clearCallingIdentity();
+ try {
+ LockPatternUtils utils = new LockPatternUtils(mContext);
+ utils.saveLockPassword(password, mode);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ return true;
+ }
+
+ public void setMaximumTimeToLock(ComponentName who, long timeMs) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who);
+ if (ap.maximumTimeToUnlock != timeMs) {
+ ap.maximumTimeToUnlock = timeMs;
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ saveSettingsLocked();
+ if (timeMs <= 0) {
+ timeMs = Integer.MAX_VALUE;
+ }
+ try {
+ getIPowerManager().setMaximumScreenOffTimeount((int)timeMs);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure talking with power manager", e);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+ }
+
+ public long getMaximumTimeToLock() {
+ synchronized (this) {
+ return mActiveAdmin != null ? mActiveAdmin.maximumTimeToUnlock : 0;
+ }
+ }
+
+ public void lockNow() {
+ synchronized (this) {
+ // This API can only be called by an active device admin,
+ // so try to retrieve it to check that the caller is one.
+ getActiveAdminForCallerLocked(null);
+ // STOPSHIP need to implement.
+ }
+ }
+
+ public void wipeData(int flags) {
+ synchronized (this) {
+ // This API can only be called by an active device admin,
+ // so try to retrieve it to check that the caller is one.
+ getActiveAdminForCallerLocked(null);
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
+ RecoverySystem.rebootWipeUserData(mContext);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed requesting data wipe", e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ public void setActivePasswordState(int mode, int length) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+
+ synchronized (this) {
+ if (mActivePasswordMode != mode || mActivePasswordLength != length
+ || mFailedPasswordAttempts != 0) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mActivePasswordMode = mode;
+ mActivePasswordLength = length;
+ mFailedPasswordAttempts = 0;
+ sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_CHANGED);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+ }
+
+ public void reportFailedPasswordAttempt() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+
+ synchronized (this) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mFailedPasswordAttempts++;
+ sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_FAILED);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ public void reportSuccessfulPasswordAttempt() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+
+ synchronized (this) {
+ if (mFailedPasswordAttempts != 0) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mFailedPasswordAttempts = 0;
+ sendAdminCommandLocked(DeviceAdmin.ACTION_PASSWORD_SUCCEEDED);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java
index 6ea50c7..363e93e 100644
--- a/services/java/com/android/server/DockObserver.java
+++ b/services/java/com/android/server/DockObserver.java
@@ -99,7 +99,7 @@
public DockObserver(Context context, PowerManagerService pm) {
mContext = context;
mPowerManager = pm;
- mLockPatternUtils = new LockPatternUtils(context.getContentResolver());
+ mLockPatternUtils = new LockPatternUtils(context);
init(); // set initial status
startObserving(DOCK_UEVENT_MATCH);
}
diff --git a/services/java/com/android/server/LightsService.java b/services/java/com/android/server/LightsService.java
index 1937b04..9cc74e8 100644
--- a/services/java/com/android/server/LightsService.java
+++ b/services/java/com/android/server/LightsService.java
@@ -80,11 +80,16 @@
}
}
+
public void pulse() {
+ pulse(0x00ffffff, 7);
+ }
+
+ public void pulse(int color, int onMS) {
synchronized (this) {
if (mColor == 0 && !mFlashing) {
- setLightLocked(0x00ffffff, LIGHT_FLASH_HARDWARE, 7, 0, BRIGHTNESS_MODE_USER);
- mH.sendMessageDelayed(Message.obtain(mH, 1, this), 3000);
+ setLightLocked(color, LIGHT_FLASH_HARDWARE, onMS, 1000, BRIGHTNESS_MODE_USER);
+ mH.sendMessageDelayed(Message.obtain(mH, 1, this), onMS);
}
}
}
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 406897d..1c82c94 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -1203,12 +1203,10 @@
// Remove expired alerts
if (intentsToRemove != null) {
for (PendingIntent i : intentsToRemove) {
- mProximityAlerts.remove(i);
- ProximityAlert alert = mProximityAlerts.get(i);
+ ProximityAlert alert = mProximityAlerts.remove(i);
mProximitiesEntered.remove(alert);
}
}
-
}
// Note: this is called with the lock held.
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index c8a6915..0cee31d 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -28,6 +28,7 @@
import android.net.Uri;
import android.os.IMountService;
import android.os.Environment;
+import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UEventObserver;
import android.os.Handler;
@@ -127,9 +128,12 @@
private boolean mUmsConnected = false;
private boolean mUmsEnabled = false;
+ private boolean mUmsEnabling = false;
private String mLegacyState = Environment.MEDIA_REMOVED;
+ private PackageManagerService mPms;
+
/**
* Constructs a new MountService instance
*
@@ -138,6 +142,7 @@
public MountService(Context context) {
mContext = context;
+ mPms = (PackageManagerService) ServiceManager.getService("package");
// Register a BOOT_COMPLETED handler so that we can start
// our NativeDaemonConnector. We defer the startup so that we don't
// start processing events before we ought-to
@@ -332,13 +337,16 @@
String vp = Environment.getExternalStorageDirectory().getPath();
String vs = getVolumeState(vp);
+ mUmsEnabling = enable;
if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
unmountVolume(vp);
+ mUmsEnabling = false;
updateUsbMassStorageNotification(true, false);
}
setShareMethodEnabled(vp, "ums", enable);
mUmsEnabled = enable;
+ mUmsEnabling = false;
if (!enable) {
mountVolume(vp);
if (mPromptUms) {
@@ -505,6 +513,8 @@
void handlePossibleExplicitUnmountBroadcast(String path) {
if (mMounted) {
mMounted = false;
+ // Update media status on PackageManagerService to unmount packages on sdcard
+ mPms.updateExternalMediaStatus(false);
Intent intent = new Intent(Intent.ACTION_MEDIA_UNMOUNTED,
Uri.parse("file://" + path));
mContext.sendBroadcast(intent);
@@ -594,10 +604,14 @@
} else if (newState == VolumeState.NoMedia) {
// NoMedia is handled via Disk Remove events
} else if (newState == VolumeState.Idle) {
- // Don't notify if we're in BAD_REMOVAL, NOFS, or UNMOUNTABLE
+ /*
+ * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
+ * if we're in the process of enabling UMS
+ */
if (!vs.equals(Environment.MEDIA_BAD_REMOVAL) &&
!vs.equals(Environment.MEDIA_NOFS) &&
- !vs.equals(Environment.MEDIA_UNMOUNTABLE)) {
+ !vs.equals(Environment.MEDIA_UNMOUNTABLE) &&
+ !mUmsEnabling) {
notifyMediaUnmounted(mountPoint);
}
} else if (newState == VolumeState.Pending) {
@@ -731,6 +745,8 @@
updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
+ // Update media status on PackageManagerService to unmount packages on sdcard
+ mPms.updateExternalMediaStatus(false);
if (mShowSafeUnmountNotificationWhenUnmounted) {
setMediaStorageNotification(
com.android.internal.R.string.ext_media_safe_unmount_notification_title,
@@ -792,6 +808,8 @@
void notifyMediaMounted(String path, boolean readOnly) {
updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
+ // Update media status on PackageManagerService to mount packages on sdcard
+ mPms.updateExternalMediaStatus(true);
setMediaStorageNotification(0, 0, 0, false, false, null);
updateUsbMassStorageNotification(false, false);
Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED,
@@ -1006,23 +1024,7 @@
}
public String[] getSecureContainerList() throws IllegalStateException {
- ArrayList<String> rsp = mConnector.doCommand("list_asec");
-
- String[] rdata = new String[rsp.size()];
- int idx = 0;
-
- for (String line : rsp) {
- String []tok = line.split(" ");
- int code = Integer.parseInt(tok[0]);
- if (code == VoldResponseCode.AsecListResult) {
- rdata[idx++] = tok[1];
- } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
- return rdata;
- } else {
- throw new IllegalStateException(String.format("Unexpected response code %d", code));
- }
- }
- throw new IllegalStateException("Got an empty response");
+ return mConnector.doListCommand("list_asec", VoldResponseCode.AsecListResult);
}
public String createSecureContainer(String id, int sizeMb, String fstype,
@@ -1049,6 +1051,11 @@
return getSecureContainerPath(id);
}
+ public void unmountSecureContainer(String id) throws IllegalStateException {
+ String cmd = String.format("unmount_asec %s", id);
+ mConnector.doCommand(cmd);
+ }
+
public String getSecureContainerPath(String id) throws IllegalStateException {
ArrayList<String> rsp = mConnector.doCommand("asec_path " + id);
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index da3e562..98e00dc 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -201,6 +201,9 @@
}
}
+ /**
+ * Issue a command to the native daemon and return the responses
+ */
public synchronized ArrayList<String> doCommand(String cmd) throws IllegalStateException {
sendCommand(cmd);
@@ -236,4 +239,38 @@
}
return response;
}
+
+ /*
+ * Issues a list command and returns the cooked list
+ */
+ public String[] doListCommand(String cmd, int expectedResponseCode)
+ throws IllegalStateException {
+
+ ArrayList<String> rsp = doCommand(cmd);
+ String[] rdata = new String[rsp.size()-1];
+ int idx = 0;
+
+ for (String line : rsp) {
+ try {
+ String[] tok = line.split(" ");
+ int code = Integer.parseInt(tok[0]);
+ if (code == expectedResponseCode) {
+ if (tok.length !=2) {
+ throw new IllegalStateException(
+ String.format("Malformatted list entry '%s'", line));
+ }
+ rdata[idx++] = tok[1];
+ } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
+ return rdata;
+ } else {
+ throw new IllegalStateException(
+ String.format("Expected list response %d, but got %d",
+ expectedResponseCode, code));
+ }
+ } catch (NumberFormatException nfe) {
+ throw new IllegalStateException(String.format("Error reading code '%s'", line));
+ }
+ }
+ throw new IllegalStateException("Got an empty response");
+ }
}
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
new file mode 100644
index 0000000..8594e44
--- /dev/null
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.net.InterfaceConfiguration;
+import android.net.INetworkManagementEventObserver;
+import android.os.INetworkManagementService;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+import android.provider.Settings;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+
+import java.io.File;
+import java.io.FileReader;
+import java.lang.IllegalStateException;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * @hide
+ */
+class NetworkManagementService extends INetworkManagementService.Stub {
+
+ private static final String TAG = "NetworkManagmentService";
+
+ class NetdResponseCode {
+ public static final int InterfaceListResult = 110;
+ public static final int TetherInterfaceListResult = 111;
+ public static final int TetherDnsFwdTgtListResult = 112;
+ public static final int TtyListResult = 113;
+
+ public static final int TetherStatusResult = 210;
+ public static final int IpFwdStatusResult = 211;
+ public static final int InterfaceGetCfgResult = 213;
+ }
+
+ /**
+ * Binder context for this service
+ */
+ private Context mContext;
+
+ /**
+ * connector object for communicating with netd
+ */
+ private NativeDaemonConnector mConnector;
+
+ private ArrayList<INetworkManagementEventObserver> mObservers;
+
+ /**
+ * Constructs a new NetworkManagementService instance
+ *
+ * @param context Binder context for this service
+ */
+ private NetworkManagementService(Context context) {
+ mContext = context;
+
+ mObservers = new ArrayList<INetworkManagementEventObserver>();
+
+ mConnector = new NativeDaemonConnector(
+ new NetdCallbackReceiver(), "netd", 10, "NetdConnector");
+ Thread thread = new Thread(mConnector, NativeDaemonConnector.class.getName());
+ thread.start();
+ }
+
+ public void registerObserver(INetworkManagementEventObserver obs) {
+ Log.d(TAG, "Registering observer");
+ mObservers.add(obs);
+ }
+
+ public void unregisterObserver(INetworkManagementEventObserver obs) {
+ Log.d(TAG, "Unregistering observer");
+ mObservers.remove(mObservers.indexOf(obs));
+ }
+
+ /**
+ * Notify our observers of an interface link status change
+ */
+ private void notifyInterfaceLinkStatusChanged(String iface, boolean link) {
+ for (INetworkManagementEventObserver obs : mObservers) {
+ try {
+ obs.interfaceLinkStatusChanged(iface, link);
+ } catch (Exception ex) {
+ Log.w(TAG, "Observer notifier failed", ex);
+ }
+ }
+ }
+
+ /**
+ * Notify our observers of an interface addition.
+ */
+ private void notifyInterfaceAdded(String iface) {
+ for (INetworkManagementEventObserver obs : mObservers) {
+ try {
+ obs.interfaceAdded(iface);
+ } catch (Exception ex) {
+ Log.w(TAG, "Observer notifier failed", ex);
+ }
+ }
+ }
+
+ /**
+ * Notify our observers of an interface removal.
+ */
+ private void notifyInterfaceRemoved(String iface) {
+ for (INetworkManagementEventObserver obs : mObservers) {
+ try {
+ obs.interfaceRemoved(iface);
+ } catch (Exception ex) {
+ Log.w(TAG, "Observer notifier failed", ex);
+ }
+ }
+ }
+
+
+ //
+ // Netd Callback handling
+ //
+
+ class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks {
+ public void onDaemonConnected() {
+ new Thread() {
+ public void run() {
+ // XXX: Run some tests
+ }
+ }.start();
+ }
+ public boolean onEvent(int code, String raw, String[] cooked) {
+ return false;
+ }
+ }
+
+ private static int stringToIpAddr(String addrString) throws UnknownHostException {
+ try {
+ String[] parts = addrString.split("\\.");
+ if (parts.length != 4) {
+ throw new UnknownHostException(addrString);
+ }
+
+ int a = Integer.parseInt(parts[0]) ;
+ int b = Integer.parseInt(parts[1]) << 8;
+ int c = Integer.parseInt(parts[2]) << 16;
+ int d = Integer.parseInt(parts[3]) << 24;
+
+ return a | b | c | d;
+ } catch (NumberFormatException ex) {
+ throw new UnknownHostException(addrString);
+ }
+ }
+
+ public static String intToIpString(int i) {
+ return ((i >> 24 ) & 0xFF) + "." + ((i >> 16 ) & 0xFF) + "." + ((i >> 8 ) & 0xFF) + "." +
+ (i & 0xFF);
+ }
+
+ //
+ // INetworkManagementService members
+ //
+
+ public String[] listInterfaces() throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
+
+ return mConnector.doListCommand("interface list", NetdResponseCode.InterfaceListResult);
+ }
+
+ public InterfaceConfiguration getInterfaceConfig(String iface) throws IllegalStateException {
+ String rsp = mConnector.doCommand("interface getcfg " + iface).get(0);
+ Log.d(TAG, String.format("rsp <%s>", rsp));
+
+ // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz.zzz.zzz.zzz [flag1 flag2 flag3]
+ StringTokenizer st = new StringTokenizer(rsp);
+
+ try {
+ int code = Integer.parseInt(st.nextToken(" "));
+ if (code != NetdResponseCode.InterfaceGetCfgResult) {
+ throw new IllegalStateException(
+ String.format("Expected code %d, but got %d",
+ NetdResponseCode.InterfaceGetCfgResult, code));
+ }
+ } catch (NumberFormatException nfe) {
+ throw new IllegalStateException(
+ String.format("Invalid response from daemon (%s)", rsp));
+ }
+
+ InterfaceConfiguration cfg = new InterfaceConfiguration();
+ cfg.hwAddr = st.nextToken(" ");
+ try {
+ cfg.ipAddr = stringToIpAddr(st.nextToken(" "));
+ } catch (UnknownHostException uhe) {
+ Log.e(TAG, "Failed to parse ipaddr", uhe);
+ cfg.ipAddr = 0;
+ }
+
+ try {
+ cfg.netmask = stringToIpAddr(st.nextToken(" "));
+ } catch (UnknownHostException uhe) {
+ Log.e(TAG, "Failed to parse netmask", uhe);
+ cfg.netmask = 0;
+ }
+ cfg.interfaceFlags = st.nextToken("]");
+ Log.d(TAG, String.format("flags <%s>", cfg.interfaceFlags));
+ return cfg;
+ }
+
+ public void setInterfaceConfig(
+ String iface, InterfaceConfiguration cfg) throws IllegalStateException {
+ String cmd = String.format("interface setcfg %s %s %s", iface,
+ intToIpString(cfg.ipAddr), intToIpString(cfg.netmask), cfg.interfaceFlags);
+ mConnector.doCommand(cmd);
+ }
+
+ public void shutdown() {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.SHUTDOWN)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires SHUTDOWN permission");
+ }
+
+ Log.d(TAG, "Shutting down");
+ }
+
+ public boolean getIpForwardingEnabled() throws IllegalStateException{
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
+
+ ArrayList<String> rsp = mConnector.doCommand("ipfwd status");
+
+ for (String line : rsp) {
+ String []tok = line.split(" ");
+ int code = Integer.parseInt(tok[0]);
+ if (code == NetdResponseCode.IpFwdStatusResult) {
+ // 211 Forwarding <enabled/disabled>
+ if (tok.length !=2) {
+ throw new IllegalStateException(
+ String.format("Malformatted list entry '%s'", line));
+ }
+ if (tok[2].equals("enabled"))
+ return true;
+ return false;
+ } else {
+ throw new IllegalStateException(String.format("Unexpected response code %d", code));
+ }
+ }
+ throw new IllegalStateException("Got an empty response");
+ }
+
+ public void setIpForwardingEnabled(boolean enable) throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ mConnector.doCommand(String.format("ipfwd %sable", (enable ? "en" : "dis")));
+ }
+
+ public void startTethering(String dhcpRangeStart, String dhcpRangeEnd)
+ throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ mConnector.doCommand(String.format("tether start %s %s", dhcpRangeStart, dhcpRangeEnd));
+ }
+
+ public void stopTethering() throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ mConnector.doCommand("tether stop");
+ }
+
+ public boolean isTetheringStarted() throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
+
+ ArrayList<String> rsp = mConnector.doCommand("tether status");
+
+ for (String line : rsp) {
+ String []tok = line.split(" ");
+ int code = Integer.parseInt(tok[0]);
+ if (code == NetdResponseCode.TetherStatusResult) {
+ // XXX: Tethering services <started/stopped> <TBD>...
+ if (tok[2].equals("started"))
+ return true;
+ return false;
+ } else {
+ throw new IllegalStateException(String.format("Unexpected response code %d", code));
+ }
+ }
+ throw new IllegalStateException("Got an empty response");
+ }
+
+ public void tetherInterface(String iface) throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ mConnector.doCommand("tether interface add " + iface);
+ }
+
+ public void untetherInterface(String iface) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ mConnector.doCommand("tether interface remove " + iface);
+ }
+
+ public String[] listTetheredInterfaces() throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
+ return mConnector.doListCommand(
+ "tether interface list", NetdResponseCode.TetherInterfaceListResult);
+ }
+
+ public void setDnsForwarders(String[] dns) throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ try {
+ String cmd = "tether dns set ";
+ for (String s : dns) {
+ cmd += InetAddress.getByName(s).toString() + " ";
+ }
+ mConnector.doCommand(cmd);
+ } catch (UnknownHostException e) {
+ throw new IllegalStateException("Error resolving dns name", e);
+ }
+ }
+
+ public String[] getDnsForwarders() throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
+ return mConnector.doListCommand(
+ "tether dns list", NetdResponseCode.TetherDnsFwdTgtListResult);
+ }
+
+ public void enableNat(String internalInterface, String externalInterface)
+ throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ mConnector.doCommand(
+ String.format("nat enable %s %s", internalInterface, externalInterface));
+ }
+
+ public void disableNat(String internalInterface, String externalInterface)
+ throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ mConnector.doCommand(
+ String.format("nat disable %s %s", internalInterface, externalInterface));
+ }
+
+ public String[] listTtys() throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
+ return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult);
+ }
+
+ public void attachPppd(String tty, String localAddr, String remoteAddr)
+ throws IllegalStateException {
+ try {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ mConnector.doCommand(String.format("pppd attach %s %s %s", tty,
+ InetAddress.getByName(localAddr).toString(),
+ InetAddress.getByName(localAddr).toString()));
+ } catch (UnknownHostException e) {
+ throw new IllegalStateException("Error resolving addr", e);
+ }
+ }
+
+ public void detachPppd(String tty) throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+ mConnector.doCommand(String.format("pppd detach %s", tty));
+ }
+}
+
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 436a60e..fc89ec8 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -91,6 +91,10 @@
private LightsService.Light mNotificationLight;
private LightsService.Light mAttentionLight;
+ private int mDefaultNotificationColor;
+ private int mDefaultNotificationLedOn;
+ private int mDefaultNotificationLedOff;
+
private NotificationRecord mSoundNotification;
private AsyncPlayer mSound;
private boolean mSystemReady;
@@ -398,6 +402,14 @@
mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS);
mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
+ Resources resources = mContext.getResources();
+ mDefaultNotificationColor = resources.getColor(
+ com.android.internal.R.color.config_defaultNotificationColor);
+ mDefaultNotificationLedOn = resources.getInteger(
+ com.android.internal.R.integer.config_defaultNotificationLedOn);
+ mDefaultNotificationLedOff = resources.getInteger(
+ com.android.internal.R.integer.config_defaultNotificationLedOff);
+
// Don't start allowing notifications until the setup wizard has run once.
// After that, including subsequent boots, init with notifications turned on.
// This works on the first boot because the setup wizard will toggle this
@@ -1024,14 +1036,25 @@
}
// we only flash if screen is off and persistent pulsing is enabled
- if (mLedNotification == null || mScreenOn || !mNotificationPulseEnabled) {
+ if (mLedNotification == null || mScreenOn) {
mNotificationLight.turnOff();
} else {
- mNotificationLight.setFlashing(
- mLedNotification.notification.ledARGB,
- LightsService.LIGHT_FLASH_TIMED,
- mLedNotification.notification.ledOnMS,
- mLedNotification.notification.ledOffMS);
+ int ledARGB = mLedNotification.notification.ledARGB;
+ int ledOnMS = mLedNotification.notification.ledOnMS;
+ int ledOffMS = mLedNotification.notification.ledOffMS;
+ if ((mLedNotification.notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
+ ledARGB = mDefaultNotificationColor;
+ ledOnMS = mDefaultNotificationLedOn;
+ ledOffMS = mDefaultNotificationLedOff;
+ }
+ if (mNotificationPulseEnabled) {
+ // pulse repeatedly
+ mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED,
+ ledOnMS, ledOffMS);
+ } else {
+ // pulse only once
+ mNotificationLight.pulse(ledARGB, ledOnMS);
+ }
}
}
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 1b6a56a..cafc804 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -4753,13 +4753,18 @@
ret = deleteInstalledPackageLI (p, deleteCodeAndResources, flags, outInfo);
}
if (ret && onSd) {
- // Post a delayed destroy on the container since there might
- // be active processes holding open file handles to package
- // resources which will get killed by the process killer when
- // destroying the container. This might even kill the current
- // process and crash the system. Delay the destroy a bit so
- // that the active processes get to handle the uninstall broadcasts.
- sendDelayedDestroySdDir(packageName);
+ if (deleteCodeAndResources) {
+ // Post a delayed destroy on the container since there might
+ // be active processes holding open file handles to package
+ // resources which will get killed by the process killer when
+ // destroying the container. This might even kill the current
+ // process and crash the system. Delay the destroy a bit so
+ // that the active processes get to handle the uninstall broadcasts.
+ sendDelayedDestroySdDir(packageName);
+ } else {
+ // Just unmount the directory
+ unMountSdDir(packageName);
+ }
}
return ret;
}
@@ -5866,7 +5871,9 @@
HashSet<String> loadedPermissions = new HashSet<String>();
GrantedPermissions(int pkgFlags) {
- this.pkgFlags = pkgFlags & ApplicationInfo.FLAG_SYSTEM;
+ this.pkgFlags = (pkgFlags & ApplicationInfo.FLAG_SYSTEM) |
+ (pkgFlags & ApplicationInfo.FLAG_FORWARD_LOCK) |
+ (pkgFlags & ApplicationInfo.FLAG_ON_SDCARD);
}
}
@@ -6672,9 +6679,8 @@
if (!pkg.resourcePathString.equals(pkg.codePathString)) {
serializer.attribute(null, "resourcePath", pkg.resourcePathString);
}
- serializer.attribute(null, "system",
- (pkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0
- ? "true" : "false");
+ serializer.attribute(null, "flags",
+ Integer.toString(pkg.pkgFlags));
serializer.attribute(null, "ts", pkg.getTimeStampStr());
serializer.attribute(null, "version", String.valueOf(pkg.versionCode));
if (pkg.sharedUser == null) {
@@ -7065,16 +7071,24 @@
} catch (NumberFormatException e) {
}
}
- systemStr = parser.getAttributeValue(null, "system");
installerPackageName = parser.getAttributeValue(null, "installer");
+
+ systemStr = parser.getAttributeValue(null, "flags");
if (systemStr != null) {
- if ("true".equals(systemStr)) {
- pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
+ try {
+ pkgFlags = Integer.parseInt(systemStr);
+ } catch (NumberFormatException e) {
}
} else {
- // Old settings that don't specify system... just treat
- // them as system, good enough.
- pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
+ // For backward compatibility
+ systemStr = parser.getAttributeValue(null, "system");
+ if (systemStr != null) {
+ pkgFlags |= ("true".equalsIgnoreCase(systemStr)) ? ApplicationInfo.FLAG_SYSTEM : 0;
+ } else {
+ // Old settings that don't specify system... just treat
+ // them as system, good enough.
+ pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
+ }
}
timeStampStr = parser.getAttributeValue(null, "ts");
if (timeStampStr != null) {
@@ -7443,6 +7457,7 @@
static final boolean DEBUG_SD_INSTALL = false;
final private String mSdEncryptKey = "AppsOnSD";
final private String mSdEncryptAlg = "AES";
+ private boolean mMediaMounted = false;
private MountService getMountService() {
return (MountService) ServiceManager.getService("mount");
@@ -7518,6 +7533,11 @@
return null;
}
+ private boolean unMountSdDir(String pkgName) {
+ // STOPSHIP unmount directory
+ return true;
+ }
+
private String getSdDir(String pkgName) {
String cachePath = null;
try {
@@ -7553,6 +7573,15 @@
}
}
+ private String[] getSecureContainerList() {
+ try {
+ return getMountService().getSecureContainerList();
+ } catch (IllegalStateException e) {
+ Log.i(TAG, "Failed to getSecureContainerList");
+ }
+ return null;
+ }
+
private void sendDelayedDestroySdDir(String pkgName) {
if (mHandler.hasMessages(DESTROY_SD_CONTAINER, pkgName)) {
// Don't have to send message again
@@ -7562,7 +7591,63 @@
mHandler.sendMessageDelayed(msg, DESTROY_SD_CONTAINER_DELAY);
}
- public void updateExternalMediaStatus(boolean mediaStatus) {
- // TODO
+ public void updateExternalMediaStatus(final boolean mediaStatus) {
+ if (mediaStatus == mMediaMounted) {
+ return;
+ }
+ mMediaMounted = mediaStatus;
+ // Queue up an async operation since the package installation may take a little while.
+ mHandler.post(new Runnable() {
+ public void run() {
+ mHandler.removeCallbacks(this);
+ final String list[] = getSecureContainerList();
+ if (list == null || list.length == 0) {
+ return;
+ }
+ for (int i = 0; i < list.length; i++) {
+ String mountPkg = list[i];
+ // TODO compare with default package
+ synchronized (mPackages) {
+ PackageSetting ps = mSettings.mPackages.get(mountPkg);
+ if (ps != null && (ps.pkgFlags & ApplicationInfo.FLAG_ON_SDCARD) != 0) {
+ if (mediaStatus) {
+ String pkgPath = getSdDir(mountPkg);
+ if (pkgPath == null) {
+ continue;
+ }
+ pkgPath = ps.codePathString;
+ int parseFlags = PackageParser.PARSE_CHATTY |
+ PackageParser.PARSE_ON_SDCARD | mDefParseFlags;
+ PackageParser pp = new PackageParser(pkgPath);
+ pp.setSeparateProcesses(mSeparateProcesses);
+ final PackageParser.Package pkg = pp.parsePackage(new File(pkgPath),
+ null, mMetrics, parseFlags);
+ if (pkg == null) {
+ Log.w(TAG, "Failed to install package : " + mountPkg + " from sd card");
+ continue;
+ }
+ int scanMode = SCAN_MONITOR;
+ // Scan the package
+ if (scanPackageLI(pkg, parseFlags, scanMode) != null) {
+ // Grant permissions
+ grantPermissionsLP(pkg, false);
+ // Persist settings
+ mSettings.writeLP();
+ } else {
+ Log.i(TAG, "Failed to install package: " + mountPkg + " from sdcard");
+ }
+ } else {
+ // Delete package
+ PackageRemovedInfo outInfo = new PackageRemovedInfo();
+ boolean res = deletePackageLI(mountPkg, false, PackageManager.DONT_DELETE_DATA, outInfo);
+ if (!res) {
+ Log.e(TAG, "Failed to delete pkg from sdcard : " + mountPkg);
+ }
+ }
+ }
+ }
+ }
+ }
+ });
}
}
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index bf6996c..f106fc3 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -169,7 +169,8 @@
private boolean mProximitySensorActive = false;
private int mProximityPendingValue = -1; // -1 == nothing, 0 == inactive, 1 == active
private long mLastProximityEventTime;
- private int mTotalDelaySetting;
+ private int mScreenOffTimeoutSetting;
+ private int mMaximumScreenOffTimeout = Integer.MAX_VALUE;
private int mKeylightDelay;
private int mDimDelay;
private int mScreenOffDelay;
@@ -378,6 +379,16 @@
Settings.System.STAY_ON_WHILE_PLUGGED_IN, val);
}
+ public void setMaximumScreenOffTimeount(int timeMs) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.WRITE_SECURE_SETTINGS, null);
+ synchronized (mLocks) {
+ mMaximumScreenOffTimeout = timeMs;
+ // recalculate everything
+ setScreenOffTimeoutsLocked();
+ }
+ }
+
private class SettingsObserver implements Observer {
private int getInt(String name) {
return mSettings.getValues(name).getAsInteger(Settings.System.VALUE);
@@ -390,7 +401,7 @@
updateWakeLockLocked();
// SCREEN_OFF_TIMEOUT
- mTotalDelaySetting = getInt(SCREEN_OFF_TIMEOUT);
+ mScreenOffTimeoutSetting = getInt(SCREEN_OFF_TIMEOUT);
// DIM_SCREEN
//mDimScreen = getInt(DIM_SCREEN) != 0;
@@ -935,7 +946,8 @@
pw.println(" mPreventScreenOn=" + mPreventScreenOn
+ " mScreenBrightnessOverride=" + mScreenBrightnessOverride
+ " mButtonBrightnessOverride=" + mButtonBrightnessOverride);
- pw.println(" mTotalDelaySetting=" + mTotalDelaySetting);
+ pw.println(" mScreenOffTimeoutSetting=" + mScreenOffTimeoutSetting
+ + " mMaximumScreenOffTimeout=" + mMaximumScreenOffTimeout);
pw.println(" mLastScreenOnTime=" + mLastScreenOnTime);
pw.println(" mBroadcastWakeLock=" + mBroadcastWakeLock);
pw.println(" mStayOnWhilePluggedInScreenDimLock=" + mStayOnWhilePluggedInScreenDimLock);
@@ -2285,7 +2297,10 @@
mDimDelay = -1;
mScreenOffDelay = 0;
} else {
- int totalDelay = mTotalDelaySetting;
+ int totalDelay = mScreenOffTimeoutSetting;
+ if (totalDelay > mMaximumScreenOffTimeout) {
+ totalDelay = mMaximumScreenOffTimeout;
+ }
mKeylightDelay = LONG_KEYLIGHT_DELAY;
if (totalDelay < 0) {
mScreenOffDelay = Integer.MAX_VALUE;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 674ade9..6b3f433 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -201,6 +201,7 @@
Log.e("System", "Failure starting core service", e);
}
+ DevicePolicyManagerService devicePolicy = null;
StatusBarService statusBar = null;
InputMethodManagerService imm = null;
AppWidgetService appWidget = null;
@@ -209,16 +210,25 @@
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
try {
+ Log.i(TAG, "Device Policy");
+ devicePolicy = new DevicePolicyManagerService(context);
+ ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, devicePolicy);
+ } catch (Throwable e) {
+ Log.e(TAG, "Failure starting DevicePolicyService", e);
+ }
+
+ try {
Log.i(TAG, "Status Bar");
statusBar = new StatusBarService(context);
- ServiceManager.addService("statusbar", statusBar);
+ ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
} catch (Throwable e) {
Log.e(TAG, "Failure starting StatusBarService", e);
}
try {
Log.i(TAG, "Clipboard Service");
- ServiceManager.addService("clipboard", new ClipboardService(context));
+ ServiceManager.addService(Context.CLIPBOARD_SERVICE,
+ new ClipboardService(context));
} catch (Throwable e) {
Log.e(TAG, "Failure starting Clipboard Service", e);
}
@@ -280,14 +290,16 @@
try {
Log.i(TAG, "Location Manager");
- ServiceManager.addService(Context.LOCATION_SERVICE, new LocationManagerService(context));
+ ServiceManager.addService(Context.LOCATION_SERVICE,
+ new LocationManagerService(context));
} catch (Throwable e) {
Log.e(TAG, "Failure starting Location Manager", e);
}
try {
Log.i(TAG, "Search Service");
- ServiceManager.addService( Context.SEARCH_SERVICE, new SearchManagerService(context) );
+ ServiceManager.addService(Context.SEARCH_SERVICE,
+ new SearchManagerService(context));
} catch (Throwable e) {
Log.e(TAG, "Failure starting Search Service", e);
}
@@ -351,7 +363,8 @@
try {
Log.i(TAG, "Backup Service");
- ServiceManager.addService(Context.BACKUP_SERVICE, new BackupManagerService(context));
+ ServiceManager.addService(Context.BACKUP_SERVICE,
+ new BackupManagerService(context));
} catch (Throwable e) {
Log.e(TAG, "Failure starting Backup Service", e);
}
@@ -391,6 +404,10 @@
// It is now time to start up the app processes...
+ if (devicePolicy != null) {
+ devicePolicy.systemReady();
+ }
+
if (notification != null) {
notification.systemReady();
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 40d194c..44b6624 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -4610,7 +4610,8 @@
}
// Log the ANR to the event log.
- EventLog.writeEvent(EventLogTags.ANR, app.pid, app.processName, annotation);
+ EventLog.writeEvent(EventLogTags.AM_ANR, app.pid, app.processName, app.info.flags,
+ annotation);
// Dump thread traces as quickly as we can, starting with "interesting" processes.
ArrayList<Integer> pids = new ArrayList<Integer>(20);
@@ -8381,7 +8382,7 @@
ActivityInfo ai = ris.get(i).activityInfo;
intent.setComponent(new ComponentName(ai.packageName, ai.name));
IIntentReceiver finisher = null;
- if (i == 0) {
+ if (i == ris.size()-1) {
finisher = new IIntentReceiver.Stub() {
public void performReceive(Intent intent, int resultCode,
String data, Bundle extras, boolean ordered,
@@ -8397,7 +8398,7 @@
Log.i(TAG, "Sending system update to: " + intent.getComponent());
broadcastIntentLocked(null, null, intent, null, finisher,
0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID);
- if (i == 0) {
+ if (finisher != null) {
mWaitingUpdate = true;
}
}
@@ -8750,6 +8751,7 @@
EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(),
app == null ? "system" : (r == null ? "unknown" : r.processName),
+ r == null ? -1 : r.info.flags,
crashInfo.exceptionClassName,
crashInfo.exceptionMessage,
crashInfo.throwFileName,
@@ -8773,6 +8775,7 @@
EventLog.writeEvent(EventLogTags.AM_WTF, Binder.getCallingPid(),
app == null ? "system" : (r == null ? "unknown" : r.processName),
+ r == null ? -1 : r.info.flags,
tag, crashInfo.exceptionMessage);
addErrorToDropBox("wtf", r, null, null, tag, null, null, crashInfo);
@@ -8842,6 +8845,7 @@
sb.append("Process: system_server\n");
} else {
sb.append("Process: ").append(process.processName).append("\n");
+ sb.append("Flags: 0x").append(Integer.toString(process.info.flags, 16)).append("\n");
}
if (activity != null) {
sb.append("Activity: ").append(activity.shortComponentName).append("\n");
@@ -8994,6 +8998,7 @@
report.installerPackageName = r.errorReportReceiver.getPackageName();
report.processName = r.processName;
report.time = timeMillis;
+ report.systemApp = (r.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
if (r.crashing) {
report.type = ApplicationErrorReport.TYPE_CRASH;
@@ -10447,7 +10452,7 @@
sInfo.applicationInfo.uid, sInfo.packageName,
sInfo.name);
}
- r = new ServiceRecord(ss, name, filter, sInfo, res);
+ r = new ServiceRecord(this, ss, name, filter, sInfo, res);
res.setService(r);
mServices.put(name, r);
mServicesByIntent.put(filter, r);
diff --git a/services/java/com/android/server/am/EventLogTags.logtags b/services/java/com/android/server/am/EventLogTags.logtags
index 952555b..0ddcc247 100644
--- a/services/java/com/android/server/am/EventLogTags.logtags
+++ b/services/java/com/android/server/am/EventLogTags.logtags
@@ -28,7 +28,7 @@
# An activity has been resumed and is now in the foreground:
30007 am_resume_activity (Token|1|5),(Task ID|1|5),(Component Name|3)
# Application Not Responding
-30008 anr (pid|1|5),(Package Name|3),(reason|3)
+30008 am_anr (pid|1|5),(Package Name|3),(Flags|1|5),(reason|3)
# Activity launch time
30009 activity_launch_time (Token|1|5),(Component Name|3),(time|2|3)
# Application process bound to work
@@ -80,6 +80,6 @@
30037 am_process_start_timeout (PID|1|5),(UID|1|5),(Process Name|3)
# Unhandled exception
-30039 am_crash (PID|1|5),(Process Name|3),(Exception|3),(Message|3),(File|3),(Line|1|5)
+30039 am_crash (PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5)
# Log.wtf() called
-30040 am_wtf (PID|1|5),(Process Name|3),(Tag|3),(Message|3)
+30040 am_wtf (PID|1|5),(Process Name|3),(Flags|1|5),(Tag|3),(Message|3)
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 2534410..89761a8 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -40,6 +40,7 @@
* A running application service.
*/
class ServiceRecord extends Binder {
+ final ActivityManagerService ams;
final BatteryStatsImpl.Uid.Pkg.Serv stats;
final ComponentName name; // service component.
final String shortName; // name.flattenToShortString().
@@ -192,8 +193,10 @@
}
}
- ServiceRecord(BatteryStatsImpl.Uid.Pkg.Serv servStats, ComponentName name,
+ ServiceRecord(ActivityManagerService ams,
+ BatteryStatsImpl.Uid.Pkg.Serv servStats, ComponentName name,
Intent.FilterComparison intent, ServiceInfo sInfo, Runnable restarter) {
+ this.ams = ams;
this.stats = servStats;
this.name = name;
shortName = name.flattenToShortString();
@@ -249,27 +252,46 @@
public void postNotification() {
if (foregroundId != 0 && foregroundNoti != null) {
- INotificationManager inm = NotificationManager.getService();
- if (inm != null) {
- try {
- int[] outId = new int[1];
- inm.enqueueNotification(packageName, foregroundId,
- foregroundNoti, outId);
- } catch (RemoteException e) {
+ // Do asynchronous communication with notification manager to
+ // avoid deadlocks.
+ final String localPackageName = packageName;
+ final int localForegroundId = foregroundId;
+ final Notification localForegroundNoti = foregroundNoti;
+ ams.mHandler.post(new Runnable() {
+ public void run() {
+ INotificationManager inm = NotificationManager.getService();
+ if (inm == null) {
+ return;
+ }
+ try {
+ int[] outId = new int[1];
+ inm.enqueueNotification(localPackageName, localForegroundId,
+ localForegroundNoti, outId);
+ } catch (RemoteException e) {
+ }
}
- }
+ });
}
}
public void cancelNotification() {
if (foregroundId != 0) {
- INotificationManager inm = NotificationManager.getService();
- if (inm != null) {
- try {
- inm.cancelNotification(packageName, foregroundId);
- } catch (RemoteException e) {
+ // Do asynchronous communication with notification manager to
+ // avoid deadlocks.
+ final String localPackageName = packageName;
+ final int localForegroundId = foregroundId;
+ ams.mHandler.post(new Runnable() {
+ public void run() {
+ INotificationManager inm = NotificationManager.getService();
+ if (inm == null) {
+ return;
+ }
+ try {
+ inm.cancelNotification(localPackageName, localForegroundId);
+ } catch (RemoteException e) {
+ }
}
- }
+ });
}
}
diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java
index 42c0254..f5aeaf0 100644
--- a/services/java/com/android/server/status/StatusBarPolicy.java
+++ b/services/java/com/android/server/status/StatusBarPolicy.java
@@ -88,6 +88,8 @@
// clock
private Calendar mCalendar;
+ private String mClockFormatString;
+ private SimpleDateFormat mClockFormat;
private IBinder mClockIcon;
private IconData mClockData;
@@ -546,37 +548,48 @@
res = R.string.twelve_hour_time_format;
}
- String format = mContext.getString(res);
-
- /*
- * Search for an unquoted "a" in the format string, so we can
- * add dummy characters around it to let us find it again after
- * formatting and change its size.
- */
- int a = -1;
- boolean quoted = false;
- for (int i = 0; i < format.length(); i++) {
- char c = format.charAt(i);
-
- if (c == '\'') {
- quoted = !quoted;
- }
-
- if (!quoted && c == 'a') {
- a = i;
- break;
- }
- }
-
final char MAGIC1 = '\uEF00';
final char MAGIC2 = '\uEF01';
- if (a >= 0) {
- format = format.substring(0, a) + MAGIC1 + "a" + MAGIC2 +
- format.substring(a + 1);
- }
+ SimpleDateFormat sdf;
+ String format = mContext.getString(res);
+ if (!format.equals(mClockFormatString)) {
+ /*
+ * Search for an unquoted "a" in the format string, so we can
+ * add dummy characters around it to let us find it again after
+ * formatting and change its size.
+ */
+ int a = -1;
+ boolean quoted = false;
+ for (int i = 0; i < format.length(); i++) {
+ char c = format.charAt(i);
- String result = new SimpleDateFormat(format).format(mCalendar.getTime());
+ if (c == '\'') {
+ quoted = !quoted;
+ }
+
+ if (!quoted && c == 'a') {
+ a = i;
+ break;
+ }
+ }
+
+ if (a >= 0) {
+ // Move a back so any whitespace before the AM/PM is also in the alternate size.
+ final int b = a;
+ while (a > 0 && Character.isWhitespace(format.charAt(a-1))) {
+ a--;
+ }
+ format = format.substring(0, a) + MAGIC1 + format.substring(a, b)
+ + "a" + MAGIC2 + format.substring(b + 1);
+ }
+
+ mClockFormat = sdf = new SimpleDateFormat(format);
+ mClockFormatString = format;
+ } else {
+ sdf = mClockFormat;
+ }
+ String result = sdf.format(mCalendar.getTime());
int magic1 = result.indexOf(MAGIC1);
int magic2 = result.indexOf(MAGIC2);
diff --git a/telephony/java/com/android/internal/telephony/TelephonyEventLog.java b/telephony/java/com/android/internal/telephony/TelephonyEventLog.java
index cdce488..0f15cb6 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyEventLog.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyEventLog.java
@@ -32,4 +32,5 @@
public static final int EVENT_LOG_PDP_NETWORK_DROP = 50109;
public static final int EVENT_LOG_CDMA_DATA_SETUP_FAILED = 50110;
public static final int EVENT_LOG_CDMA_DATA_DROP = 50111;
+ public static final int EVENT_LOG_GSM_RAT_SWITCHED = 50112;
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 48e5c97..fffc3cb 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -38,6 +38,7 @@
import android.provider.Telephony.Intents;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
import android.util.Config;
@@ -858,6 +859,21 @@
cellLoc = newCellLoc;
newCellLoc = tcl;
+
+ // Add an event log when network type switched
+ // TODO: we may add filtering to reduce the event logged,
+ // i.e. check preferred network setting, only switch to 2G, etc
+ if (hasNetworkTypeChanged) {
+ int cid = -1;
+ GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation());
+ if (loc != null) cid = loc.getCid();
+ EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_GSM_RAT_SWITCHED,
+ cid, networkType, newNetworkType);
+ Log.d(LOG_TAG,
+ "RAT switched " + networkTypeToString(networkType) + " -> "
+ + networkTypeToString(newNetworkType) + " at cell " + cid);
+ }
+
gprsState = newGPRSState;
networkType = newNetworkType;
diff --git a/tests/AndroidTests/Android.mk b/tests/AndroidTests/Android.mk
index 757044f..a81b779 100644
--- a/tests/AndroidTests/Android.mk
+++ b/tests/AndroidTests/Android.mk
@@ -5,7 +5,7 @@
LOCAL_JAVA_LIBRARIES := framework-tests android.test.runner services
-LOCAL_STATIC_JAVA_LIBRARIES := googlelogin-client
+LOCAL_STATIC_JAVA_LIBRARIES := gsf-client
# Resource unit tests use a private locale
LOCAL_AAPT_FLAGS = -c xx_YY -c cs -c 160dpi -c 32dpi -c 240dpi
diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml
index eb422be..e0d8f79 100644
--- a/tests/AndroidTests/AndroidManifest.xml
+++ b/tests/AndroidTests/AndroidManifest.xml
@@ -52,9 +52,6 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="com.android.unit_tests.permission.TEST_GRANTED" />
- <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
- <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
- <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
<!-- InstrumentationTestRunner for AndroidTests -->
<instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/tests/AndroidTests/src/com/android/unit_tests/GoogleLoginServiceTest.java b/tests/AndroidTests/src/com/android/unit_tests/GoogleLoginServiceTest.java
deleted file mode 100644
index 59f14bf..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/GoogleLoginServiceTest.java
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2008 The Android Open Source Project
-// All rights reserved.
-
-package com.android.unit_tests;
-
-import java.util.Arrays;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.Condition;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.Suppress;
-import android.util.Log;
-
-import com.google.android.googleapps.GoogleLoginCredentialsResult;
-import com.google.android.googleapps.IGoogleLoginService;
-import com.google.android.googlelogin.GoogleLoginServiceConstants;
-
-import junit.framework.Assert;
-
-// Suppress until bug http://b/issue?id=1416570 is fixed
-@Suppress
-/** Unit test for the Google login service. */
-public class GoogleLoginServiceTest extends AndroidTestCase {
- private static final String TAG = "GoogleLoginServiceTest";
-
- private IGoogleLoginService mGls = null;
- private Lock mGlsLock = new ReentrantLock();
- private Condition mGlsCv = mGlsLock.newCondition();
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- mGlsLock.lock();
- try {
- mGls = IGoogleLoginService.Stub.asInterface(service);
- mGlsCv.signalAll();
- } finally {
- mGlsLock.unlock();
- }
- Log.v(TAG, "service is connected");
- }
- public void onServiceDisconnected(ComponentName className) {
- mGlsLock.lock();
- try {
- mGls = null;
- mGlsCv.signalAll();
- } finally {
- mGlsLock.unlock();
- }
- Log.v(TAG, "service is disconnected");
- }
- };
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- getContext().bindService((new Intent())
- .setClassName("com.google.android.googleapps",
- "com.google.android.googleapps.GoogleLoginService"),
- mConnection, Context.BIND_AUTO_CREATE);
-
- // wait for the service to cnnnect
- mGlsLock.lock();
- try {
- while (mGls == null) {
- try {
- mGlsCv.await();
- } catch (InterruptedException ignore) {
- }
- }
- } finally {
- mGlsLock.unlock();
- }
- }
-
- @Override
- protected void tearDown() throws Exception {
- getContext().unbindService(mConnection);
- super.tearDown();
- }
-
- public void testSingleAccountScheme() throws Exception {
- Assert.assertNotNull(mGls);
- mGls.deleteAllAccounts();
-
- Assert.assertNull(mGls.getAccount(false));
- Assert.assertNull(mGls.getAccount(true));
-
- mGls.saveUsernameAndPassword("vespa@gmail.com", "meow",
- GoogleLoginServiceConstants.FLAG_GOOGLE_ACCOUNT);
- Assert.assertEquals("vespa@gmail.com", mGls.getAccount(false));
- Assert.assertEquals("vespa@gmail.com", mGls.getAccount(true));
-
- mGls.saveUsernameAndPassword("mackerel@hosted.com", "purr",
- GoogleLoginServiceConstants.FLAG_HOSTED_ACCOUNT);
- Assert.assertEquals("mackerel@hosted.com", mGls.getAccount(false));
- Assert.assertEquals("vespa@gmail.com", mGls.getAccount(true));
- }
-
- public void listsEqual(String[] a, String[] b) {
- Assert.assertEquals(a.length, b.length);
- Arrays.sort(a);
- Arrays.sort(b);
- Assert.assertTrue(Arrays.equals(a, b));
- }
-
- public void testAuthTokens() throws Exception {
- Assert.assertNotNull(mGls);
- mGls.deleteAllAccounts();
-
- Assert.assertNull(mGls.peekCredentials("vespa@example.com", "mail"));
-
- mGls.saveUsernameAndPassword("vespa@example.com", "meow",
- GoogleLoginServiceConstants.FLAG_HOSTED_ACCOUNT);
- Assert.assertNull(mGls.peekCredentials("vespa@example.com", "mail"));
- Assert.assertNull(mGls.peekCredentials(null, "mail"));
-
- mGls.saveAuthToken("vespa@example.com", "mail", "1234");
- Assert.assertEquals("1234", mGls.peekCredentials("vespa@example.com", "mail"));
- Assert.assertEquals("1234", mGls.peekCredentials(null, "mail"));
-
- mGls.saveUsernameAndPassword("mackerel@example.com", "purr",
- GoogleLoginServiceConstants.FLAG_GOOGLE_ACCOUNT);
- mGls.saveAuthToken("mackerel@example.com", "mail", "5678");
- Assert.assertEquals("1234", mGls.peekCredentials(null, "mail"));
-
- mGls.saveAuthToken("mackerel@example.com", "mail", "8765");
- Assert.assertEquals("8765", mGls.peekCredentials("mackerel@example.com", "mail"));
-
- GoogleLoginCredentialsResult r = mGls.blockingGetCredentials(
- "vespa@example.com", "mail", false);
- Assert.assertEquals("vespa@example.com", r.getAccount());
- Assert.assertEquals("1234", r.getCredentialsString());
- Assert.assertNull(r.getCredentialsIntent());
-
- mGls.saveAuthToken("vespa@example.com", "cl", "abcd");
- Assert.assertEquals("1234", mGls.peekCredentials("vespa@example.com", "mail"));
- Assert.assertEquals("abcd", mGls.peekCredentials("vespa@example.com", "cl"));
- }
-}
diff --git a/tests/BrowserTestPlugin/jni/main.cpp b/tests/BrowserTestPlugin/jni/main.cpp
index 586d139..87ddbc4 100644
--- a/tests/BrowserTestPlugin/jni/main.cpp
+++ b/tests/BrowserTestPlugin/jni/main.cpp
@@ -53,7 +53,7 @@
NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value);
extern "C" {
-EXPORT NPError NP_Initialize(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, void *java_env, void *application_context);
+EXPORT NPError NP_Initialize(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, void *java_env);
EXPORT NPError NP_GetValue(NPP instance, NPPVariable variable, void *value);
EXPORT const char* NP_GetMIMEDescription(void);
EXPORT void NP_Shutdown(void);
@@ -70,7 +70,7 @@
#define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0]))
-NPError NP_Initialize(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, void *java_env, void *application_context)
+NPError NP_Initialize(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, void *java_env)
{
// Make sure we have a function table equal or larger than we are built against.
if (browserFuncs->size < sizeof(NPNetscapeFuncs)) {
diff --git a/tests/DumpRenderTree/AndroidManifest.xml b/tests/DumpRenderTree/AndroidManifest.xml
index efa41131..03b7e26 100644
--- a/tests/DumpRenderTree/AndroidManifest.xml
+++ b/tests/DumpRenderTree/AndroidManifest.xml
@@ -17,14 +17,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.dumprendertree">
<application android:name="HTMLHostApp">
<uses-library android:name="android.test.runner" />
- <activity android:name="Menu" android:label="1 Dump Render Tree">
+ <activity android:name="Menu" android:label="Dump Render Tree"
+ android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.TEST" />
</intent-filter>
</activity>
- <activity android:name="TestShellActivity" android:launchMode="singleTop" />
- <activity android:name="ReliabilityTestActivity" />
+ <activity android:name="TestShellActivity" android:launchMode="singleTop"
+ android:screenOrientation="portrait"/>
+ <activity android:name="ReliabilityTestActivity" android:screenOrientation="portrait"/>
</application>
<instrumentation android:name=".LayoutTestsAutoRunner"
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index f2ddd0f..ffc2cbc 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -121,6 +121,20 @@
}
},
+ new Test("Times") {
+ public void run()
+ {
+ long now = System.currentTimeMillis();
+
+ timeNotification(7, "24 hours from now", now+(1000*60*60*24));
+ timeNotification(6, "12:01:00 from now", now+(1000*60*60*12)+(60*1000));
+ timeNotification(5, "12 hours from now", now+(1000*60*60*12));
+ timeNotification(4, "now", now);
+ timeNotification(3, "11:59:00 ago", now-((1000*60*60*12)-(60*1000)));
+ timeNotification(2, "12 hours ago", now-(1000*60*60*12));
+ timeNotification(1, "24 hours ago", now-(1000*60*60*24));
+ }
+ },
new StateStress("Stress - Ongoing / Latest", 100, 100, new Runnable[] {
new Runnable() {
public void run() {
@@ -590,5 +604,12 @@
mHandler.postDelayed(mRunnable, mPause);
}
}
+
+ void timeNotification(int n, String label, long time) {
+ mNM.notify(n, new Notification(NotificationTestList.this,
+ R.drawable.ic_statusbar_missedcall, null,
+ time, label, "" + new java.util.Date(time), null));
+
+ }
}
diff --git a/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
index 52286d1..31ee120 100644
--- a/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
+++ b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java
@@ -24,9 +24,8 @@
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.TransformGestureDetector;
+import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.LinearLayout;
@@ -48,9 +47,6 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- final LayoutInflater li = (LayoutInflater)getSystemService(
- LAYOUT_INFLATER_SERVICE);
this.setTitle(R.string.act_title);
LinearLayout root = new LinearLayout(this);
@@ -71,15 +67,19 @@
private float mPosY;
private float mScale = 1.f;
private Matrix mMatrix;
- private TransformGestureDetector mDetector;
+ private ScaleGestureDetector mDetector;
- private class Listener implements TransformGestureDetector.OnTransformGestureListener {
+ private float mLastX;
+ private float mLastY;
+
+ private class Listener implements ScaleGestureDetector.OnScaleGestureListener {
- public boolean onTransform(TransformGestureDetector detector) {
- Log.d("ttest", "Translation: (" + detector.getTranslateX() +
- ", " + detector.getTranslateY() + ")");
+ public boolean onScale(ScaleGestureDetector detector) {
float scale = detector.getScaleFactor();
+
Log.d("ttest", "Scale: " + scale);
+
+ // Limit the scale so our object doesn't get too big or disappear
if (mScale * scale > 0.1f) {
if (mScale * scale < 10.f) {
mScale *= scale;
@@ -89,16 +89,13 @@
} else {
mScale = 0.1f;
}
-
- mPosX += detector.getTranslateX();
- mPosY += detector.getTranslateY();
Log.d("ttest", "mScale: " + mScale + " mPos: (" + mPosX + ", " + mPosY + ")");
float sizeX = mDrawable.getIntrinsicWidth()/2;
float sizeY = mDrawable.getIntrinsicHeight()/2;
- float centerX = detector.getCenterX();
- float centerY = detector.getCenterY();
+ float centerX = detector.getFocusX();
+ float centerY = detector.getFocusY();
float diffX = centerX - mPosX;
float diffY = centerY - mPosY;
diffX = diffX*scale - diffX;
@@ -115,24 +112,20 @@
return true;
}
- public boolean onTransformBegin(TransformGestureDetector detector) {
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
- public boolean onTransformEnd(TransformGestureDetector detector) {
- return true;
- }
-
- public boolean onTransformFling(TransformGestureDetector detector) {
- return false;
- }
-
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mLastX = detector.getFocusX();
+ mLastY = detector.getFocusY();
+ }
}
public TransformView(Context context) {
super(context);
mMatrix = new Matrix();
- mDetector = new TransformGestureDetector(context, new Listener());
+ mDetector = new ScaleGestureDetector(context, new Listener());
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mPosX = metrics.widthPixels/2;
mPosY = metrics.heightPixels/2;
@@ -151,12 +144,37 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- boolean handled = mDetector.onTouchEvent(event);
+ mDetector.onTouchEvent(event);
- int pointerCount = event.getPointerCount();
- Log.d("ttest", "pointerCount: " + pointerCount);
+ // Handling single finger pan
+ if (!mDetector.isInProgress()) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mLastX = event.getX();
+ mLastY = event.getY();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ final float x = event.getX();
+ final float y = event.getY();
+ mPosX += x - mLastX;
+ mPosY += y - mLastY;
+ mLastX = x;
+ mLastY = y;
+
+ float sizeX = mDrawable.getIntrinsicWidth()/2;
+ float sizeY = mDrawable.getIntrinsicHeight()/2;
+
+ mMatrix.reset();
+ mMatrix.postTranslate(-sizeX, -sizeY);
+ mMatrix.postScale(mScale, mScale);
+ mMatrix.postTranslate(mPosX, mPosY);
+ invalidate();
+ break;
+ }
+ }
- return handled;
+ return true;
}
@Override
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index ae8f242..c530dd4 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1750,51 +1750,59 @@
depth++;
String8 tag(tree.getElementName(&len));
// printf("Depth %d tag %s\n", depth, tag.string());
+ bool keepTag = false;
if (depth == 1) {
if (tag != "manifest") {
fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n");
return -1;
}
pkg = getAttribute(tree, NULL, "package", NULL);
- } else if (depth == 2 && tag == "application") {
- inApplication = true;
+ } else if (depth == 2) {
+ if (tag == "application") {
+ inApplication = true;
+ keepTag = true;
+ } else if (tag == "instrumentation") {
+ keepTag = true;
+ }
}
- if (inApplication) {
- if (tag == "application" || tag == "activity" || tag == "service" || tag == "receiver"
- || tag == "provider") {
- String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android",
- "name", &error);
- if (error != "") {
- fprintf(stderr, "ERROR: %s\n", error.string());
- return -1;
+ if (!keepTag && inApplication && depth == 3) {
+ if (tag == "activity" || tag == "service" || tag == "receiver" || tag == "provider") {
+ keepTag = true;
+ }
+ }
+ if (keepTag) {
+ String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android",
+ "name", &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ return -1;
+ }
+ if (name.length() > 0) {
+ // asdf --> package.asdf
+ // .asdf .a.b --> package.asdf package.a.b
+ // asdf.adsf --> asdf.asdf
+ String8 rule("-keep class ");
+ const char* p = name.string();
+ const char* q = strchr(p, '.');
+ if (p == q) {
+ rule += pkg;
+ rule += name;
+ } else if (q == NULL) {
+ rule += pkg;
+ rule += ".";
+ rule += name;
+ } else {
+ rule += name;
}
- if (name.length() > 0) {
- // asdf --> package.asdf
- // .asdf .a.b --> package.asdf package.a.b
- // asdf.adsf --> asdf.asdf
- String8 rule("-keep class ");
- const char* p = name.string();
- const char* q = strchr(p, '.');
- if (p == q) {
- rule += pkg;
- rule += name;
- } else if (q == NULL) {
- rule += pkg;
- rule += ".";
- rule += name;
- } else {
- rule += name;
- }
- String8 location = tag;
- location += " ";
- location += assFile->getSourceFile();
- char lineno[20];
- sprintf(lineno, ":%d", tree.getLineNumber());
- location += lineno;
+ String8 location = tag;
+ location += " ";
+ location += assFile->getSourceFile();
+ char lineno[20];
+ sprintf(lineno, ":%d", tree.getLineNumber());
+ location += lineno;
- keep->add(rule, location);
- }
+ keep->add(rule, location);
}
}
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java
index ff1b295..35f022e 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java
@@ -20,6 +20,7 @@
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import javax.imageio.ImageIO;
@@ -33,6 +34,12 @@
mImage = ImageIO.read(input);
}
+ public Bitmap(InputStream is) throws IOException {
+ super(1, true, null, -1);
+
+ mImage = ImageIO.read(is);
+ }
+
Bitmap(BufferedImage image) {
super(1, true, null, -1);
mImage = image;
@@ -237,4 +244,35 @@
return createBitmap(colors, 0, width, width, height, config);
}
+ public static Bitmap createScaledBitmap(Bitmap src, int dstWidth,
+ int dstHeight, boolean filter) {
+ Matrix m;
+ synchronized (Bitmap.class) {
+ // small pool of just 1 matrix
+ m = sScaleMatrix;
+ sScaleMatrix = null;
+ }
+
+ if (m == null) {
+ m = new Matrix();
+ }
+
+ final int width = src.getWidth();
+ final int height = src.getHeight();
+ final float sx = dstWidth / (float)width;
+ final float sy = dstHeight / (float)height;
+ m.setScale(sx, sy);
+ Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
+
+ synchronized (Bitmap.class) {
+ // do we need to check for null? why not just assign everytime?
+ if (sScaleMatrix == null) {
+ sScaleMatrix = m;
+ }
+ }
+
+ return b;
+ }
+
+
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java
new file mode 100644
index 0000000..e978fe8
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java
@@ -0,0 +1,566 @@
+/*
+ * 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 android.graphics;
+
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import java.io.BufferedInputStream;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Creates Bitmap objects from various sources, including files, streams,
+ * and byte-arrays.
+ */
+public class BitmapFactory {
+ public static class Options {
+ /**
+ * Create a default Options object, which if left unchanged will give
+ * the same result from the decoder as if null were passed.
+ */
+ public Options() {
+ inDither = true;
+ inScaled = true;
+ }
+
+ /**
+ * If set to true, the decoder will return null (no bitmap), but
+ * the out... fields will still be set, allowing the caller to query
+ * the bitmap without having to allocate the memory for its pixels.
+ */
+ public boolean inJustDecodeBounds;
+
+ /**
+ * If set to a value > 1, requests the decoder to subsample the original
+ * image, returning a smaller image to save memory. The sample size is
+ * the number of pixels in either dimension that correspond to a single
+ * pixel in the decoded bitmap. For example, inSampleSize == 4 returns
+ * an image that is 1/4 the width/height of the original, and 1/16 the
+ * number of pixels. Any value <= 1 is treated the same as 1. Note: the
+ * decoder will try to fulfill this request, but the resulting bitmap
+ * may have different dimensions that precisely what has been requested.
+ * Also, powers of 2 are often faster/easier for the decoder to honor.
+ */
+ public int inSampleSize;
+
+ /**
+ * If this is non-null, the decoder will try to decode into this
+ * internal configuration. If it is null, or the request cannot be met,
+ * the decoder will try to pick the best matching config based on the
+ * system's screen depth, and characteristics of the original image such
+ * as if it has per-pixel alpha (requiring a config that also does).
+ */
+ public Bitmap.Config inPreferredConfig;
+
+ /**
+ * If dither is true, the decoder will attempt to dither the decoded
+ * image.
+ */
+ public boolean inDither;
+
+ /**
+ * The pixel density to use for the bitmap. This will always result
+ * in the returned bitmap having a density set for it (see
+ * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)). In addition,
+ * if {@link #inScaled} is set (which it is by default} and this
+ * density does not match {@link #inTargetDensity}, then the bitmap
+ * will be scaled to the target density before being returned.
+ *
+ * <p>If this is 0,
+ * {@link BitmapFactory#decodeResource(Resources, int)},
+ * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
+ * and {@link BitmapFactory#decodeResourceStream}
+ * will fill in the density associated with the resource. The other
+ * functions will leave it as-is and no density will be applied.
+ *
+ * @see #inTargetDensity
+ * @see #inScreenDensity
+ * @see #inScaled
+ * @see Bitmap#setDensity(int)
+ * @see android.util.DisplayMetrics#densityDpi
+ */
+ public int inDensity;
+
+ /**
+ * The pixel density of the destination this bitmap will be drawn to.
+ * This is used in conjunction with {@link #inDensity} and
+ * {@link #inScaled} to determine if and how to scale the bitmap before
+ * returning it.
+ *
+ * <p>If this is 0,
+ * {@link BitmapFactory#decodeResource(Resources, int)},
+ * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
+ * and {@link BitmapFactory#decodeResourceStream}
+ * will fill in the density associated the Resources object's
+ * DisplayMetrics. The other
+ * functions will leave it as-is and no scaling for density will be
+ * performed.
+ *
+ * @see #inDensity
+ * @see #inScreenDensity
+ * @see #inScaled
+ * @see android.util.DisplayMetrics#densityDpi
+ */
+ public int inTargetDensity;
+
+ /**
+ * The pixel density of the actual screen that is being used. This is
+ * purely for applications running in density compatibility code, where
+ * {@link #inTargetDensity} is actually the density the application
+ * sees rather than the real screen density.
+ *
+ * <p>By setting this, you
+ * allow the loading code to avoid scaling a bitmap that is currently
+ * in the screen density up/down to the compatibility density. Instead,
+ * if {@link #inDensity} is the same as {@link #inScreenDensity}, the
+ * bitmap will be left as-is. Anything using the resulting bitmap
+ * must also used {@link Bitmap#getScaledWidth(int)
+ * Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight
+ * Bitmap.getScaledHeight} to account for any different between the
+ * bitmap's density and the target's density.
+ *
+ * <p>This is never set automatically for the caller by
+ * {@link BitmapFactory} itself. It must be explicitly set, since the
+ * caller must deal with the resulting bitmap in a density-aware way.
+ *
+ * @see #inDensity
+ * @see #inTargetDensity
+ * @see #inScaled
+ * @see android.util.DisplayMetrics#densityDpi
+ */
+ public int inScreenDensity;
+
+ /**
+ * When this flag is set, if {@link #inDensity} and
+ * {@link #inTargetDensity} are not 0, the
+ * bitmap will be scaled to match {@link #inTargetDensity} when loaded,
+ * rather than relying on the graphics system scaling it each time it
+ * is drawn to a Canvas.
+ *
+ * <p>This flag is turned on by default and should be turned off if you need
+ * a non-scaled version of the bitmap. Nine-patch bitmaps ignore this
+ * flag and are always scaled.
+ */
+ public boolean inScaled;
+
+ /**
+ * If this is set to true, then the resulting bitmap will allocate its
+ * pixels such that they can be purged if the system needs to reclaim
+ * memory. In that instance, when the pixels need to be accessed again
+ * (e.g. the bitmap is drawn, getPixels() is called), they will be
+ * automatically re-decoded.
+ *
+ * For the re-decode to happen, the bitmap must have access to the
+ * encoded data, either by sharing a reference to the input
+ * or by making a copy of it. This distinction is controlled by
+ * inInputShareable. If this is true, then the bitmap may keep a shallow
+ * reference to the input. If this is false, then the bitmap will
+ * explicitly make a copy of the input data, and keep that. Even if
+ * sharing is allowed, the implementation may still decide to make a
+ * deep copy of the input data.
+ */
+ public boolean inPurgeable;
+
+ /**
+ * This field works in conjuction with inPurgeable. If inPurgeable is
+ * false, then this field is ignored. If inPurgeable is true, then this
+ * field determines whether the bitmap can share a reference to the
+ * input data (inputstream, array, etc.) or if it must make a deep copy.
+ */
+ public boolean inInputShareable;
+
+ /**
+ * Normally bitmap allocations count against the dalvik heap, which
+ * means they help trigger GCs when a lot have been allocated. However,
+ * in rare cases, the caller may want to allocate the bitmap outside of
+ * that heap. To request that, set inNativeAlloc to true. In these
+ * rare instances, it is solely up to the caller to ensure that OOM is
+ * managed explicitly by calling bitmap.recycle() as soon as such a
+ * bitmap is no longer needed.
+ *
+ * @hide pending API council approval
+ */
+ public boolean inNativeAlloc;
+
+ /**
+ * The resulting width of the bitmap, set independent of the state of
+ * inJustDecodeBounds. However, if there is an error trying to decode,
+ * outWidth will be set to -1.
+ */
+ public int outWidth;
+
+ /**
+ * The resulting height of the bitmap, set independent of the state of
+ * inJustDecodeBounds. However, if there is an error trying to decode,
+ * outHeight will be set to -1.
+ */
+ public int outHeight;
+
+ /**
+ * If known, this string is set to the mimetype of the decoded image.
+ * If not know, or there is an error, it is set to null.
+ */
+ public String outMimeType;
+
+ /**
+ * Temp storage to use for decoding. Suggest 16K or so.
+ */
+ public byte[] inTempStorage;
+
+ private native void requestCancel();
+
+ /**
+ * Flag to indicate that cancel has been called on this object. This
+ * is useful if there's an intermediary that wants to first decode the
+ * bounds and then decode the image. In that case the intermediary
+ * can check, inbetween the bounds decode and the image decode, to see
+ * if the operation is canceled.
+ */
+ public boolean mCancel;
+
+ /**
+ * This can be called from another thread while this options object is
+ * inside a decode... call. Calling this will notify the decoder that
+ * it should cancel its operation. This is not guaranteed to cancel
+ * the decode, but if it does, the decoder... operation will return
+ * null, or if inJustDecodeBounds is true, will set outWidth/outHeight
+ * to -1
+ */
+ public void requestCancelDecode() {
+ mCancel = true;
+ requestCancel();
+ }
+ }
+
+ /**
+ * Decode a file path into a bitmap. If the specified file name is null,
+ * or cannot be decoded into a bitmap, the function returns null.
+ *
+ * @param pathName complete path name for the file to be decoded.
+ * @param opts null-ok; Options that control downsampling and whether the
+ * image should be completely decoded, or just is size returned.
+ * @return The decoded bitmap, or null if the image data could not be
+ * decoded, or, if opts is non-null, if opts requested only the
+ * size be returned (in opts.outWidth and opts.outHeight)
+ */
+ public static Bitmap decodeFile(String pathName, Options opts) {
+ Bitmap bm = null;
+ InputStream stream = null;
+ try {
+ stream = new FileInputStream(pathName);
+ bm = decodeStream(stream, null, opts);
+ } catch (Exception e) {
+ /* do nothing.
+ If the exception happened on open, bm will be null.
+ */
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ // do nothing here
+ }
+ }
+ }
+ return bm;
+ }
+
+ /**
+ * Decode a file path into a bitmap. If the specified file name is null,
+ * or cannot be decoded into a bitmap, the function returns null.
+ *
+ * @param pathName complete path name for the file to be decoded.
+ * @return the resulting decoded bitmap, or null if it could not be decoded.
+ */
+ public static Bitmap decodeFile(String pathName) {
+ return decodeFile(pathName, null);
+ }
+
+ /**
+ * Decode a new Bitmap from an InputStream. This InputStream was obtained from
+ * resources, which we pass to be able to scale the bitmap accordingly.
+ */
+ public static Bitmap decodeResourceStream(Resources res, TypedValue value,
+ InputStream is, Rect pad, Options opts) {
+
+ if (opts == null) {
+ opts = new Options();
+ }
+
+ if (opts.inDensity == 0 && value != null) {
+ final int density = value.density;
+ if (density == TypedValue.DENSITY_DEFAULT) {
+ opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
+ } else if (density != TypedValue.DENSITY_NONE) {
+ opts.inDensity = density;
+ }
+ }
+
+ if (opts.inTargetDensity == 0 && res != null) {
+ opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
+ }
+
+ return decodeStream(is, pad, opts);
+ }
+
+ /**
+ * Synonym for opening the given resource and calling
+ * {@link #decodeResourceStream}.
+ *
+ * @param res The resources object containing the image data
+ * @param id The resource id of the image data
+ * @param opts null-ok; Options that control downsampling and whether the
+ * image should be completely decoded, or just is size returned.
+ * @return The decoded bitmap, or null if the image data could not be
+ * decoded, or, if opts is non-null, if opts requested only the
+ * size be returned (in opts.outWidth and opts.outHeight)
+ */
+ public static Bitmap decodeResource(Resources res, int id, Options opts) {
+ Bitmap bm = null;
+ InputStream is = null;
+
+ try {
+ final TypedValue value = new TypedValue();
+ is = res.openRawResource(id, value);
+
+ bm = decodeResourceStream(res, value, is, null, opts);
+ } catch (Exception e) {
+ /* do nothing.
+ If the exception happened on open, bm will be null.
+ If it happened on close, bm is still valid.
+ */
+ } finally {
+ try {
+ if (is != null) is.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+
+ return bm;
+ }
+
+ /**
+ * Synonym for {@link #decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}
+ * will null Options.
+ *
+ * @param res The resources object containing the image data
+ * @param id The resource id of the image data
+ * @return The decoded bitmap, or null if the image could not be decode.
+ */
+ public static Bitmap decodeResource(Resources res, int id) {
+ return decodeResource(res, id, null);
+ }
+
+ /**
+ * Decode an immutable bitmap from the specified byte array.
+ *
+ * @param data byte array of compressed image data
+ * @param offset offset into imageData for where the decoder should begin
+ * parsing.
+ * @param length the number of bytes, beginning at offset, to parse
+ * @param opts null-ok; Options that control downsampling and whether the
+ * image should be completely decoded, or just is size returned.
+ * @return The decoded bitmap, or null if the image data could not be
+ * decoded, or, if opts is non-null, if opts requested only the
+ * size be returned (in opts.outWidth and opts.outHeight)
+ */
+ public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
+ if ((offset | length) < 0 || data.length < offset + length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+
+ // FIXME: implement as needed, but it's unlikely that this is needed in the context of the bridge.
+ return null;
+ //return nativeDecodeByteArray(data, offset, length, opts);
+ }
+
+ /**
+ * Decode an immutable bitmap from the specified byte array.
+ *
+ * @param data byte array of compressed image data
+ * @param offset offset into imageData for where the decoder should begin
+ * parsing.
+ * @param length the number of bytes, beginning at offset, to parse
+ * @return The decoded bitmap, or null if the image could not be decode.
+ */
+ public static Bitmap decodeByteArray(byte[] data, int offset, int length) {
+ return decodeByteArray(data, offset, length, null);
+ }
+
+ /**
+ * Decode an input stream into a bitmap. If the input stream is null, or
+ * cannot be used to decode a bitmap, the function returns null.
+ * The stream's position will be where ever it was after the encoded data
+ * was read.
+ *
+ * @param is The input stream that holds the raw data to be decoded into a
+ * bitmap.
+ * @param outPadding If not null, return the padding rect for the bitmap if
+ * it exists, otherwise set padding to [-1,-1,-1,-1]. If
+ * no bitmap is returned (null) then padding is
+ * unchanged.
+ * @param opts null-ok; Options that control downsampling and whether the
+ * image should be completely decoded, or just is size returned.
+ * @return The decoded bitmap, or null if the image data could not be
+ * decoded, or, if opts is non-null, if opts requested only the
+ * size be returned (in opts.outWidth and opts.outHeight)
+ */
+ public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
+ // we don't throw in this case, thus allowing the caller to only check
+ // the cache, and not force the image to be decoded.
+ if (is == null) {
+ return null;
+ }
+
+ // we need mark/reset to work properly
+
+ if (!is.markSupported()) {
+ is = new BufferedInputStream(is, 16 * 1024);
+ }
+
+ // so we can call reset() if a given codec gives up after reading up to
+ // this many bytes. FIXME: need to find out from the codecs what this
+ // value should be.
+ is.mark(1024);
+
+ Bitmap bm;
+
+ if (is instanceof AssetManager.AssetInputStream) {
+ // FIXME: log this.
+ return null;
+ } else {
+ // pass some temp storage down to the native code. 1024 is made up,
+ // but should be large enough to avoid too many small calls back
+ // into is.read(...) This number is not related to the value passed
+ // to mark(...) above.
+ try {
+ bm = new Bitmap(is);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ return finishDecode(bm, outPadding, opts);
+ }
+
+ private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
+ if (bm == null || opts == null) {
+ return bm;
+ }
+
+ final int density = opts.inDensity;
+ if (density == 0) {
+ return bm;
+ }
+
+ bm.setDensity(density);
+ final int targetDensity = opts.inTargetDensity;
+ if (targetDensity == 0 || density == targetDensity
+ || density == opts.inScreenDensity) {
+ return bm;
+ }
+
+ byte[] np = bm.getNinePatchChunk();
+ final boolean isNinePatch = false; //np != null && NinePatch.isNinePatchChunk(np);
+ if (opts.inScaled || isNinePatch) {
+ float scale = targetDensity / (float)density;
+ // TODO: This is very inefficient and should be done in native by Skia
+ final Bitmap oldBitmap = bm;
+ bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
+ (int) (bm.getHeight() * scale + 0.5f), true);
+ oldBitmap.recycle();
+
+ if (isNinePatch) {
+ //np = nativeScaleNinePatch(np, scale, outPadding);
+ bm.setNinePatchChunk(np);
+ }
+ bm.setDensity(targetDensity);
+ }
+
+ return bm;
+ }
+
+ /**
+ * Decode an input stream into a bitmap. If the input stream is null, or
+ * cannot be used to decode a bitmap, the function returns null.
+ * The stream's position will be where ever it was after the encoded data
+ * was read.
+ *
+ * @param is The input stream that holds the raw data to be decoded into a
+ * bitmap.
+ * @return The decoded bitmap, or null if the image data could not be
+ * decoded, or, if opts is non-null, if opts requested only the
+ * size be returned (in opts.outWidth and opts.outHeight)
+ */
+ public static Bitmap decodeStream(InputStream is) {
+ return decodeStream(is, null, null);
+ }
+
+ /**
+ * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded
+ * return null. The position within the descriptor will not be changed when
+ * this returns, so the descriptor can be used again as-is.
+ *
+ * @param fd The file descriptor containing the bitmap data to decode
+ * @param outPadding If not null, return the padding rect for the bitmap if
+ * it exists, otherwise set padding to [-1,-1,-1,-1]. If
+ * no bitmap is returned (null) then padding is
+ * unchanged.
+ * @param opts null-ok; Options that control downsampling and whether the
+ * image should be completely decoded, or just is size returned.
+ * @return the decoded bitmap, or null
+ */
+ public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) {
+ return null;
+
+ /* FIXME: implement as needed
+ try {
+ if (MemoryFile.isMemoryFile(fd)) {
+ int mappedlength = MemoryFile.getMappedSize(fd);
+ MemoryFile file = new MemoryFile(fd, mappedlength, "r");
+ InputStream is = file.getInputStream();
+ Bitmap bm = decodeStream(is, outPadding, opts);
+ return finishDecode(bm, outPadding, opts);
+ }
+ } catch (IOException ex) {
+ // invalid filedescriptor, no need to call nativeDecodeFileDescriptor()
+ return null;
+ }
+ //Bitmap bm = nativeDecodeFileDescriptor(fd, outPadding, opts);
+ //return finishDecode(bm, outPadding, opts);
+ */
+ }
+
+ /**
+ * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded
+ * return null. The position within the descriptor will not be changed when
+ * this returns, so the descriptor can be used again as is.
+ *
+ * @param fd The file descriptor containing the bitmap data to decode
+ * @return the decoded bitmap, or null
+ */
+ public static Bitmap decodeFileDescriptor(FileDescriptor fd) {
+ return decodeFileDescriptor(fd, null, null);
+ }
+}
+
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java
index 8bf7fcc..ad3974c 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader.java
@@ -16,6 +16,8 @@
package android.graphics;
+import java.awt.Paint;
+
public class BitmapShader extends Shader {
// we hold on just for the GC, since our native counterpart is using it
@@ -31,11 +33,16 @@
public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY) {
mBitmap = bitmap;
}
-
+
//---------- Custom methods
-
+
public Bitmap getBitmap() {
return mBitmap;
}
+
+ @Override
+ Paint getJavaPaint() {
+ return null;
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
index 4986c77..8bf5e85 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java
@@ -31,6 +31,7 @@
import android.graphics.Region.Op;
import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
@@ -103,13 +104,37 @@
* Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
* <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
*/
- private Graphics2D getNewGraphics(Paint paint, Graphics2D g) {
+ private Graphics2D getCustomGraphics(Paint paint) {
// make new one
+ Graphics2D g = getGraphics2d();
g = (Graphics2D)g.create();
+
+ // configure it
g.setColor(new Color(paint.getColor()));
int alpha = paint.getAlpha();
float falpha = alpha / 255.f;
+ Style style = paint.getStyle();
+ if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
+ PathEffect e = paint.getPathEffect();
+ if (e instanceof DashPathEffect) {
+ DashPathEffect dpe = (DashPathEffect)e;
+ g.setStroke(new BasicStroke(
+ paint.getStrokeWidth(),
+ paint.getStrokeCap().getJavaCap(),
+ paint.getStrokeJoin().getJavaJoin(),
+ paint.getStrokeMiter(),
+ dpe.getIntervals(),
+ dpe.getPhase()));
+ } else {
+ g.setStroke(new BasicStroke(
+ paint.getStrokeWidth(),
+ paint.getStrokeCap().getJavaCap(),
+ paint.getStrokeJoin().getJavaJoin(),
+ paint.getStrokeMiter()));
+ }
+ }
+
Xfermode xfermode = paint.getXfermode();
if (xfermode instanceof PorterDuffXfermode) {
PorterDuff.Mode mode = ((PorterDuffXfermode)xfermode).getMode();
@@ -125,13 +150,16 @@
}
Shader shader = paint.getShader();
- if (shader instanceof LinearGradient) {
- g.setPaint(((LinearGradient)shader).getPaint());
- } else {
- if (mLogger != null && shader != null) {
- mLogger.warning(String.format(
- "Shader '%1$s' is not supported in the Layout Editor.",
- shader.getClass().getCanonicalName()));
+ if (shader != null) {
+ java.awt.Paint shaderPaint = shader.getJavaPaint();
+ if (shaderPaint != null) {
+ g.setPaint(shaderPaint);
+ } else {
+ if (mLogger != null) {
+ mLogger.warning(String.format(
+ "Shader '%1$s' is not supported in the Layout Editor.",
+ shader.getClass().getCanonicalName()));
+ }
}
}
@@ -236,10 +264,15 @@
*/
@Override
public int save() {
+ // get the current save count
+ int count = mGraphicsStack.size();
+
+ // create a new graphics and add it to the stack
Graphics2D g = (Graphics2D)getGraphics2d().create();
mGraphicsStack.push(g);
- return mGraphicsStack.size() - 1;
+ // return the old save count
+ return count;
}
/* (non-Javadoc)
@@ -274,10 +307,9 @@
*/
@Override
public int getSaveCount() {
- return mGraphicsStack.size() - 1;
+ return mGraphicsStack.size();
}
-
/* (non-Javadoc)
* @see android.graphics.Canvas#clipRect(float, float, float, float, android.graphics.Region.Op)
*/
@@ -405,7 +437,7 @@
g.setColor(new Color(color));
- getGraphics2d().fillRect(0, 0, getWidth(), getHeight());
+ g.fillRect(0, 0, getWidth(), getHeight());
g.setComposite(composite);
@@ -776,24 +808,24 @@
}
private final void doDrawRect(int left, int top, int width, int height, Paint paint) {
- // get current graphisc
- Graphics2D g = getGraphics2d();
+ if (width > 0 && height > 0) {
+ // get a Graphics2D object configured with the drawing parameters.
+ Graphics2D g = getCustomGraphics(paint);
- g = getNewGraphics(paint, g);
+ Style style = paint.getStyle();
- Style style = paint.getStyle();
+ // draw
+ if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
+ g.fillRect(left, top, width, height);
+ }
- // draw
- if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
- g.fillRect(left, top, width, height);
+ if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
+ g.drawRect(left, top, width, height);
+ }
+
+ // dispose Graphics2D object
+ g.dispose();
}
-
- if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
- g.drawRect(left, top, width, height);
- }
-
- // dispose Graphics2D object
- g.dispose();
}
/* (non-Javadoc)
@@ -801,30 +833,30 @@
*/
@Override
public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
- // get current graphisc
- Graphics2D g = getGraphics2d();
+ if (rect.width() > 0 && rect.height() > 0) {
+ // get a Graphics2D object configured with the drawing parameters.
+ Graphics2D g = getCustomGraphics(paint);
- g = getNewGraphics(paint, g);
+ Style style = paint.getStyle();
- Style style = paint.getStyle();
+ // draw
- // draw
+ int arcWidth = (int)(rx * 2);
+ int arcHeight = (int)(ry * 2);
- int arcWidth = (int)(rx * 2);
- int arcHeight = (int)(ry * 2);
+ if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
+ g.fillRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
+ arcWidth, arcHeight);
+ }
- if (style == Style.FILL || style == Style.FILL_AND_STROKE) {
- g.fillRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
- arcWidth, arcHeight);
+ if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
+ g.drawRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
+ arcWidth, arcHeight);
+ }
+
+ // dispose Graphics2D object
+ g.dispose();
}
-
- if (style == Style.STROKE || style == Style.FILL_AND_STROKE) {
- g.drawRoundRect((int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
- arcWidth, arcHeight);
- }
-
- // dispose Graphics2D object
- g.dispose();
}
@@ -833,10 +865,8 @@
*/
@Override
public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
- // get current graphisc
- Graphics2D g = getGraphics2d();
-
- g = getNewGraphics(paint, g);
+ // get a Graphics2D object configured with the drawing parameters.
+ Graphics2D g = getCustomGraphics(paint);
g.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
@@ -849,10 +879,8 @@
*/
@Override
public void drawLines(float[] pts, int offset, int count, Paint paint) {
- // get current graphisc
- Graphics2D g = getGraphics2d();
-
- g = getNewGraphics(paint, g);
+ // get a Graphics2D object configured with the drawing parameters.
+ Graphics2D g = getCustomGraphics(paint);
for (int i = 0 ; i < count ; i += 4) {
g.drawLine((int)pts[i + offset], (int)pts[i + offset + 1],
@@ -876,10 +904,8 @@
*/
@Override
public void drawCircle(float cx, float cy, float radius, Paint paint) {
- // get current graphisc
- Graphics2D g = getGraphics2d();
-
- g = getNewGraphics(paint, g);
+ // get a Graphics2D object configured with the drawing parameters.
+ Graphics2D g = getCustomGraphics(paint);
Style style = paint.getStyle();
@@ -903,10 +929,8 @@
*/
@Override
public void drawOval(RectF oval, Paint paint) {
- // get current graphics
- Graphics2D g = getGraphics2d();
-
- g = getNewGraphics(paint, g);
+ // get a Graphics2D object configured with the drawing parameters.
+ Graphics2D g = getCustomGraphics(paint);
Style style = paint.getStyle();
@@ -928,10 +952,8 @@
*/
@Override
public void drawPath(Path path, Paint paint) {
- // get current graphics
- Graphics2D g = getGraphics2d();
-
- g = getNewGraphics(paint, g);
+ // get a Graphics2D object configured with the drawing parameters.
+ Graphics2D g = getCustomGraphics(paint);
Style style = paint.getStyle();
@@ -953,10 +975,6 @@
*/
@Override
public void setMatrix(Matrix matrix) {
- // since SetMatrix *replaces* all the other transformation, we have to restore/save
- restore();
- save();
-
// get the new current graphics
Graphics2D g = getGraphics2d();
@@ -968,6 +986,27 @@
}
}
+ /* (non-Javadoc)
+ * @see android.graphics.Canvas#concat(android.graphics.Matrix)
+ */
+ @Override
+ public void concat(Matrix matrix) {
+ // get the current top graphics2D object.
+ Graphics2D g = getGraphics2d();
+
+ // get its current matrix
+ AffineTransform currentTx = g.getTransform();
+ // get the AffineTransform of the given matrix
+ AffineTransform matrixTx = matrix.getTransform();
+
+ // combine them so that the given matrix is applied after.
+ currentTx.preConcatenate(matrixTx);
+
+ // give it to the graphics2D as a new matrix replacing all previous transform
+ g.setTransform(currentTx);
+ }
+
+
// --------------------
/* (non-Javadoc)
@@ -1008,15 +1047,6 @@
}
/* (non-Javadoc)
- * @see android.graphics.Canvas#concat(android.graphics.Matrix)
- */
- @Override
- public void concat(Matrix matrix) {
- // TODO Auto-generated method stub
- super.concat(matrix);
- }
-
- /* (non-Javadoc)
* @see android.graphics.Canvas#drawArc(android.graphics.RectF, float, float, boolean, android.graphics.Paint)
*/
@Override
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java
index 968a597..863d64a 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader.java
@@ -16,6 +16,8 @@
package android.graphics;
+import java.awt.Paint;
+
/** A subclass of shader that returns the composition of two other shaders, combined by
an {@link android.graphics.Xfermode} subclass.
*/
@@ -42,5 +44,10 @@
public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) {
// FIXME Implement shader
}
+
+ @Override
+ Paint getJavaPaint() {
+ return null;
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java
new file mode 100644
index 0000000..46d4c70
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect.java
@@ -0,0 +1,54 @@
+/*
+ * 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 android.graphics;
+
+public class DashPathEffect extends PathEffect {
+
+ private final float[] mIntervals;
+ private final float mPhase;
+
+ /**
+ * The intervals array must contain an even number of entries (>=2), with
+ * the even indices specifying the "on" intervals, and the odd indices
+ * specifying the "off" intervals. phase is an offset into the intervals
+ * array (mod the sum of all of the intervals). The intervals array
+ * controls the length of the dashes. The paint's strokeWidth controls the
+ * thickness of the dashes.
+ * Note: this patheffect only affects drawing with the paint's style is set
+ * to STROKE or STROKE_AND_FILL. It is ignored if the drawing is done with
+ * style == FILL.
+ * @param intervals array of ON and OFF distances
+ * @param phase offset into the intervals array
+ */
+ public DashPathEffect(float intervals[], float phase) {
+ if (intervals.length < 2) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+
+ mIntervals = intervals;
+ mPhase = phase;
+ }
+
+ public float[] getIntervals() {
+ return mIntervals;
+ }
+
+ public float getPhase() {
+ return mPhase;
+ }
+}
+
diff --git a/tools/layoutlib/bridge/src/android/graphics/GradientShader.java b/tools/layoutlib/bridge/src/android/graphics/GradientShader.java
new file mode 100644
index 0000000..8c5a2a4
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/GradientShader.java
@@ -0,0 +1,205 @@
+/*
+ * 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 android.graphics;
+
+
+/**
+ * Base class for Gradient shader. This is not a standard android class and is just used
+ * as a base class for the re-implemented gradient classes.
+ *
+ * It also provides a base class to handle common code between the different shaders'
+ * implementations of {@link java.awt.Paint}.
+ *
+ * @see LinearGradient
+ * @see RadialGradient
+ * @see SweepGradient
+ */
+public abstract class GradientShader extends Shader {
+
+ protected final int[] mColors;
+ protected final float[] mPositions;
+
+ /**
+ * Creates the base shader and do some basic test on the parameters.
+ *
+ * @param colors The colors to be distributed along the gradient line
+ * @param positions May be null. The relative positions [0..1] of each
+ * corresponding color in the colors array. If this is null, the
+ * the colors are distributed evenly along the gradient line.
+ */
+ protected GradientShader(int colors[], float positions[]) {
+ if (colors.length < 2) {
+ throw new IllegalArgumentException("needs >= 2 number of colors");
+ }
+ if (positions != null && colors.length != positions.length) {
+ throw new IllegalArgumentException("color and position arrays must be of equal length");
+ }
+
+ if (positions == null) {
+ float spacing = 1.f / (colors.length - 1);
+ positions = new float[colors.length];
+ positions[0] = 0.f;
+ positions[colors.length-1] = 1.f;
+ for (int i = 1; i < colors.length - 1 ; i++) {
+ positions[i] = spacing * i;
+ }
+ }
+
+ mColors = colors;
+ mPositions = positions;
+ }
+
+ /**
+ * Base class for (Java) Gradient Paints. This handles computing the gradient colors based
+ * on the color and position lists, as well as the {@link TileMode}
+ *
+ */
+ protected abstract static class GradientPaint implements java.awt.Paint {
+ private final static int GRADIENT_SIZE = 100;
+
+ private final int[] mColors;
+ private final float[] mPositions;
+ private final TileMode mTileMode;
+ private int[] mGradient;
+
+ protected GradientPaint(int[] colors, float[] positions, TileMode tileMode) {
+ mColors = colors;
+ mPositions = positions;
+ mTileMode = tileMode;
+ }
+
+ public int getTransparency() {
+ return java.awt.Paint.TRANSLUCENT;
+ }
+
+ /**
+ * Pre-computes the colors for the gradient. This must be called once before any call
+ * to {@link #getGradientColor(float)}
+ */
+ protected synchronized void precomputeGradientColors() {
+ if (mGradient == null) {
+ // actually create an array with an extra size, so that we can really go
+ // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
+ mGradient = new int[GRADIENT_SIZE+1];
+
+ int prevPos = 0;
+ int nextPos = 1;
+ for (int i = 0 ; i <= GRADIENT_SIZE ; i++) {
+ // compute current position
+ float currentPos = (float)i/GRADIENT_SIZE;
+ while (currentPos > mPositions[nextPos]) {
+ prevPos = nextPos++;
+ }
+
+ float percent = (currentPos - mPositions[prevPos]) /
+ (mPositions[nextPos] - mPositions[prevPos]);
+
+ mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
+ }
+ }
+ }
+
+ /**
+ * Returns the color based on the position in the gradient.
+ * <var>pos</var> can be anything, even < 0 or > > 1, as the gradient
+ * will use {@link TileMode} value to convert it into a [0,1] value.
+ */
+ protected int getGradientColor(float pos) {
+ if (pos < 0.f) {
+ if (mTileMode != null) {
+ switch (mTileMode) {
+ case CLAMP:
+ pos = 0.f;
+ break;
+ case REPEAT:
+ // remove the integer part to stay in the [0,1] range
+ // careful: this is a negative value, so use ceil instead of floor
+ pos = pos - (float)Math.ceil(pos);
+ break;
+ case MIRROR:
+ // get the integer and the decimal part
+ // careful: this is a negative value, so use ceil instead of floor
+ int intPart = (int)Math.ceil(pos);
+ pos = pos - intPart;
+ // 0 -> -1 : mirrored order
+ // -1 -> -2: normal order
+ // etc..
+ // this means if the intpart is even we invert
+ if ((intPart % 2) == 0) {
+ pos = 1.f - pos;
+ }
+ break;
+ }
+ } else {
+ pos = 0.0f;
+ }
+ } else if (pos > 1f) {
+ if (mTileMode != null) {
+ switch (mTileMode) {
+ case CLAMP:
+ pos = 1.f;
+ break;
+ case REPEAT:
+ // remove the integer part to stay in the [0,1] range
+ pos = pos - (float)Math.floor(pos);
+ break;
+ case MIRROR:
+ // get the integer and the decimal part
+ int intPart = (int)Math.floor(pos);
+ pos = pos - intPart;
+ // 0 -> 1 : normal order
+ // 1 -> 2: mirrored
+ // etc..
+ // this means if the intpart is odd we invert
+ if ((intPart % 2) == 1) {
+ pos = 1.f - pos;
+ }
+ break;
+ }
+ } else {
+ pos = 1.0f;
+ }
+ }
+
+ int index = (int)((pos * GRADIENT_SIZE) + .5);
+
+ return mGradient[index];
+ }
+
+ /**
+ * Returns the color between c1, and c2, based on the percent of the distance
+ * between c1 and c2.
+ */
+ private int computeColor(int c1, int c2, float percent) {
+ int a = computeChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
+ int r = computeChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
+ int g = computeChannel((c1 >> 8) & 0xFF, (c2 >> 8) & 0xFF, percent);
+ int b = computeChannel((c1 ) & 0xFF, (c2 ) & 0xFF, percent);
+ return a << 24 | r << 16 | g << 8 | b;
+ }
+
+ /**
+ * Returns the channel value between 2 values based on the percent of the distance between
+ * the 2 values..
+ */
+ private int computeChannel(int c1, int c2, float percent) {
+ return c1 + (int)((percent * (c2-c1)) + .5);
+ }
+
+
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
index 1a0dc05..10c4a5e 100644
--- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient.java
@@ -16,56 +16,158 @@
package android.graphics;
-import java.awt.GradientPaint;
-import java.awt.Color;
-import java.awt.Paint;
+public class LinearGradient extends GradientShader {
-public class LinearGradient extends Shader {
-
- private GradientPaint mGradientPaint;
+ private java.awt.Paint mJavaPaint;
- /** Create a shader that draws a linear gradient along a line.
- @param x0 The x-coordinate for the start of the gradient line
- @param y0 The y-coordinate for the start of the gradient line
- @param x1 The x-coordinate for the end of the gradient line
- @param y1 The y-coordinate for the end of the gradient line
- @param colors The colors to be distributed along the gradient line
- @param positions May be null. The relative positions [0..1] of
- each corresponding color in the colors array. If this is null,
- the the colors are distributed evenly along the gradient line.
- @param tile The Shader tiling mode
- */
- public LinearGradient(float x0, float y0, float x1, float y1,
- int colors[], float positions[], TileMode tile) {
- if (colors.length < 2) {
- throw new IllegalArgumentException("needs >= 2 number of colors");
- }
- if (positions != null && colors.length != positions.length) {
- throw new IllegalArgumentException("color and position arrays must be of equal length");
- }
-
- // FIXME implement multi color linear gradient
+ /**
+ * Create a shader that draws a linear gradient along a line.
+ *
+ * @param x0 The x-coordinate for the start of the gradient line
+ * @param y0 The y-coordinate for the start of the gradient line
+ * @param x1 The x-coordinate for the end of the gradient line
+ * @param y1 The y-coordinate for the end of the gradient line
+ * @param colors The colors to be distributed along the gradient line
+ * @param positions May be null. The relative positions [0..1] of each
+ * corresponding color in the colors array. If this is null, the
+ * the colors are distributed evenly along the gradient line.
+ * @param tile The Shader tiling mode
+ */
+ public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],
+ TileMode tile) {
+ super(colors, positions);
+ mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile);
}
- /** Create a shader that draws a linear gradient along a line.
- @param x0 The x-coordinate for the start of the gradient line
- @param y0 The y-coordinate for the start of the gradient line
- @param x1 The x-coordinate for the end of the gradient line
- @param y1 The y-coordinate for the end of the gradient line
- @param color0 The color at the start of the gradient line.
- @param color1 The color at the end of the gradient line.
- @param tile The Shader tiling mode
- */
- public LinearGradient(float x0, float y0, float x1, float y1,
- int color0, int color1, TileMode tile) {
- mGradientPaint = new GradientPaint(x0, y0, new Color(color0, true /* hasalpha */),
- x1,y1, new Color(color1, true /* hasalpha */), tile != TileMode.CLAMP);
+ /**
+ * Create a shader that draws a linear gradient along a line.
+ *
+ * @param x0 The x-coordinate for the start of the gradient line
+ * @param y0 The y-coordinate for the start of the gradient line
+ * @param x1 The x-coordinate for the end of the gradient line
+ * @param y1 The y-coordinate for the end of the gradient line
+ * @param color0 The color at the start of the gradient line.
+ * @param color1 The color at the end of the gradient line.
+ * @param tile The Shader tiling mode
+ */
+ public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
+ TileMode tile) {
+ this(x0, y0, x1, y1, new int[] { color0, color1}, null /*positions*/, tile);
}
-
- //---------- Custom Methods
-
- public Paint getPaint() {
- return mGradientPaint;
+
+ // ---------- Custom Methods
+
+ @Override
+ java.awt.Paint getJavaPaint() {
+ return mJavaPaint;
+ }
+
+ /**
+ * Linear Gradient (Java) Paint able to handle more than 2 points, as
+ * {@link java.awt.GradientPaint} only supports 2 points and does not support Android's tile
+ * modes.
+ */
+ private static class LinearGradientPaint extends GradientPaint {
+
+ private final float mX0;
+ private final float mY0;
+ private final float mDx;
+ private final float mDy;
+ private final float mDSize2;
+
+ public LinearGradientPaint(float x0, float y0, float x1, float y1, int colors[],
+ float positions[], TileMode tile) {
+ super(colors, positions, tile);
+ mX0 = x0;
+ mY0 = y0;
+ mDx = x1 - x0;
+ mDy = y1 - y0;
+ mDSize2 = mDx * mDx + mDy * mDy;
+ }
+
+ public java.awt.PaintContext createContext(
+ java.awt.image.ColorModel colorModel,
+ java.awt.Rectangle deviceBounds,
+ java.awt.geom.Rectangle2D userBounds,
+ java.awt.geom.AffineTransform xform,
+ java.awt.RenderingHints hints) {
+ precomputeGradientColors();
+ return new LinearGradientPaintContext(colorModel);
+ }
+
+ private class LinearGradientPaintContext implements java.awt.PaintContext {
+
+ private final java.awt.image.ColorModel mColorModel;
+
+ public LinearGradientPaintContext(java.awt.image.ColorModel colorModel) {
+ mColorModel = colorModel;
+ // FIXME: so far all this is always the same rect gotten in getRaster with an indentity matrix?
+ }
+
+ public void dispose() {
+ }
+
+ public java.awt.image.ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+ java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h,
+ java.awt.image.BufferedImage.TYPE_INT_ARGB);
+
+ int[] data = new int[w*h];
+
+ if (mDx == 0) { // vertical gradient
+ // compute first column and copy to all other columns
+ int index = 0;
+ for (int iy = 0 ; iy < h ; iy++) {
+ int color = getColor(iy + y, mY0, mDy);
+ for (int ix = 0 ; ix < w ; ix++) {
+ data[index++] = color;
+ }
+ }
+ } else if (mDy == 0) { // horizontal
+ // compute first line in a tmp array and copy to all lines
+ int[] line = new int[w];
+ for (int ix = 0 ; ix < w ; ix++) {
+ line[ix] = getColor(ix + x, mX0, mDx);
+ }
+
+ for (int iy = 0 ; iy < h ; iy++) {
+ System.arraycopy(line, 0, data, iy*w, line.length);
+ }
+ } else {
+ int index = 0;
+ for (int iy = 0 ; iy < h ; iy++) {
+ for (int ix = 0 ; ix < w ; ix++) {
+ data[index++] = getColor(ix + x, iy + y);
+ }
+ }
+ }
+
+ image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
+
+ return image.getRaster();
+ }
+ }
+
+ /** Returns a color for the easy vertical/horizontal mode */
+ private int getColor(float absPos, float refPos, float refSize) {
+ float pos = (absPos - refPos) / refSize;
+
+ return getGradientColor(pos);
+ }
+
+ /**
+ * Returns a color for an arbitrary point.
+ */
+ private int getColor(float x, float y) {
+ // find the x position on the gradient vector.
+ float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2;
+ // from it get the position relative to the vector
+ float pos = (float) ((_x - mX0) / mDx);
+
+ return getGradientColor(pos);
+ }
}
}
-
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix.java b/tools/layoutlib/bridge/src/android/graphics/Matrix.java
index 3974e08..522415c 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix.java
@@ -87,8 +87,12 @@
}
public AffineTransform getTransform() {
- return new AffineTransform(mValues[0], mValues[1], mValues[2],
- mValues[3], mValues[4], mValues[5]);
+ // the AffineTransform constructor takes the value in a different order
+ // for a matrix [ 0 1 2 ]
+ // [ 3 4 5 ]
+ // the order is 0, 3, 1, 4, 2, 5...
+ return new AffineTransform(mValues[0], mValues[3], mValues[1],
+ mValues[4], mValues[2], mValues[5]);
}
public boolean hasPerspective() {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint.java b/tools/layoutlib/bridge/src/android/graphics/Paint.java
index f3af133..2d03618 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint.java
@@ -21,6 +21,7 @@
import android.text.SpannedString;
import android.text.TextUtils;
+import java.awt.BasicStroke;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.font.FontRenderContext;
@@ -59,23 +60,14 @@
private final FontRenderContext mFontContext = new FontRenderContext(
new AffineTransform(), true, true);
- @SuppressWarnings("hiding")
public static final int ANTI_ALIAS_FLAG = _Original_Paint.ANTI_ALIAS_FLAG;
- @SuppressWarnings("hiding")
public static final int FILTER_BITMAP_FLAG = _Original_Paint.FILTER_BITMAP_FLAG;
- @SuppressWarnings("hiding")
public static final int DITHER_FLAG = _Original_Paint.DITHER_FLAG;
- @SuppressWarnings("hiding")
public static final int UNDERLINE_TEXT_FLAG = _Original_Paint.UNDERLINE_TEXT_FLAG;
- @SuppressWarnings("hiding")
public static final int STRIKE_THRU_TEXT_FLAG = _Original_Paint.STRIKE_THRU_TEXT_FLAG;
- @SuppressWarnings("hiding")
public static final int FAKE_BOLD_TEXT_FLAG = _Original_Paint.FAKE_BOLD_TEXT_FLAG;
- @SuppressWarnings("hiding")
public static final int LINEAR_TEXT_FLAG = _Original_Paint.LINEAR_TEXT_FLAG;
- @SuppressWarnings("hiding")
public static final int SUBPIXEL_TEXT_FLAG = _Original_Paint.SUBPIXEL_TEXT_FLAG;
- @SuppressWarnings("hiding")
public static final int DEV_KERN_TEXT_FLAG = _Original_Paint.DEV_KERN_TEXT_FLAG;
public static class FontMetrics extends _Original_Paint.FontMetrics {
@@ -136,6 +128,19 @@
this.nativeInt = nativeInt;
}
final int nativeInt;
+
+ /** custom for layoutlib */
+ public int getJavaCap() {
+ switch (this) {
+ case BUTT:
+ return BasicStroke.CAP_BUTT;
+ case ROUND:
+ return BasicStroke.CAP_ROUND;
+ default:
+ case SQUARE:
+ return BasicStroke.CAP_SQUARE;
+ }
+ }
}
/**
@@ -160,6 +165,19 @@
this.nativeInt = nativeInt;
}
final int nativeInt;
+
+ /** custom for layoutlib */
+ public int getJavaJoin() {
+ switch (this) {
+ default:
+ case MITER:
+ return BasicStroke.JOIN_MITER;
+ case ROUND:
+ return BasicStroke.JOIN_ROUND;
+ case BEVEL:
+ return BasicStroke.JOIN_BEVEL;
+ }
+ }
}
/**
diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
index 61b693a..4409a80 100644
--- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient.java
@@ -16,47 +16,117 @@
package android.graphics;
-public class RadialGradient extends Shader {
+public class RadialGradient extends GradientShader {
- /** Create a shader that draws a radial gradient given the center and radius.
- @param x The x-coordinate of the center of the radius
- @param y The y-coordinate of the center of the radius
- @param radius Must be positive. The radius of the circle for this gradient
- @param colors The colors to be distributed between the center and edge of the circle
- @param positions May be NULL. The relative position of
- each corresponding color in the colors array. If this is NULL,
- the the colors are distributed evenly between the center and edge of the circle.
- @param tile The Shader tiling mode
- */
- public RadialGradient(float x, float y, float radius,
- int colors[], float positions[], TileMode tile) {
- if (radius <= 0) {
- throw new IllegalArgumentException("radius must be > 0");
- }
- if (colors.length < 2) {
- throw new IllegalArgumentException("needs >= 2 number of colors");
- }
- if (positions != null && colors.length != positions.length) {
- throw new IllegalArgumentException("color and position arrays must be of equal length");
- }
+ private RadialGradientPaint mPaint;
- // FIXME Implement shader
- }
+ /**
+ * Create a shader that draws a radial gradient given the center and radius.
+ *
+ * @param x The x-coordinate of the center of the radius
+ * @param y The y-coordinate of the center of the radius
+ * @param radius Must be positive. The radius of the circle for this
+ * gradient
+ * @param colors The colors to be distributed between the center and edge of
+ * the circle
+ * @param positions May be NULL. The relative position of each corresponding
+ * color in the colors array. If this is NULL, the the colors are
+ * distributed evenly between the center and edge of the circle.
+ * @param tile The Shader tiling mode
+ */
+ public RadialGradient(float x, float y, float radius, int colors[], float positions[],
+ TileMode tile) {
+ super(colors, positions);
+ if (radius <= 0) {
+ throw new IllegalArgumentException("radius must be > 0");
+ }
- /** Create a shader that draws a radial gradient given the center and radius.
- @param x The x-coordinate of the center of the radius
- @param y The y-coordinate of the center of the radius
- @param radius Must be positive. The radius of the circle for this gradient
- @param color0 The color at the center of the circle.
- @param color1 The color at the edge of the circle.
- @param tile The Shader tiling mode
- */
- public RadialGradient(float x, float y, float radius,
- int color0, int color1, TileMode tile) {
- if (radius <= 0) {
- throw new IllegalArgumentException("radius must be > 0");
- }
- // FIXME Implement shader
- }
+ mPaint = new RadialGradientPaint(x, y, radius, mColors, mPositions, tile);
+ }
+
+ /**
+ * Create a shader that draws a radial gradient given the center and radius.
+ *
+ * @param x The x-coordinate of the center of the radius
+ * @param y The y-coordinate of the center of the radius
+ * @param radius Must be positive. The radius of the circle for this
+ * gradient
+ * @param color0 The color at the center of the circle.
+ * @param color1 The color at the edge of the circle.
+ * @param tile The Shader tiling mode
+ */
+ public RadialGradient(float x, float y, float radius, int color0, int color1, TileMode tile) {
+ this(x, y, radius, new int[] { color0, color1 }, null /* positions */, tile);
+ }
+
+ @Override
+ java.awt.Paint getJavaPaint() {
+ return mPaint;
+ }
+
+ private static class RadialGradientPaint extends GradientPaint {
+
+ private final float mX;
+ private final float mY;
+ private final float mRadius;
+
+ public RadialGradientPaint(float x, float y, float radius, int[] colors, float[] positions, TileMode mode) {
+ super(colors, positions, mode);
+ mX = x;
+ mY = y;
+ mRadius = radius;
+ }
+
+ public java.awt.PaintContext createContext(
+ java.awt.image.ColorModel colorModel,
+ java.awt.Rectangle deviceBounds,
+ java.awt.geom.Rectangle2D userBounds,
+ java.awt.geom.AffineTransform xform,
+ java.awt.RenderingHints hints) {
+ precomputeGradientColors();
+ return new RadialGradientPaintContext(colorModel);
+ }
+
+ private class RadialGradientPaintContext implements java.awt.PaintContext {
+
+ private final java.awt.image.ColorModel mColorModel;
+
+ public RadialGradientPaintContext(java.awt.image.ColorModel colorModel) {
+ mColorModel = colorModel;
+ }
+
+ public void dispose() {
+ }
+
+ public java.awt.image.ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+ java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h,
+ java.awt.image.BufferedImage.TYPE_INT_ARGB);
+
+ int[] data = new int[w*h];
+
+ // compute distance from each point to the center, and figure out the distance from
+ // it.
+ int index = 0;
+ for (int iy = 0 ; iy < h ; iy++) {
+ for (int ix = 0 ; ix < w ; ix++) {
+ float _x = x + ix - mX;
+ float _y = y + iy - mY;
+ float distance = (float) Math.sqrt(_x * _x + _y * _y);
+
+ data[index++] = getGradientColor(distance / mRadius);
+ }
+ }
+
+ image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
+
+ return image.getRaster();
+ }
+
+ }
+ }
+
}
-
diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader.java b/tools/layoutlib/bridge/src/android/graphics/Shader.java
index 3a9fda5..0cc5940 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Shader.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Shader.java
@@ -16,14 +16,16 @@
package android.graphics;
+
+
/**
* Shader is the based class for objects that return horizontal spans of colors
* during drawing. A subclass of Shader is installed in a Paint calling
* paint.setShader(shader). After that any object (other than a bitmap) that is
* drawn with that paint will get its color(s) from the shader.
*/
-public class Shader {
-
+public abstract class Shader {
+
private final Matrix mMatrix = new Matrix();
public enum TileMode {
@@ -41,7 +43,7 @@
* mirror images so that adjacent images always seam
*/
MIRROR (2);
-
+
TileMode(int nativeInt) {
this.nativeInt = nativeInt;
}
@@ -57,7 +59,7 @@
if (localM != null) {
localM.set(mMatrix);
}
-
+
return !mMatrix.isIdentity();
}
@@ -73,4 +75,9 @@
mMatrix.reset();
}
}
+
+ /**
+ * Returns a java.awt.Paint object matching this shader.
+ */
+ abstract java.awt.Paint getJavaPaint();
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java
index e79e970..87036ed 100644
--- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java
+++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient.java
@@ -16,7 +16,9 @@
package android.graphics;
-public class SweepGradient extends Shader {
+public class SweepGradient extends GradientShader {
+
+ private SweepGradientPaint mPaint;
/**
* A subclass of Shader that draws a sweep gradient around a center point.
@@ -34,15 +36,9 @@
*/
public SweepGradient(float cx, float cy,
int colors[], float positions[]) {
- if (colors.length < 2) {
- throw new IllegalArgumentException("needs >= 2 number of colors");
- }
- if (positions != null && colors.length != positions.length) {
- throw new IllegalArgumentException(
- "color and position arrays must be of equal length");
- }
-
- // FIXME Implement shader
+ super(colors, positions);
+
+ mPaint = new SweepGradientPaint(cx, cy, mColors, mPositions);
}
/**
@@ -54,7 +50,91 @@
* @param color1 The color to use at the end of the sweep
*/
public SweepGradient(float cx, float cy, int color0, int color1) {
- // FIXME Implement shader
+ this(cx, cy, new int[] { color0, color1}, null /*positions*/);
}
+
+ @Override
+ java.awt.Paint getJavaPaint() {
+ return mPaint;
+ }
+
+ private static class SweepGradientPaint extends GradientPaint {
+
+ private final float mCx;
+ private final float mCy;
+
+ public SweepGradientPaint(float cx, float cy, int[] colors, float[] positions) {
+ super(colors, positions, null /*tileMode*/);
+ mCx = cx;
+ mCy = cy;
+ }
+
+ public java.awt.PaintContext createContext(
+ java.awt.image.ColorModel colorModel,
+ java.awt.Rectangle deviceBounds,
+ java.awt.geom.Rectangle2D userBounds,
+ java.awt.geom.AffineTransform xform,
+ java.awt.RenderingHints hints) {
+ precomputeGradientColors();
+ return new SweepGradientPaintContext(colorModel);
+ }
+
+ private class SweepGradientPaintContext implements java.awt.PaintContext {
+
+ private final java.awt.image.ColorModel mColorModel;
+
+ public SweepGradientPaintContext(java.awt.image.ColorModel colorModel) {
+ mColorModel = colorModel;
+ }
+
+ public void dispose() {
+ }
+
+ public java.awt.image.ColorModel getColorModel() {
+ return mColorModel;
+ }
+
+ public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
+ java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(w, h,
+ java.awt.image.BufferedImage.TYPE_INT_ARGB);
+
+ int[] data = new int[w*h];
+
+ // compute angle from each point to the center, and figure out the distance from
+ // it.
+ int index = 0;
+ for (int iy = 0 ; iy < h ; iy++) {
+ for (int ix = 0 ; ix < w ; ix++) {
+ float dx = x + ix - mCx;
+ float dy = y + iy - mCy;
+ float angle;
+ if (dx == 0) {
+ angle = (float) (dy < 0 ? 3 * Math.PI / 2 : Math.PI / 2);
+ } else if (dy == 0) {
+ angle = (float) (dx < 0 ? Math.PI : 0);
+ } else {
+ angle = (float) Math.atan(dy / dx);
+ if (dx > 0) {
+ if (dy < 0) {
+ angle += Math.PI * 2;
+ }
+ } else {
+ angle += Math.PI;
+ }
+ }
+
+ // convert to 0-1. value and get color
+ data[index++] = getGradientColor((float) (angle / (2 * Math.PI)));
+ }
+ }
+
+ image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
+
+ return image.getRaster();
+ }
+
+ }
+ }
+
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 5a13b0b..2ed8641 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -43,9 +43,11 @@
public final static String[] RENAMED_CLASSES =
new String[] {
"android.graphics.Bitmap", "android.graphics._Original_Bitmap",
+ "android.graphics.BitmapFactory", "android.graphics._Original_BitmapFactory",
"android.graphics.BitmapShader", "android.graphics._Original_BitmapShader",
"android.graphics.Canvas", "android.graphics._Original_Canvas",
"android.graphics.ComposeShader", "android.graphics._Original_ComposeShader",
+ "android.graphics.DashPathEffect", "android.graphics._Original_DashPathEffect",
"android.graphics.LinearGradient", "android.graphics._Original_LinearGradient",
"android.graphics.Matrix", "android.graphics._Original_Matrix",
"android.graphics.Paint", "android.graphics._Original_Paint",
diff --git a/tools/preload/Policy.java b/tools/preload/Policy.java
index 7a190ac..a8d761d 100644
--- a/tools/preload/Policy.java
+++ b/tools/preload/Policy.java
@@ -46,7 +46,6 @@
"com.android.phone",
"com.google.android.apps.maps.FriendService",
"com.google.android.apps.maps.LocationFriendService",
- "com.google.android.googleapps",
"com.google.process.gapps",
"android.tts"
));