diff options
137 files changed, 9294 insertions, 3465 deletions
diff --git a/CleanSpec.mk b/CleanSpec.mk index 26d8a1b11d02..1efe77c2484f 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -54,7 +54,19 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/FrameworkTest_in $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android.policy*) $(call add-clean-step, rm -rf $(TARGET_OUT_JAVA_LIBRARIES)/android.policy.jar) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) - +$(call add-clean-step, rm -f $(PRODUCT_OUT)/obj/lib/libequalizer.so) +$(call add-clean-step, rm -f $(PRODUCT_OUT)/obj/lib/libequalizertest.so) +$(call add-clean-step, rm -f $(PRODUCT_OUT)/obj/lib/libreverb.so) +$(call add-clean-step, rm -f $(PRODUCT_OUT)/obj/lib/libreverbtest.so) +$(call add-clean-step, rm -f $(PRODUCT_OUT)/symbols/system/lib/libequalizer.so) +$(call add-clean-step, rm -f $(PRODUCT_OUT)/symbols/system/lib/libequalizertest.so) +$(call add-clean-step, rm -f $(PRODUCT_OUT)/symbols/system/lib/libreverb.so) +$(call add-clean-step, rm -f $(PRODUCT_OUT)/symbols/system/lib/libreverbtest.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libequalizer_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libequalizertest_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libreverb_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libreverbtest_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/soundfx/) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST diff --git a/api/current.xml b/api/current.xml index 5cbdc0e3bec4..4dd0909218e4 100644 --- a/api/current.xml +++ b/api/current.xml @@ -38159,6 +38159,17 @@ visibility="public" > </field> +<field name="DOWNLOAD_SERVICE" + type="java.lang.String" + transient="false" + volatile="false" + value=""download"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="DROPBOX_SERVICE" type="java.lang.String" transient="false" @@ -77538,6 +77549,17 @@ visibility="public" > </method> +<method name="getMinDelay" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getName" return="java.lang.String" abstract="false" @@ -77836,6 +77858,21 @@ deprecated="not deprecated" visibility="public" > +<method name="getAltitude" + return="float" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="p0" type="float"> +</parameter> +<parameter name="p" type="float"> +</parameter> +</method> <method name="getDefaultSensor" return="android.hardware.Sensor" abstract="false" @@ -78415,6 +78452,17 @@ visibility="public" > </field> +<field name="PRESSURE_STANDARD_ATMOSPHERE" + type="float" + transient="false" + volatile="false" + value="1013.25f" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="RAW_DATA_INDEX" type="int" transient="false" @@ -92749,6 +92797,567 @@ > </field> </class> +<class name="DownloadManager" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="enqueue" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="request" type="android.net.DownloadManager.Request"> +</parameter> +</method> +<method name="openDownloadedFile" + return="android.os.ParcelFileDescriptor" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="id" type="long"> +</parameter> +<exception name="FileNotFoundException" type="java.io.FileNotFoundException"> +</exception> +</method> +<method name="query" + return="android.database.Cursor" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="query" type="android.net.DownloadManager.Query"> +</parameter> +</method> +<method name="remove" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="id" type="long"> +</parameter> +</method> +<field name="ACTION_DOWNLOAD_COMPLETE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.DOWNLOAD_COMPLETE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_NOTIFICATION_CLICKED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_BYTES_DOWNLOADED_SO_FAR" + type="java.lang.String" + transient="false" + volatile="false" + value=""bytes_so_far"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_DESCRIPTION" + type="java.lang.String" + transient="false" + volatile="false" + value=""description"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_ERROR_CODE" + type="java.lang.String" + transient="false" + volatile="false" + value=""error_code"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_ID" + type="java.lang.String" + transient="false" + volatile="false" + value=""id"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_LAST_MODIFIED_TIMESTAMP" + type="java.lang.String" + transient="false" + volatile="false" + value=""last_modified_timestamp"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_LOCAL_URI" + type="java.lang.String" + transient="false" + volatile="false" + value=""local_uri"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_MEDIA_TYPE" + type="java.lang.String" + transient="false" + volatile="false" + value=""media_type"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_STATUS" + type="java.lang.String" + transient="false" + volatile="false" + value=""status"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_TITLE" + type="java.lang.String" + transient="false" + volatile="false" + value=""title"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_TOTAL_SIZE_BYTES" + type="java.lang.String" + transient="false" + volatile="false" + value=""total_size"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="COLUMN_URI" + type="java.lang.String" + transient="false" + volatile="false" + value=""uri"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_DEVICE_NOT_FOUND" + type="int" + transient="false" + volatile="false" + value="1007" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_FILE_ERROR" + type="int" + transient="false" + volatile="false" + value="1001" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_HTTP_DATA_ERROR" + type="int" + transient="false" + volatile="false" + value="1004" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_INSUFFICIENT_SPACE" + type="int" + transient="false" + volatile="false" + value="1006" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_TOO_MANY_REDIRECTS" + type="int" + transient="false" + volatile="false" + value="1005" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_UNHANDLED_HTTP_CODE" + type="int" + transient="false" + volatile="false" + value="1002" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ERROR_UNKNOWN" + type="int" + transient="false" + volatile="false" + value="1000" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="EXTRA_DOWNLOAD_ID" + type="java.lang.String" + transient="false" + volatile="false" + value=""extra_download_id"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATUS_FAILED" + type="int" + transient="false" + volatile="false" + value="16" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATUS_PAUSED" + type="int" + transient="false" + volatile="false" + value="4" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATUS_PENDING" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATUS_RUNNING" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="STATUS_SUCCESSFUL" + type="int" + transient="false" + volatile="false" + value="8" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="DownloadManager.Query" + extends="java.lang.Object" + abstract="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="DownloadManager.Query" + type="android.net.DownloadManager.Query" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<method name="setFilterById" + return="android.net.DownloadManager.Query" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="id" type="long"> +</parameter> +</method> +<method name="setFilterByStatus" + return="android.net.DownloadManager.Query" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="flags" type="int"> +</parameter> +</method> +</class> +<class name="DownloadManager.Request" + extends="java.lang.Object" + abstract="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="DownloadManager.Request" + type="android.net.DownloadManager.Request" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +</constructor> +<method name="setAllowedNetworkTypes" + return="android.net.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="flags" type="int"> +</parameter> +</method> +<method name="setAllowedOverRoaming" + return="android.net.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="allowed" type="boolean"> +</parameter> +</method> +<method name="setDescription" + return="android.net.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="description" type="java.lang.String"> +</parameter> +</method> +<method name="setDestinationUri" + return="android.net.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +</method> +<method name="setMediaType" + return="android.net.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="mediaType" type="java.lang.String"> +</parameter> +</method> +<method name="setRequestHeader" + return="android.net.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="header" type="java.lang.String"> +</parameter> +<parameter name="value" type="java.lang.String"> +</parameter> +</method> +<method name="setShowNotification" + return="android.net.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="flags" type="int"> +</parameter> +</method> +<method name="setTitle" + return="android.net.DownloadManager.Request" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="title" type="java.lang.String"> +</parameter> +</method> +<field name="NETWORK_MOBILE" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="NETWORK_WIFI" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="NETWORK_WIMAX" + type="int" + transient="false" + volatile="false" + value="4" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="NOTIFICATION_WHEN_RUNNING" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> <class name="LocalServerSocket" extends="java.lang.Object" abstract="false" @@ -173184,6 +173793,17 @@ visibility="public" > </method> +<method name="getKeyboardType" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getMotionRange" return="android.view.InputDevice.MotionRange" abstract="false" @@ -173232,6 +173852,39 @@ <parameter name="keyCode" type="int"> </parameter> </method> +<field name="KEYBOARD_TYPE_ALPHABETIC" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="KEYBOARD_TYPE_NONE" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="KEYBOARD_TYPE_NON_ALPHABETIC" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="MOTION_RANGE_ORIENTATION" type="int" transient="false" @@ -173331,6 +173984,17 @@ visibility="public" > </field> +<field name="SOURCE_ANY" + type="int" + transient="false" + volatile="false" + value="-256" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="SOURCE_CLASS_BUTTON" type="int" transient="false" @@ -173590,6 +174254,17 @@ > <implements name="android.os.Parcelable"> </implements> +<method name="describeContents" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getDevice" return="android.view.InputDevice" abstract="false" @@ -173623,6 +174298,16 @@ visibility="public" > </method> +<field name="CREATOR" + type="android.os.Parcelable.Creator" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="mDeviceId" type="int" transient="false" @@ -174266,17 +174951,6 @@ <parameter name="newFlags" type="int"> </parameter> </method> -<method name="describeContents" - return="int" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</method> <method name="dispatch" return="boolean" abstract="false" @@ -177476,17 +178150,6 @@ <parameter name="metaState" type="int"> </parameter> </method> -<method name="describeContents" - return="int" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</method> <method name="findPointerIndex" return="int" abstract="false" diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index 6981cd6872ba..30815c353a78 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -79,6 +79,11 @@ public class ApplicationErrorReport implements Parcelable { public static final int TYPE_STRICT_MODE_VIOLATION = 4; /** + * An error report about a StrictMode violation. + */ + public static final int TYPE_RUNNING_SERVICE = 5; + + /** * Type of this report. Can be one of {@link #TYPE_NONE}, * {@link #TYPE_CRASH}, {@link #TYPE_ANR}, or {@link #TYPE_BATTERY}. */ @@ -130,6 +135,12 @@ public class ApplicationErrorReport implements Parcelable { public BatteryInfo batteryInfo; /** + * If this report is of type {@link #TYPE_RUNNING_SERVICE}, contains an instance + * of RunningServiceInfo; otherwise null. + */ + public RunningServiceInfo runningServiceInfo; + + /** * Create an uninitialized instance of {@link ApplicationErrorReport}. */ public ApplicationErrorReport() { @@ -223,6 +234,9 @@ public class ApplicationErrorReport implements Parcelable { case TYPE_BATTERY: batteryInfo.writeToParcel(dest, flags); break; + case TYPE_RUNNING_SERVICE: + runningServiceInfo.writeToParcel(dest, flags); + break; } } @@ -239,16 +253,25 @@ public class ApplicationErrorReport implements Parcelable { crashInfo = new CrashInfo(in); anrInfo = null; batteryInfo = null; + runningServiceInfo = null; break; case TYPE_ANR: anrInfo = new AnrInfo(in); crashInfo = null; batteryInfo = null; + runningServiceInfo = null; break; case TYPE_BATTERY: batteryInfo = new BatteryInfo(in); anrInfo = null; crashInfo = null; + runningServiceInfo = null; + break; + case TYPE_RUNNING_SERVICE: + batteryInfo = null; + anrInfo = null; + crashInfo = null; + runningServiceInfo = new RunningServiceInfo(in); break; } } @@ -494,6 +517,51 @@ public class ApplicationErrorReport implements Parcelable { } } + /** + * Describes a running service report. + */ + public static class RunningServiceInfo { + /** + * Duration in milliseconds that the service has been running. + */ + public long durationMillis; + + /** + * Dump of debug information about the service. + */ + public String serviceDetails; + + /** + * Create an uninitialized instance of RunningServiceInfo. + */ + public RunningServiceInfo() { + } + + /** + * Create an instance of RunningServiceInfo initialized from a Parcel. + */ + public RunningServiceInfo(Parcel in) { + durationMillis = in.readLong(); + serviceDetails = in.readString(); + } + + /** + * Save a RunningServiceInfo instance to a parcel. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(durationMillis); + dest.writeString(serviceDetails); + } + + /** + * Dump a BatteryInfo instance to a Printer. + */ + public void dump(Printer pw, String prefix) { + pw.println(prefix + "durationMillis: " + durationMillis); + pw.println(prefix + "serviceDetails: " + serviceDetails); + } + } + public static final Parcelable.Creator<ApplicationErrorReport> CREATOR = new Parcelable.Creator<ApplicationErrorReport>() { public ApplicationErrorReport createFromParcel(Parcel source) { diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 697a987d5f20..b7a750b6573e 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -400,7 +400,15 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java * start_compatibility} - * + * + * <p class="caution">Note that the system calls this on your + * service's main thread. A service's main thread is the same + * thread where UI operations place for Activities running in the + * same process. You should always avoid stalling the main + * thread's event loop. When doing long-running operations, + * network calls, or heavy disk I/O, you should kick off a new + * thread, or use {@link android.os.AsyncTask}.</p> + * * @param intent The Intent supplied to {@link android.content.Context#startService}, * as given. This may be null if the service is being restarted after * its process has gone away, and it had previously returned anything diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 5fb2aaec83d7..9b9f796a1607 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -57,6 +57,7 @@ import java.util.ArrayList; * * <p>The primary methods that need to be implemented are: * <ul> + * <li>{@link #onCreate} which is called to initialize the provider</li> * <li>{@link #query} which returns data to the caller</li> * <li>{@link #insert} which inserts new data into the content provider</li> * <li>{@link #update} which updates existing data in the content provider</li> @@ -64,8 +65,15 @@ import java.util.ArrayList; * <li>{@link #getType} which returns the MIME type of data in the content provider</li> * </ul></p> * - * <p>This class takes care of cross process calls so subclasses don't have to worry about which - * process a request is coming from.</p> + * <p class="caution">Data access methods (such as {@link #insert} and + * {@link #update}) may be called from many threads at once, and must be thread-safe. + * Other methods (such as {@link #onCreate}) are only called from the application + * main thread, and must avoid performing lengthy operations. See the method + * descriptions for their expected thread behavior.</p> + * + * <p>Requests to {@link ContentResolver} are automatically forwarded to the appropriate + * ContentProvider instance, so subclasses don't have to worry about the details of + * cross-process calls.</p> */ public abstract class ContentProvider implements ComponentCallbacks { /* @@ -81,6 +89,21 @@ public abstract class ContentProvider implements ComponentCallbacks { private Transport mTransport = new Transport(); + /** + * Construct a ContentProvider instance. Content providers must be + * <a href="{@docRoot}guide/topics/manifest/provider-element.html">declared + * in the manifest</a>, accessed with {@link ContentResolver}, and created + * automatically by the system, so applications usually do not create + * ContentProvider instances directly. + * + * <p>At construction time, the object is uninitialized, and most fields and + * methods are unavailable. Subclasses should initialize themselves in + * {@link #onCreate}, not the constructor. + * + * <p>Content providers are created on the application main thread at + * application launch time. The constructor must not perform lengthy + * operations, or application startup will be delayed. + */ public ContentProvider() { } @@ -328,8 +351,8 @@ public abstract class ContentProvider implements ComponentCallbacks { /** - * Retrieve the Context this provider is running in. Only available once - * onCreate(Map icicle) has been called -- this will be null in the + * Retrieves the Context this provider is running in. Only available once + * {@link #onCreate} has been called -- this will return null in the * constructor. */ public final Context getContext() { @@ -403,23 +426,59 @@ public abstract class ContentProvider implements ComponentCallbacks { } /** - * Called when the provider is being started. + * Implement this to initialize your content provider on startup. + * This method is called for all registered content providers on the + * application main thread at application launch time. It must not perform + * lengthy operations, or application startup will be delayed. + * + * <p>You should defer nontrivial initialization (such as opening, + * upgrading, and scanning databases) until the content provider is used + * (via {@link #query}, {@link #insert}, etc). Deferred initialization + * keeps application startup fast, avoids unnecessary work if the provider + * turns out not to be needed, and stops database errors (such as a full + * disk) from halting application launch. + * + * <p>If you use SQLite, {@link android.database.sqlite.SQLiteOpenHelper} + * is a helpful utility class that makes it easy to manage databases, + * and will automatically defer opening until first use. If you do use + * SQLiteOpenHelper, make sure to avoid calling + * {@link android.database.sqlite.SQLiteOpenHelper#getReadableDatabase} or + * {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} + * from this method. (Instead, override + * {@link android.database.sqlite.SQLiteOpenHelper#onOpen} to initialize the + * database when it is first opened.) * * @return true if the provider was successfully loaded, false otherwise */ public abstract boolean onCreate(); + /** + * {@inheritDoc} + * This method is always called on the application main thread, and must + * not perform lengthy operations. + * + * <p>The default content provider implementation does nothing. + * Override this method to take appropriate action. + * (Content providers do not usually care about things like screen + * orientation, but may want to know about locale changes.) + */ public void onConfigurationChanged(Configuration newConfig) { } - + + /** + * {@inheritDoc} + * This method is always called on the application main thread, and must + * not perform lengthy operations. + * + * <p>The default content provider implementation does nothing. + * Subclasses may override this method to take appropriate action. + */ public void onLowMemory() { } /** - * Receives a query request from a client in a local process, and - * returns a Cursor. This is called internally by the {@link ContentResolver}. - * This method can be called from multiple - * threads, as described in + * Implement this to handle query requests from clients. + * This method can be called from multiple threads, as described in * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: * Processes and Threads</a>. * <p> @@ -476,11 +535,11 @@ public abstract class ContentProvider implements ComponentCallbacks { String selection, String[] selectionArgs, String sortOrder); /** - * Return the MIME type of the data at the given URI. This should start with + * Implement this to handle requests for the MIME type of the data at the + * given URI. The returned MIME type should start with * <code>vnd.android.cursor.item</code> for a single record, * or <code>vnd.android.cursor.dir/</code> for multiple items. - * This method can be called from multiple - * threads, as described in + * This method can be called from multiple threads, as described in * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: * Processes and Threads</a>. * @@ -490,11 +549,10 @@ public abstract class ContentProvider implements ComponentCallbacks { public abstract String getType(Uri uri); /** - * Implement this to insert a new row. + * Implement this to handle requests to insert a new row. * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()} * after inserting. - * This method can be called from multiple - * threads, as described in + * This method can be called from multiple threads, as described in * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: * Processes and Threads</a>. * @param uri The content:// URI of the insertion request. @@ -504,12 +562,12 @@ public abstract class ContentProvider implements ComponentCallbacks { public abstract Uri insert(Uri uri, ContentValues values); /** - * Implement this to insert a set of new rows, or the default implementation will - * iterate over the values and call {@link #insert} on each of them. + * Override this to handle requests to insert a set of new rows, or the + * default implementation will iterate over the values and call + * {@link #insert} on each of them. * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()} * after inserting. - * This method can be called from multiple - * threads, as described in + * This method can be called from multiple threads, as described in * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: * Processes and Threads</a>. * @@ -526,13 +584,12 @@ public abstract class ContentProvider implements ComponentCallbacks { } /** - * A request to delete one or more rows. The selection clause is applied when performing - * the deletion, allowing the operation to affect multiple rows in a - * directory. + * Implement this to handle requests to delete one or more rows. + * The implementation should apply the selection clause when performing + * deletion, allowing the operation to affect multiple rows in a directory. * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyDelete()} * after deleting. - * This method can be called from multiple - * threads, as described in + * This method can be called from multiple threads, as described in * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: * Processes and Threads</a>. * @@ -549,13 +606,12 @@ public abstract class ContentProvider implements ComponentCallbacks { public abstract int delete(Uri uri, String selection, String[] selectionArgs); /** - * Update a content URI. All rows matching the optionally provided selection - * will have their columns listed as the keys in the values map with the - * values of those keys. + * Implement this to handle requests to update one or more rows. + * The implementation should update all rows matching the selection + * to set the columns according to the provided values map. * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()} * after updating. - * This method can be called from multiple - * threads, as described in + * This method can be called from multiple threads, as described in * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: * Processes and Threads</a>. * @@ -570,18 +626,15 @@ public abstract class ContentProvider implements ComponentCallbacks { String[] selectionArgs); /** - * Open a file blob associated with a content URI. - * This method can be called from multiple - * threads, as described in + * Override this to handle requests to open a file blob. + * The default implementation always throws {@link FileNotFoundException}. + * This method can be called from multiple threads, as described in * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: * Processes and Threads</a>. - * - * <p>Returns a - * ParcelFileDescriptor, from which you can obtain a - * {@link java.io.FileDescriptor} for use with - * {@link java.io.FileInputStream}, {@link java.io.FileOutputStream}, etc. - * This can be used to store large data (such as an image) associated with - * a particular piece of content. + * + * <p>This method returns a ParcelFileDescriptor, which is returned directly + * to the caller. This way large data (such as images and documents) can be + * returned without copying the content. * * <p>The returned ParcelFileDescriptor is owned by the caller, so it is * their responsibility to close it when done. That is, the implementation @@ -599,31 +652,35 @@ public abstract class ContentProvider implements ComponentCallbacks { * no file associated with the given URI or the mode is invalid. * @throws SecurityException Throws SecurityException if the caller does * not have permission to access the file. - * + * * @see #openAssetFile(Uri, String) * @see #openFileHelper(Uri, String) - */ + */ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { throw new FileNotFoundException("No files supported by provider at " + uri); } - + /** * This is like {@link #openFile}, but can be implemented by providers * that need to be able to return sub-sections of files, often assets - * inside of their .apk. Note that when implementing this your clients - * must be able to deal with such files, either directly with - * {@link ContentResolver#openAssetFileDescriptor - * ContentResolver.openAssetFileDescriptor}, or by using the higher-level + * inside of their .apk. + * This method can be called from multiple threads, as described in + * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: + * Processes and Threads</a>. + * + * <p>If you implement this, your clients must be able to deal with such + * file slices, either directly with + * {@link ContentResolver#openAssetFileDescriptor}, or by using the higher-level * {@link ContentResolver#openInputStream ContentResolver.openInputStream} * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream} * methods. - * - * <p><em>Note: if you are implementing this to return a full file, you + * + * <p class="note">If you are implementing this to return a full file, you * should create the AssetFileDescriptor with * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with - * applications that can not handle sub-sections of files.</em></p> + * applications that can not handle sub-sections of files.</p> * * @param uri The URI whose file is to be opened. * @param mode Access mode for the file. May be "r" for read-only access, @@ -735,17 +792,21 @@ public abstract class ContentProvider implements ComponentCallbacks { } /** - * Applies each of the {@link ContentProviderOperation} objects and returns an array - * of their results. Passes through OperationApplicationException, which may be thrown - * by the call to {@link ContentProviderOperation#apply}. - * If all the applications succeed then a {@link ContentProviderResult} array with the - * same number of elements as the operations will be returned. It is implementation-specific - * how many, if any, operations will have been successfully applied if a call to - * apply results in a {@link OperationApplicationException}. + * Override this to handle requests to perform a batch of operations, or the + * default implementation will iterate over the operations and call + * {@link ContentProviderOperation#apply} on each of them. + * If all calls to {@link ContentProviderOperation#apply} succeed + * then a {@link ContentProviderResult} array with as many + * elements as there were operations will be returned. If any of the calls + * fail, it is up to the implementation how many of the others take effect. + * This method can be called from multiple threads, as described in + * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: + * Processes and Threads</a>. + * * @param operations the operations to apply * @return the results of the applications - * @throws OperationApplicationException thrown if an application fails. - * See {@link ContentProviderOperation#apply} for more information. + * @throws OperationApplicationException thrown if any operation fails. + * @see ContentProviderOperation#apply */ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index e3b7731e4048..7951a302a437 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1538,7 +1538,6 @@ public abstract class Context { * {@link android.net.DownloadManager} for requesting HTTP downloads. * * @see #getSystemService - * @hide (TODO) for now */ public static final String DOWNLOAD_SERVICE = "download"; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 5dc41d2e3ade..73c401135522 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2786,6 +2786,9 @@ public class PackageParser { // For use by package manager to keep track of where it has done dexopt. public boolean mDidDexOpt; + // User set enabled state. + public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + // Additional data supplied by callers. public Object mExtras; @@ -3012,6 +3015,12 @@ public class PackageParser { } private static boolean copyNeeded(int flags, Package p, Bundle metaData) { + if (p.mSetEnabled != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { + boolean enabled = p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + if (p.applicationInfo.enabled != enabled) { + return true; + } + } if ((flags & PackageManager.GET_META_DATA) != 0 && (metaData != null || p.mAppMetaData != null)) { return true; @@ -3045,6 +3054,7 @@ public class PackageParser { if (!sCompatibilityModeEnabled) { ai.disableCompatibilityMode(); } + ai.enabled = p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; return ai; } diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java index aefbabc4da91..b4615eb6b261 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -22,10 +22,16 @@ import android.util.Log; /** * A helper class to manage database creation and version management. - * You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and + * + * <p>You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and * optionally {@link #onOpen}, and this class takes care of opening the database * if it exists, creating it if it does not, and upgrading it as necessary. * Transactions are used to make sure the database is always in a sensible state. + * + * <p>This class makes it easy for {@link android.content.ContentProvider} + * implementations to defer opening and upgrading the database until first use, + * to avoid blocking application startup with long-running database upgrades. + * * <p>For an example, see the NotePadProvider class in the NotePad sample application, * in the <em>samples/</em> directory of the SDK.</p> * @@ -48,8 +54,9 @@ public abstract class SQLiteOpenHelper { /** * Create a helper object to create, open, and/or manage a database. - * The database is not actually created or opened until one of - * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. + * This method always returns very quickly. The database is not actually + * created or opened until one of {@link #getWritableDatabase} or + * {@link #getReadableDatabase} is called. * * @param context to use to open or create the database * @param name of the database file, or null for an in-memory database @@ -68,13 +75,20 @@ public abstract class SQLiteOpenHelper { /** * Create and/or open a database that will be used for reading and writing. - * Once opened successfully, the database is cached, so you can call this - * method every time you need to write to the database. Make sure to call - * {@link #close} when you no longer need it. + * The first time this is called, the database will be opened and + * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be + * called. * - * <p>Errors such as bad permissions or a full disk may cause this operation + * <p>Once opened successfully, the database is cached, so you can + * call this method every time you need to write to the database. + * (Make sure to call {@link #close} when you no longer need the database.) + * Errors such as bad permissions or a full disk may cause this method * to fail, but future attempts may succeed if the problem is fixed.</p> * + * <p class="caution">Database upgrade may take a long time, you + * should not call this method from the application main thread, including + * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. + * * @throws SQLiteException if the database cannot be opened for writing * @return a read/write database object valid until {@link #close} is called */ @@ -151,6 +165,11 @@ public abstract class SQLiteOpenHelper { * database object will be closed and the read/write object will be returned * in the future. * + * <p class="caution">Like {@link #getWritableDatabase}, this method may + * take a long time to return, so you should not call it from the + * application main thread, including from + * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. + * * @throws SQLiteException if the database cannot be opened * @return a database object valid until {@link #getWritableDatabase} * or {@link #close} is called. @@ -229,9 +248,9 @@ public abstract class SQLiteOpenHelper { public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); /** - * Called when the database has been opened. - * Override method should check {@link SQLiteDatabase#isReadOnly} before - * updating the database. + * Called when the database has been opened. The implementation + * should check {@link SQLiteDatabase#isReadOnly} before updating the + * database. * * @param db The database. */ diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 3490ac0e7d77..b49a409ceef8 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -92,6 +92,7 @@ public class Sensor { private float mMaxRange; private float mResolution; private float mPower; + private int mMinDelay; private int mLegacyType; @@ -147,6 +148,15 @@ public class Sensor { return mPower; } + /** + * @return the minimum delay allowed between two events in microsecond + * or zero if this sensor only returns a value when the data it's measuring + * changes. + */ + public int getMinDelay() { + return mMinDelay; + } + int getHandle() { return mHandle; } diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index e6750e677705..f6d237acde4b 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -268,6 +268,10 @@ public class SensorManager public static final float MAGNETIC_FIELD_EARTH_MIN = 30.0f; + /** Standard atmosphere, or average sea-level pressure in hPa (millibar) */ + public static final float PRESSURE_STANDARD_ATMOSPHERE = 1013.25f; + + /** Maximum luminance of sunlight in lux */ public static final float LIGHT_SUNLIGHT_MAX = 120000.0f; /** luminance of sunlight in lux */ @@ -573,11 +577,11 @@ public class SensorManager // which won't get the rotated values try { sRotation = sWindowManager.watchRotation( - new IRotationWatcher.Stub() { - public void onRotationChanged(int rotation) { - SensorManager.this.onRotationChanged(rotation); + new IRotationWatcher.Stub() { + public void onRotationChanged(int rotation) { + SensorManager.this.onRotationChanged(rotation); + } } - } ); } catch (RemoteException e) { } @@ -638,7 +642,7 @@ public class SensorManager break; case Sensor.TYPE_ORIENTATION: result |= SensorManager.SENSOR_ORIENTATION | - SensorManager.SENSOR_ORIENTATION_RAW; + SensorManager.SENSOR_ORIENTATION_RAW; break; } } @@ -935,7 +939,8 @@ public class SensorManager * received faster or slower than the specified rate. Usually events * are received faster. The value must be one of * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI}, - * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST}. + * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST} + * or, the desired delay between events in microsecond. * * @return <code>true</code> if the sensor is supported and successfully * enabled. @@ -967,6 +972,7 @@ public class SensorManager * are received faster. The value must be one of * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI}, * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST}. + * or, the desired delay between events in microsecond. * * @param handler * The {@link android.os.Handler Handler} the @@ -992,16 +998,17 @@ public class SensorManager delay = 0; break; case SENSOR_DELAY_GAME: - delay = 20; + delay = 20000; break; case SENSOR_DELAY_UI: - delay = 60; + delay = 60000; break; case SENSOR_DELAY_NORMAL: - delay = 200; + delay = 200000; break; default: - return false; + delay = rate; + break; } synchronized (sListeners) { @@ -1485,7 +1492,7 @@ public class SensorManager * @see #getRotationMatrix(float[], float[], float[], float[]) * @see GeomagneticField */ - public static float[] getOrientation(float[] R, float values[]) { + public static float[] getOrientation(float[] R, float values[]) { /* * 4x4 (length=16) case: * / R[ 0] R[ 1] R[ 2] 0 \ @@ -1511,8 +1518,27 @@ public class SensorManager return values; } - /** + * Computes the Altitude in meters from the atmospheric pressure and the + * pressure at sea level. + * <p> + * Typically the atmospheric pressure is read from a + * {@link Sensor#TYPE_PRESSURE} sensor. The pressure at sea level must be + * known, usually it can be retrieved from airport databases in the + * vicinity. + * </p> + * + * @param p0 pressure at sea level + * @param p atmospheric pressure + * @return Altitude in meters + */ + public static float getAltitude(float p0, float p) { + final float coef = 1.0f / 5.255f; + return 44330.0f * (1.0f - (float)Math.pow(p/p0, coef)); + } + + + /** * {@hide} */ public void onRotationChanged(int rotation) { diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/net/DownloadManager.java index 455bd36e5a3e..1d88c18ea511 100644 --- a/core/java/android/net/DownloadManager.java +++ b/core/java/android/net/DownloadManager.java @@ -42,8 +42,6 @@ import java.util.Set; * Instances of this class should be obtained through * {@link android.content.Context#getSystemService(String)} by passing * {@link android.content.Context#DOWNLOAD_SERVICE}. - * - * @hide */ public class DownloadManager { /** diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 0afd6cd22bea..a69938889f12 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -106,7 +106,7 @@ public abstract class BatteryStats implements Parcelable { // NOTE: Update this list if you add/change any stats above. // These characters are supposed to represent "total", "last", "current", - // and "unplugged". They were shortened for effeciency sake. + // and "unplugged". They were shortened for efficiency sake. private static final String[] STAT_NAMES = { "t", "l", "c", "u" }; /** @@ -395,19 +395,23 @@ public abstract class BatteryStats implements Parcelable { public char batteryTemperature; public char batteryVoltage; + // Constants from SCREEN_BRIGHTNESS_* public static final int STATE_BRIGHTNESS_MASK = 0x000000f; public static final int STATE_BRIGHTNESS_SHIFT = 0; + // Constants from SIGNAL_STRENGTH_* public static final int STATE_SIGNAL_STRENGTH_MASK = 0x00000f0; public static final int STATE_SIGNAL_STRENGTH_SHIFT = 4; + // Constants from ServiceState.STATE_* public static final int STATE_PHONE_STATE_MASK = 0x0000f00; public static final int STATE_PHONE_STATE_SHIFT = 8; + // Constants from DATA_CONNECTION_* public static final int STATE_DATA_CONNECTION_MASK = 0x000f000; public static final int STATE_DATA_CONNECTION_SHIFT = 12; public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<30; public static final int STATE_SCREEN_ON_FLAG = 1<<29; public static final int STATE_GPS_ON_FLAG = 1<<28; - public static final int STATE_PHONE_ON_FLAG = 1<<27; + public static final int STATE_PHONE_IN_CALL_FLAG = 1<<27; public static final int STATE_PHONE_SCANNING_FLAG = 1<<26; public static final int STATE_WIFI_ON_FLAG = 1<<25; public static final int STATE_WIFI_RUNNING_FLAG = 1<<24; @@ -619,7 +623,7 @@ public abstract class BatteryStats implements Parcelable { new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged"), new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen"), new BitDescription(HistoryItem.STATE_GPS_ON_FLAG, "gps"), - new BitDescription(HistoryItem.STATE_PHONE_ON_FLAG, "phone"), + new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call"), new BitDescription(HistoryItem.STATE_PHONE_SCANNING_FLAG, "phone_scanning"), new BitDescription(HistoryItem.STATE_WIFI_ON_FLAG, "wifi"), new BitDescription(HistoryItem.STATE_WIFI_RUNNING_FLAG, "wifi_running"), @@ -717,6 +721,18 @@ public abstract class BatteryStats implements Parcelable { public abstract int getDischargeCurrentLevel(); /** + * Get the amount the battery has discharged since the stats were + * last reset after charging, as a lower-end approximation. + */ + public abstract int getLowDischargeAmountSinceCharge(); + + /** + * Get the amount the battery has discharged since the stats were + * last reset after charging, as an upper-end approximation. + */ + public abstract int getHighDischargeAmountSinceCharge(); + + /** * Returns the total, last, or current battery uptime in microseconds. * * @param curTime the elapsed realtime in microseconds. diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 59b8274a481b..f8260cababeb 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -226,9 +226,16 @@ public class Binder implements IBinder { try { fd.close(); } catch (IOException e) { + // swallowed, not propagated back to the caller } } } + // Write the StrictMode header. + if (reply != null) { + reply.writeNoException(); + } else { + StrictMode.clearGatheredViolations(); + } return true; } return false; @@ -341,12 +348,15 @@ final class BinderProxy implements IBinder { public void dump(FileDescriptor fd, String[] args) throws RemoteException { Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); data.writeFileDescriptor(fd); data.writeStringArray(args); try { - transact(DUMP_TRANSACTION, data, null, 0); + transact(DUMP_TRANSACTION, data, reply, 0); + reply.readException(); } finally { data.recycle(); + reply.recycle(); } } diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index dedc34773c91..01cc408074d0 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -26,6 +26,7 @@ interface IPowerManager void releaseWakeLock(IBinder lock, int flags); void userActivity(long when, boolean noChangeLights); void userActivityWithForce(long when, boolean noChangeLights, boolean force); + void clearUserActivityTimeout(long now, long timeout); void setPokeLock(int pokey, IBinder lock, String tag); int getSupportedWakeLockFlags(); void setStayOnSetting(int val); diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index dc9259078152..d4b0500a5ac1 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -97,7 +97,7 @@ public final class StrictMode { * via Parcel.writeNoException() (amusingly) where the caller can * choose how to react. */ - private static ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>> gatheredViolations = + private static final ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>> gatheredViolations = new ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>>() { @Override protected ArrayList<ApplicationErrorReport.CrashInfo> initialValue() { // Starts null to avoid unnecessary allocations when diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 26c167e1f013..841257f36770 100755 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -1065,6 +1065,9 @@ public class TextToSpeech { if (!mStarted) { return result; } + if (loc == null) { + return result; + } try { String language = loc.getISO3Language(); String country = loc.getISO3Country(); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 9b7b2f40fd3c..d6b92125490c 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -26,6 +26,7 @@ import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; import android.view.IWindowSession; import android.view.KeyEvent; +import android.view.InputEvent; import android.view.MotionEvent; /** @@ -50,10 +51,13 @@ interface IWindowManager boolean inputMethodClientHasFocus(IInputMethodClient client); // These can only be called when injecting events to your own window, - // or by holding the INJECT_EVENTS permission. + // or by holding the INJECT_EVENTS permission. These methods may block + // until pending input events are finished being dispatched even when 'sync' is false. + // Avoid calling these methods on your UI thread or use the 'NoWait' version instead. boolean injectKeyEvent(in KeyEvent ev, boolean sync); boolean injectPointerEvent(in MotionEvent ev, boolean sync); boolean injectTrackballEvent(in MotionEvent ev, boolean sync); + boolean injectInputEventNoWait(in InputEvent ev); // These can only be called when holding the MANAGE_APP_TOKENS permission. void pauseKeyDispatching(IBinder token); diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 21c297675052..d5b2121ea3dd 100755 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -30,11 +30,13 @@ package android.view; * As a further wrinkle, different kinds of input sources uses different coordinate systems * to describe motion events. Refer to the comments on the input source constants for * the appropriate interpretation. + * </p> */ public final class InputDevice { private int mId; private String mName; private int mSources; + private int mKeyboardType; /** * A mask for input source classes. @@ -173,6 +175,12 @@ public final class InputDevice { * @see #SOURCE_CLASS_JOYSTICK */ public static final int SOURCE_JOYSTICK_RIGHT = 0x02000000 | SOURCE_CLASS_JOYSTICK; + + /** + * A special input source constant that is used when filtering input devices + * to match devices that provide any type of input source. + */ + public static final int SOURCE_ANY = 0xffffff00; /** * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#x}. @@ -237,6 +245,22 @@ public final class InputDevice { * @see #getMotionRange */ public static final int MOTION_RANGE_ORIENTATION = 8; + + /** + * There is no keyboard. + */ + public static final int KEYBOARD_TYPE_NONE = 0; + + /** + * The keyboard is not fully alphabetic. It may be a numeric keypad or an assortment + * of buttons that are not mapped as alphabetic keys suitable for text input. + */ + public static final int KEYBOARD_TYPE_NON_ALPHABETIC = 1; + + /** + * The keyboard supports a complement of alphabetic keys. + */ + public static final int KEYBOARD_TYPE_ALPHABETIC = 2; /** * Gets information about the input device with the specified id. @@ -265,6 +289,14 @@ public final class InputDevice { } /** + * Gets the keyboard type. + * @return The keyboard type. + */ + public int getKeyboardType() { + return mKeyboardType; + } + + /** * Gets the key character map associated with this input device. * @return The key character map. */ diff --git a/core/java/android/view/InputEvent.aidl b/core/java/android/view/InputEvent.aidl new file mode 100644 index 000000000000..61acaaf1fa8d --- /dev/null +++ b/core/java/android/view/InputEvent.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android.view.InputEvent.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.view; + +parcelable InputEvent; diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java index 78b73fe95805..9afd16e8f06c 100755 --- a/core/java/android/view/InputEvent.java +++ b/core/java/android/view/InputEvent.java @@ -16,6 +16,7 @@ package android.view; +import android.os.Parcel; import android.os.Parcelable; /** @@ -25,6 +26,11 @@ public abstract class InputEvent implements Parcelable { protected int mDeviceId; protected int mSource; + /** @hide */ + protected static final int PARCEL_TOKEN_MOTION_EVENT = 1; + /** @hide */ + protected static final int PARCEL_TOKEN_KEY_EVENT = 2; + /*package*/ InputEvent() { } @@ -69,4 +75,38 @@ public abstract class InputEvent implements Parcelable { public final void setSource(int source) { mSource = source; } + + public final int describeContents() { + return 0; + } + + /** @hide */ + protected final void readBaseFromParcel(Parcel in) { + mDeviceId = in.readInt(); + mSource = in.readInt(); + } + + /** @hide */ + protected final void writeBaseToParcel(Parcel out) { + out.writeInt(mDeviceId); + out.writeInt(mSource); + } + + public static final Parcelable.Creator<InputEvent> CREATOR + = new Parcelable.Creator<InputEvent>() { + public InputEvent createFromParcel(Parcel in) { + int token = in.readInt(); + if (token == PARCEL_TOKEN_KEY_EVENT) { + return KeyEvent.createFromParcelBody(in); + } else if (token == PARCEL_TOKEN_MOTION_EVENT) { + return MotionEvent.createFromParcelBody(in); + } else { + throw new IllegalStateException("Unexpected input event type token in parcel."); + } + } + + public InputEvent[] newArray(int size) { + return new InputEvent[size]; + } + }; } diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index dd0d21a307fa..9223e17fb5a5 100755 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -1211,44 +1211,48 @@ public class KeyEvent extends InputEvent implements Parcelable { public static final Parcelable.Creator<KeyEvent> CREATOR = new Parcelable.Creator<KeyEvent>() { public KeyEvent createFromParcel(Parcel in) { - return new KeyEvent(in); + in.readInt(); // skip token, we already know this is a KeyEvent + return KeyEvent.createFromParcelBody(in); } public KeyEvent[] newArray(int size) { return new KeyEvent[size]; } }; - - public int describeContents() { - return 0; + + /** @hide */ + public static KeyEvent createFromParcelBody(Parcel in) { + return new KeyEvent(in); + } + + private KeyEvent(Parcel in) { + readBaseFromParcel(in); + + mAction = in.readInt(); + mKeyCode = in.readInt(); + mRepeatCount = in.readInt(); + mMetaState = in.readInt(); + mScanCode = in.readInt(); + mFlags = in.readInt(); + mDownTime = in.readLong(); + mEventTime = in.readLong(); } public void writeToParcel(Parcel out, int flags) { + out.writeInt(PARCEL_TOKEN_KEY_EVENT); + + writeBaseToParcel(out); + out.writeInt(mAction); out.writeInt(mKeyCode); out.writeInt(mRepeatCount); out.writeInt(mMetaState); - out.writeInt(mDeviceId); - out.writeInt(mSource); out.writeInt(mScanCode); out.writeInt(mFlags); out.writeLong(mDownTime); out.writeLong(mEventTime); } - private KeyEvent(Parcel in) { - mAction = in.readInt(); - mKeyCode = in.readInt(); - mRepeatCount = in.readInt(); - mMetaState = in.readInt(); - mDeviceId = in.readInt(); - mSource = in.readInt(); - mScanCode = in.readInt(); - mFlags = in.readInt(); - mDownTime = in.readLong(); - mEventTime = in.readLong(); - } - private native boolean native_isSystemKey(int keyCode); private native boolean native_hasDefaultAction(int keyCode); } diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 13360d9eb97a..98fe73f891d4 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -181,61 +181,61 @@ public final class MotionEvent extends InputEvent implements Parcelable { * Offset for the sample's X coordinate. * @hide */ - static public final int SAMPLE_X = 0; + static private final int SAMPLE_X = 0; /** * Offset for the sample's Y coordinate. * @hide */ - static public final int SAMPLE_Y = 1; + static private final int SAMPLE_Y = 1; /** * Offset for the sample's pressure. * @hide */ - static public final int SAMPLE_PRESSURE = 2; + static private final int SAMPLE_PRESSURE = 2; /** * Offset for the sample's size * @hide */ - static public final int SAMPLE_SIZE = 3; + static private final int SAMPLE_SIZE = 3; /** * Offset for the sample's touch major axis length. * @hide */ - static public final int SAMPLE_TOUCH_MAJOR = 4; + static private final int SAMPLE_TOUCH_MAJOR = 4; /** * Offset for the sample's touch minor axis length. * @hide */ - static public final int SAMPLE_TOUCH_MINOR = 5; + static private final int SAMPLE_TOUCH_MINOR = 5; /** * Offset for the sample's tool major axis length. * @hide */ - static public final int SAMPLE_TOOL_MAJOR = 6; + static private final int SAMPLE_TOOL_MAJOR = 6; /** * Offset for the sample's tool minor axis length. * @hide */ - static public final int SAMPLE_TOOL_MINOR = 7; + static private final int SAMPLE_TOOL_MINOR = 7; /** * Offset for the sample's orientation. * @hide */ - static public final int SAMPLE_ORIENTATION = 8; + static private final int SAMPLE_ORIENTATION = 8; /** * Number of data items for each sample. * @hide */ - static public final int NUM_SAMPLE_DATA = 9; + static private final int NUM_SAMPLE_DATA = 9; /** * Number of possible pointers. @@ -918,7 +918,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * upwards, is perfectly circular or is of unknown orientation. A positive angle * indicates that the major axis of contact is oriented to the right. A negative angle * indicates that the major axis of contact is oriented to the left. - * The full range is from -PI/4 radians (finger pointing fully left) to PI/4 radians + * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians * (finger pointing fully right). * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 * (the first pointer that is down) to {@link #getPointerCount()}-1. @@ -1453,43 +1453,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { public static final Parcelable.Creator<MotionEvent> CREATOR = new Parcelable.Creator<MotionEvent>() { public MotionEvent createFromParcel(Parcel in) { - final int NP = in.readInt(); - final int NS = in.readInt(); - final int NI = NP * NS * NUM_SAMPLE_DATA; - - MotionEvent ev = obtain(NP, NS); - ev.mNumPointers = NP; - ev.mNumSamples = NS; - - ev.mDownTimeNano = in.readLong(); - ev.mAction = in.readInt(); - ev.mXOffset = in.readFloat(); - ev.mYOffset = in.readFloat(); - ev.mXPrecision = in.readFloat(); - ev.mYPrecision = in.readFloat(); - ev.mDeviceId = in.readInt(); - ev.mSource = in.readInt(); - ev.mEdgeFlags = in.readInt(); - ev.mMetaState = in.readInt(); - - final int[] pointerIdentifiers = ev.mPointerIdentifiers; - for (int i = 0; i < NP; i++) { - pointerIdentifiers[i] = in.readInt(); - } - - final long[] eventTimeNanoSamples = ev.mEventTimeNanoSamples; - for (int i = 0; i < NS; i++) { - eventTimeNanoSamples[i] = in.readLong(); - } - - final float[] dataSamples = ev.mDataSamples; - for (int i = 0; i < NI; i++) { - dataSamples[i] = in.readFloat(); - } - - ev.mLastEventTimeNanoSampleIndex = NS - 1; - ev.mLastDataSampleIndex = (NS - 1) * NP * NUM_SAMPLE_DATA; - return ev; + in.readInt(); // skip token, we already know this is a MotionEvent + return MotionEvent.createFromParcelBody(in); } public MotionEvent[] newArray(int size) { @@ -1497,11 +1462,50 @@ public final class MotionEvent extends InputEvent implements Parcelable { } }; - public int describeContents() { - return 0; - } + /** @hide */ + public static MotionEvent createFromParcelBody(Parcel in) { + final int NP = in.readInt(); + final int NS = in.readInt(); + final int NI = NP * NS * NUM_SAMPLE_DATA; + + MotionEvent ev = obtain(NP, NS); + ev.mNumPointers = NP; + ev.mNumSamples = NS; + + ev.readBaseFromParcel(in); + + ev.mDownTimeNano = in.readLong(); + ev.mAction = in.readInt(); + ev.mXOffset = in.readFloat(); + ev.mYOffset = in.readFloat(); + ev.mXPrecision = in.readFloat(); + ev.mYPrecision = in.readFloat(); + ev.mEdgeFlags = in.readInt(); + ev.mMetaState = in.readInt(); + + final int[] pointerIdentifiers = ev.mPointerIdentifiers; + for (int i = 0; i < NP; i++) { + pointerIdentifiers[i] = in.readInt(); + } + + final long[] eventTimeNanoSamples = ev.mEventTimeNanoSamples; + for (int i = 0; i < NS; i++) { + eventTimeNanoSamples[i] = in.readLong(); + } + final float[] dataSamples = ev.mDataSamples; + for (int i = 0; i < NI; i++) { + dataSamples[i] = in.readFloat(); + } + + ev.mLastEventTimeNanoSampleIndex = NS - 1; + ev.mLastDataSampleIndex = (NS - 1) * NP * NUM_SAMPLE_DATA; + return ev; + } + public void writeToParcel(Parcel out, int flags) { + out.writeInt(PARCEL_TOKEN_MOTION_EVENT); + final int NP = mNumPointers; final int NS = mNumSamples; final int NI = NP * NS * NUM_SAMPLE_DATA; @@ -1509,14 +1513,14 @@ public final class MotionEvent extends InputEvent implements Parcelable { out.writeInt(NP); out.writeInt(NS); + writeBaseToParcel(out); + out.writeLong(mDownTimeNano); out.writeInt(mAction); out.writeFloat(mXOffset); out.writeFloat(mYOffset); out.writeFloat(mXPrecision); out.writeFloat(mYPrecision); - out.writeInt(mDeviceId); - out.writeInt(mSource); out.writeInt(mEdgeFlags); out.writeInt(mMetaState); @@ -1614,28 +1618,28 @@ public final class MotionEvent extends InputEvent implements Parcelable { * upwards, is perfectly circular or is of unknown orientation. A positive angle * indicates that the major axis of contact is oriented to the right. A negative angle * indicates that the major axis of contact is oriented to the left. - * The full range is from -PI/4 radians (finger pointing fully left) to PI/4 radians + * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians * (finger pointing fully right). */ public float orientation; /* - private static final float PI_8 = (float) (Math.PI / 8); + private static final float PI_4 = (float) (Math.PI / 4); public float getTouchWidth() { - return Math.abs(orientation) > PI_8 ? touchMajor : touchMinor; + return Math.abs(orientation) > PI_4 ? touchMajor : touchMinor; } public float getTouchHeight() { - return Math.abs(orientation) > PI_8 ? touchMinor : touchMajor; + return Math.abs(orientation) > PI_4 ? touchMinor : touchMajor; } public float getToolWidth() { - return Math.abs(orientation) > PI_8 ? toolMajor : toolMinor; + return Math.abs(orientation) > PI_4 ? toolMajor : toolMinor; } public float getToolHeight() { - return Math.abs(orientation) > PI_8 ? toolMinor : toolMajor; + return Math.abs(orientation) > PI_4 ? toolMinor : toolMajor; } */ } diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java index 784a75f22a7c..4cb0839036f2 100644 --- a/core/java/android/widget/Scroller.java +++ b/core/java/android/widget/Scroller.java @@ -218,7 +218,11 @@ public class Scroller { // Pin to mMinY <= mCurrY <= mMaxY mCurrY = Math.min(mCurrY, mMaxY); mCurrY = Math.max(mCurrY, mMinY); - + + if (mCurrX == mFinalX && mCurrY == mFinalY) { + mFinished = true; + } + break; } } diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java index a96253bb4bc4..d1aff2a0bfb8 100644 --- a/core/java/com/android/internal/app/ShutdownThread.java +++ b/core/java/com/android/internal/app/ShutdownThread.java @@ -84,7 +84,7 @@ public final class ShutdownThread extends Thread { public static void shutdown(final Context context, boolean confirm) { // ensure that only one thread is trying to power down. // any additional calls are just returned - synchronized (sIsStartedGuard){ + synchronized (sIsStartedGuard) { if (sIsStarted) { Log.d(TAG, "Request to shutdown already running, returning."); return; @@ -133,6 +133,10 @@ public final class ShutdownThread extends Thread { private static void beginShutdownSequence(Context context) { synchronized (sIsStartedGuard) { + if (sIsStarted) { + Log.d(TAG, "Request to shutdown already running, returning."); + return; + } sIsStarted = true; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 642c313eec11..b3323e9aca8d 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -63,7 +63,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 48; + private static final int VERSION = 49; // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 1000; @@ -184,6 +184,8 @@ public final class BatteryStatsImpl extends BatteryStats { int mDischargeStartLevel; int mDischargeUnplugLevel; int mDischargeCurrentLevel; + int mLowDischargeAmountSinceCharge; + int mHighDischargeAmountSinceCharge; long mLastWriteTime = 0; // Milliseconds @@ -260,7 +262,7 @@ public final class BatteryStatsImpl extends BatteryStats { mPluggedCount = in.readInt(); mCount.set(mPluggedCount); mLoadedCount = in.readInt(); - mLastCount = in.readInt(); + mLastCount = 0; mUnpluggedCount = in.readInt(); unpluggables.add(this); } @@ -273,7 +275,6 @@ public final class BatteryStatsImpl extends BatteryStats { public void writeToParcel(Parcel out) { out.writeInt(mCount.get()); out.writeInt(mLoadedCount); - out.writeInt(mLastCount); out.writeInt(mUnpluggedCount); } @@ -348,13 +349,12 @@ public final class BatteryStatsImpl extends BatteryStats { void writeSummaryFromParcelLocked(Parcel out) { int count = mCount.get(); out.writeInt(count); - out.writeInt(count - mLoadedCount); } void readSummaryFromParcelLocked(Parcel in) { mLoadedCount = in.readInt(); mCount.set(mLoadedCount); - mLastCount = in.readInt(); + mLastCount = 0; mUnpluggedCount = mPluggedCount = mLoadedCount; } } @@ -428,11 +428,11 @@ public final class BatteryStatsImpl extends BatteryStats { mCount = in.readInt(); mLoadedCount = in.readInt(); - mLastCount = in.readInt(); + mLastCount = 0; mUnpluggedCount = in.readInt(); mTotalTime = in.readLong(); mLoadedTime = in.readLong(); - mLastTime = in.readLong(); + mLastTime = 0; mUnpluggedTime = in.readLong(); unpluggables.add(this); } @@ -467,11 +467,9 @@ public final class BatteryStatsImpl extends BatteryStats { public void writeToParcel(Parcel out, long batteryRealtime) { out.writeInt(mCount); out.writeInt(mLoadedCount); - out.writeInt(mLastCount); out.writeInt(mUnpluggedCount); out.writeLong(computeRunTimeLocked(batteryRealtime)); out.writeLong(mLoadedTime); - out.writeLong(mLastTime); out.writeLong(mUnpluggedTime); } @@ -569,18 +567,16 @@ public final class BatteryStatsImpl extends BatteryStats { long runTime = computeRunTimeLocked(batteryRealtime); // Divide by 1000 for backwards compatibility out.writeLong((runTime + 500) / 1000); - out.writeLong(((runTime - mLoadedTime) + 500) / 1000); out.writeInt(mCount); - out.writeInt(mCount - mLoadedCount); } void readSummaryFromParcelLocked(Parcel in) { // Multiply by 1000 for backwards compatibility mTotalTime = mLoadedTime = in.readLong() * 1000; - mLastTime = in.readLong() * 1000; + mLastTime = 0; mUnpluggedTime = mTotalTime; mCount = mLoadedCount = in.readInt(); - mLastCount = in.readInt(); + mLastCount = 0; mUnpluggedCount = mCount; } } @@ -1243,7 +1239,6 @@ public final class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(SystemClock.elapsedRealtime()); - mGpsNesting++; mScreenOn = true; mScreenOnTimer.startRunningLocked(this); if (mScreenBrightnessBin >= 0) { @@ -1297,7 +1292,7 @@ public final class BatteryStatsImpl extends BatteryStats { public void notePhoneOnLocked() { if (!mPhoneOn) { - mHistoryCur.states |= HistoryItem.STATE_PHONE_ON_FLAG; + mHistoryCur.states |= HistoryItem.STATE_PHONE_IN_CALL_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(SystemClock.elapsedRealtime()); @@ -1308,7 +1303,7 @@ public final class BatteryStatsImpl extends BatteryStats { public void notePhoneOffLocked() { if (mPhoneOn) { - mHistoryCur.states &= ~HistoryItem.STATE_PHONE_ON_FLAG; + mHistoryCur.states &= ~HistoryItem.STATE_PHONE_IN_CALL_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(SystemClock.elapsedRealtime()); @@ -1317,38 +1312,43 @@ public final class BatteryStatsImpl extends BatteryStats { } } + void stopAllSignalStrengthTimersLocked(int except) { + for (int i = 0; i < NUM_SIGNAL_STRENGTH_BINS; i++) { + if (i == except) { + continue; + } + while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) { + mPhoneSignalStrengthsTimer[i].stopRunningLocked(this); + } + } + } + /** * Telephony stack updates the phone state. * @param state phone state from ServiceState.getState() */ public void notePhoneStateLocked(int state) { + boolean scanning = false; + int bin = mPhoneSignalStrengthBin; - boolean isAirplaneMode = state == ServiceState.STATE_POWER_OFF; - // Stop all timers - if (isAirplaneMode || state == ServiceState.STATE_OUT_OF_SERVICE) { - for (int i = 0; i < NUM_SIGNAL_STRENGTH_BINS; i++) { - while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) { - mPhoneSignalStrengthsTimer[i].stopRunningLocked(this); - } - } - } - // Stop Signal Scanning timer, in case we're going into service - if (mPhoneSignalScanningTimer.isRunningLocked()) { - mHistoryCur.states &= ~HistoryItem.STATE_PHONE_SCANNING_FLAG; - if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: " - + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(SystemClock.elapsedRealtime()); - mPhoneSignalScanningTimer.stopRunningLocked(this); - } + + // If the phone is powered off, stop all timers. + if (state == ServiceState.STATE_POWER_OFF) { + stopAllSignalStrengthTimersLocked(-1); // If we're back in service or continuing in service, restart the old timer. - if (state == ServiceState.STATE_IN_SERVICE) { + } if (state == ServiceState.STATE_IN_SERVICE) { if (bin == -1) bin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; if (!mPhoneSignalStrengthsTimer[bin].isRunningLocked()) { mPhoneSignalStrengthsTimer[bin].startRunningLocked(this); } + + // If we're out of service, we are in the lowest signal strength + // bin and have the scanning bit set. } else if (state == ServiceState.STATE_OUT_OF_SERVICE) { + scanning = true; mPhoneSignalStrengthBin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + stopAllSignalStrengthTimersLocked(mPhoneSignalStrengthBin); if (!mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].isRunningLocked()) { mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].startRunningLocked(this); } @@ -1361,6 +1361,17 @@ public final class BatteryStatsImpl extends BatteryStats { } } + if (!scanning) { + // If we are no longer scanning, then stop the scanning timer. + if (mPhoneSignalScanningTimer.isRunningLocked()) { + mHistoryCur.states &= ~HistoryItem.STATE_PHONE_SCANNING_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + mPhoneSignalScanningTimer.stopRunningLocked(this); + } + } + if (mPhoneServiceState != state) { mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_STATE_MASK) | (state << HistoryItem.STATE_PHONE_STATE_SHIFT); @@ -2622,10 +2633,6 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(mLoadedSystemTime); out.writeLong(mLoadedForegroundTime); out.writeInt(mLoadedStarts); - out.writeLong(mLastUserTime); - out.writeLong(mLastSystemTime); - out.writeLong(mLastForegroundTime); - out.writeInt(mLastStarts); out.writeLong(mUnpluggedUserTime); out.writeLong(mUnpluggedSystemTime); out.writeLong(mUnpluggedForegroundTime); @@ -2652,10 +2659,10 @@ public final class BatteryStatsImpl extends BatteryStats { mLoadedSystemTime = in.readLong(); mLoadedForegroundTime = in.readLong(); mLoadedStarts = in.readInt(); - mLastUserTime = in.readLong(); - mLastSystemTime = in.readLong(); - mLastForegroundTime = in.readLong(); - mLastStarts = in.readInt(); + mLastUserTime = 0; + mLastSystemTime = 0; + mLastForegroundTime = 0; + mLastStarts = 0; mUnpluggedUserTime = in.readLong(); mUnpluggedSystemTime = in.readLong(); mUnpluggedForegroundTime = in.readLong(); @@ -2828,7 +2835,7 @@ public final class BatteryStatsImpl extends BatteryStats { void readFromParcelLocked(Parcel in) { mWakeups = in.readInt(); mLoadedWakeups = in.readInt(); - mLastWakeups = in.readInt(); + mLastWakeups = 0; mUnpluggedWakeups = in.readInt(); int numServs = in.readInt(); @@ -2845,7 +2852,6 @@ public final class BatteryStatsImpl extends BatteryStats { void writeToParcelLocked(Parcel out) { out.writeInt(mWakeups); out.writeInt(mLoadedWakeups); - out.writeInt(mLastWakeups); out.writeInt(mUnpluggedWakeups); out.writeInt(mServiceStats.size()); @@ -3002,9 +3008,9 @@ public final class BatteryStatsImpl extends BatteryStats { mLoadedStartTime = in.readLong(); mLoadedStarts = in.readInt(); mLoadedLaunches = in.readInt(); - mLastStartTime = in.readLong(); - mLastStarts = in.readInt(); - mLastLaunches = in.readInt(); + mLastStartTime = 0; + mLastStarts = 0; + mLastLaunches = 0; mUnpluggedStartTime = in.readLong(); mUnpluggedStarts = in.readInt(); mUnpluggedLaunches = in.readInt(); @@ -3022,9 +3028,6 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(mLoadedStartTime); out.writeInt(mLoadedStarts); out.writeInt(mLoadedLaunches); - out.writeLong(mLastStartTime); - out.writeInt(mLastStarts); - out.writeInt(mLastLaunches); out.writeLong(mUnpluggedStartTime); out.writeInt(mUnpluggedStarts); out.writeInt(mUnpluggedLaunches); @@ -3336,6 +3339,8 @@ public final class BatteryStatsImpl extends BatteryStats { mDischargeStartLevel = 0; mDischargeUnplugLevel = 0; mDischargeCurrentLevel = 0; + mLowDischargeAmountSinceCharge = 0; + mHighDischargeAmountSinceCharge = 0; } public BatteryStatsImpl(Parcel p) { @@ -3434,6 +3439,8 @@ public final class BatteryStatsImpl extends BatteryStats { doWrite = true; resetAllStatsLocked(); mDischargeStartLevel = level; + mLowDischargeAmountSinceCharge = 0; + mHighDischargeAmountSinceCharge = 0; } updateKernelWakelocksLocked(); mHistoryCur.batteryLevel = (byte)level; @@ -3457,6 +3464,10 @@ public final class BatteryStatsImpl extends BatteryStats { mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart; mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart; mDischargeCurrentLevel = level; + if (level < mDischargeUnplugLevel) { + mLowDischargeAmountSinceCharge = mDischargeUnplugLevel-level-1; + mHighDischargeAmountSinceCharge = mDischargeUnplugLevel-level; + } doPlugLocked(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime)); } if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) { @@ -3723,6 +3734,20 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override + public int getLowDischargeAmountSinceCharge() { + synchronized(this) { + return mLowDischargeAmountSinceCharge; + } + } + + @Override + public int getHighDischargeAmountSinceCharge() { + synchronized(this) { + return mHighDischargeAmountSinceCharge; + } + } + + @Override public int getCpuSpeedSteps() { return sNumSpeedSteps; } @@ -3926,15 +3951,13 @@ public final class BatteryStatsImpl extends BatteryStats { mStartCount = in.readInt(); mBatteryUptime = in.readLong(); - mBatteryLastUptime = in.readLong(); mBatteryRealtime = in.readLong(); - mBatteryLastRealtime = in.readLong(); mUptime = in.readLong(); - mLastUptime = in.readLong(); mRealtime = in.readLong(); - mLastRealtime = in.readLong(); mDischargeUnplugLevel = in.readInt(); mDischargeCurrentLevel = in.readInt(); + mLowDischargeAmountSinceCharge = in.readInt(); + mHighDischargeAmountSinceCharge = in.readInt(); mStartCount++; @@ -4058,11 +4081,8 @@ public final class BatteryStatsImpl extends BatteryStats { String procName = in.readString(); Uid.Proc p = u.getProcessStatsLocked(procName); p.mUserTime = p.mLoadedUserTime = in.readLong(); - p.mLastUserTime = in.readLong(); p.mSystemTime = p.mLoadedSystemTime = in.readLong(); - p.mLastSystemTime = in.readLong(); p.mStarts = p.mLoadedStarts = in.readInt(); - p.mLastStarts = in.readInt(); } NP = in.readInt(); @@ -4074,17 +4094,13 @@ public final class BatteryStatsImpl extends BatteryStats { String pkgName = in.readString(); Uid.Pkg p = u.getPackageStatsLocked(pkgName); p.mWakeups = p.mLoadedWakeups = in.readInt(); - p.mLastWakeups = in.readInt(); final int NS = in.readInt(); for (int is = 0; is < NS; is++) { String servName = in.readString(); Uid.Pkg.Serv s = u.getServiceStatsLocked(pkgName, servName); s.mStartTime = s.mLoadedStartTime = in.readLong(); - s.mLastStartTime = in.readLong(); s.mStarts = s.mLoadedStarts = in.readInt(); - s.mLastStarts = in.readInt(); s.mLaunches = s.mLoadedLaunches = in.readInt(); - s.mLastLaunches = in.readInt(); } } @@ -4111,16 +4127,13 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mStartCount); out.writeLong(computeBatteryUptime(NOW_SYS, STATS_SINCE_CHARGED)); - out.writeLong(computeBatteryUptime(NOW_SYS, STATS_CURRENT)); out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED)); - out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_CURRENT)); out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED)); - out.writeLong(computeUptime(NOW_SYS, STATS_CURRENT)); out.writeLong(computeRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED)); - out.writeLong(computeRealtime(NOWREAL_SYS, STATS_CURRENT)); out.writeInt(mDischargeUnplugLevel); out.writeInt(mDischargeCurrentLevel); - + out.writeInt(mLowDischargeAmountSinceCharge); + out.writeInt(mHighDischargeAmountSinceCharge); mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { @@ -4256,11 +4269,8 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeString(ent.getKey()); Uid.Proc ps = ent.getValue(); out.writeLong(ps.mUserTime); - out.writeLong(ps.mUserTime - ps.mLoadedUserTime); out.writeLong(ps.mSystemTime); - out.writeLong(ps.mSystemTime - ps.mLoadedSystemTime); out.writeInt(ps.mStarts); - out.writeInt(ps.mStarts - ps.mLoadedStarts); } } @@ -4272,7 +4282,6 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeString(ent.getKey()); Uid.Pkg ps = ent.getValue(); out.writeInt(ps.mWakeups); - out.writeInt(ps.mWakeups - ps.mLoadedWakeups); final int NS = ps.mServiceStats.size(); out.writeInt(NS); if (NS > 0) { @@ -4282,11 +4291,8 @@ public final class BatteryStatsImpl extends BatteryStats { BatteryStatsImpl.Uid.Pkg.Serv ss = sent.getValue(); long time = ss.getStartTimeToNowLocked(NOW); out.writeLong(time); - out.writeLong(time - ss.mLoadedStartTime); out.writeInt(ss.mStarts); - out.writeInt(ss.mStarts - ss.mLoadedStarts); out.writeInt(ss.mLaunches); - out.writeInt(ss.mLaunches - ss.mLoadedLaunches); } } } @@ -4311,9 +4317,9 @@ public final class BatteryStatsImpl extends BatteryStats { mStartCount = in.readInt(); mBatteryUptime = in.readLong(); - mBatteryLastUptime = in.readLong(); + mBatteryLastUptime = 0; mBatteryRealtime = in.readLong(); - mBatteryLastRealtime = in.readLong(); + mBatteryLastRealtime = 0; mScreenOn = false; mScreenOnTimer = new StopwatchTimer(-1, null, mUnpluggables, in); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { @@ -4337,10 +4343,10 @@ public final class BatteryStatsImpl extends BatteryStats { mBluetoothOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in); mUptime = in.readLong(); mUptimeStart = in.readLong(); - mLastUptime = in.readLong(); + mLastUptime = 0; mRealtime = in.readLong(); mRealtimeStart = in.readLong(); - mLastRealtime = in.readLong(); + mLastRealtime = 0; mOnBattery = in.readInt() != 0; mOnBatteryInternal = false; // we are no longer really running. mTrackBatteryPastUptime = in.readLong(); @@ -4351,6 +4357,8 @@ public final class BatteryStatsImpl extends BatteryStats { mUnpluggedBatteryRealtime = in.readLong(); mDischargeUnplugLevel = in.readInt(); mDischargeCurrentLevel = in.readInt(); + mLowDischargeAmountSinceCharge = in.readInt(); + mHighDischargeAmountSinceCharge = in.readInt(); mLastWriteTime = in.readLong(); mMobileDataRx[STATS_LAST] = in.readLong(); @@ -4396,11 +4404,15 @@ public final class BatteryStatsImpl extends BatteryStats { } public void writeToParcel(Parcel out, int flags) { - writeToParcelLocked(out, flags); + writeToParcelLocked(out, true, flags); + } + + public void writeToParcelWithoutUids(Parcel out, int flags) { + writeToParcelLocked(out, false, flags); } @SuppressWarnings("unused") - void writeToParcelLocked(Parcel out, int flags) { + void writeToParcelLocked(Parcel out, boolean inclUids, int flags) { final long uSecUptime = SystemClock.uptimeMillis() * 1000; final long uSecRealtime = SystemClock.elapsedRealtime() * 1000; final long batteryUptime = getBatteryUptimeLocked(uSecUptime); @@ -4412,9 +4424,7 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mStartCount); out.writeLong(mBatteryUptime); - out.writeLong(mBatteryLastUptime); out.writeLong(mBatteryRealtime); - out.writeLong(mBatteryLastRealtime); mScreenOnTimer.writeToParcel(out, batteryRealtime); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { mScreenBrightnessTimer[i].writeToParcel(out, batteryRealtime); @@ -4433,10 +4443,8 @@ public final class BatteryStatsImpl extends BatteryStats { mBluetoothOnTimer.writeToParcel(out, batteryRealtime); out.writeLong(mUptime); out.writeLong(mUptimeStart); - out.writeLong(mLastUptime); out.writeLong(mRealtime); out.writeLong(mRealtimeStart); - out.writeLong(mLastRealtime); out.writeInt(mOnBattery ? 1 : 0); out.writeLong(batteryUptime); out.writeLong(mTrackBatteryUptimeStart); @@ -4446,6 +4454,8 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(mUnpluggedBatteryRealtime); out.writeInt(mDischargeUnplugLevel); out.writeInt(mDischargeCurrentLevel); + out.writeInt(mLowDischargeAmountSinceCharge); + out.writeInt(mHighDischargeAmountSinceCharge); out.writeLong(mLastWriteTime); out.writeLong(getMobileTcpBytesReceived(STATS_SINCE_UNPLUGGED)); @@ -4458,27 +4468,35 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(getBluetoothPingCount()); - out.writeInt(mKernelWakelockStats.size()); - for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { - SamplingTimer kwlt = ent.getValue(); - if (kwlt != null) { - out.writeInt(1); - out.writeString(ent.getKey()); - Timer.writeTimerToParcel(out, kwlt, batteryRealtime); - } else { - out.writeInt(0); + if (inclUids) { + out.writeInt(mKernelWakelockStats.size()); + for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { + SamplingTimer kwlt = ent.getValue(); + if (kwlt != null) { + out.writeInt(1); + out.writeString(ent.getKey()); + Timer.writeTimerToParcel(out, kwlt, batteryRealtime); + } else { + out.writeInt(0); + } } + } else { + out.writeInt(0); } out.writeInt(sNumSpeedSteps); - int size = mUidStats.size(); - out.writeInt(size); - for (int i = 0; i < size; i++) { - out.writeInt(mUidStats.keyAt(i)); - Uid uid = mUidStats.valueAt(i); + if (inclUids) { + int size = mUidStats.size(); + out.writeInt(size); + for (int i = 0; i < size; i++) { + out.writeInt(mUidStats.keyAt(i)); + Uid uid = mUidStats.valueAt(i); - uid.writeToParcelLocked(out, batteryRealtime); + uid.writeToParcelLocked(out, batteryRealtime); + } + } else { + out.writeInt(0); } } diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index 7b2341813099..e29495cf0543 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -38,6 +38,7 @@ struct SensorOffsets jfieldID range; jfieldID resolution; jfieldID power; + jfieldID minDelay; } gSensorOffsets; /* @@ -74,6 +75,7 @@ sensors_module_get_next_sensor(JNIEnv *env, jobject clazz, jobject sensor, jint env->SetFloatField(sensor, sensorOffsets.range, list->getMaxValue()); env->SetFloatField(sensor, sensorOffsets.resolution, list->getResolution()); env->SetFloatField(sensor, sensorOffsets.power, list->getPowerUsage()); + env->SetIntField(sensor, sensorOffsets.minDelay, list->getMinDelay()); next++; return next<count ? next : 0; @@ -154,6 +156,7 @@ nativeClassInit (JNIEnv *_env, jclass _this) sensorOffsets.range = _env->GetFieldID(sensorClass, "mMaxRange", "F"); sensorOffsets.resolution = _env->GetFieldID(sensorClass, "mResolution","F"); sensorOffsets.power = _env->GetFieldID(sensorClass, "mPower", "F"); + sensorOffsets.minDelay = _env->GetFieldID(sensorClass, "mMinDelay", "I"); } static JNINativeMethod gMethods[] = { diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 040b32494def..5c4e4fd971dc 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -1566,15 +1566,15 @@ static void android_os_Parcel_enforceInterface(JNIEnv* env, jobject clazz, jstri if (parcel != NULL) { const jchar* str = env->GetStringCritical(name, 0); if (str) { - const int32_t old_strict_policy = - IPCThreadState::self()->getStrictModePolicy(); - int32_t strict_policy; - bool isValid = parcel->enforceInterface( + IPCThreadState* threadState = IPCThreadState::self(); + const int32_t oldPolicy = threadState->getStrictModePolicy(); + const bool isValid = parcel->enforceInterface( String16(str, env->GetStringLength(name)), - &strict_policy); + threadState); env->ReleaseStringCritical(name, str); if (isValid) { - if (old_strict_policy != strict_policy) { + const int32_t newPolicy = threadState->getStrictModePolicy(); + if (oldPolicy != newPolicy) { // Need to keep the Java-level thread-local strict // mode policy in sync for the libcore // enforcements, which involves an upcall back @@ -1582,7 +1582,7 @@ static void android_os_Parcel_enforceInterface(JNIEnv* env, jobject clazz, jstri // Parcel.enforceInterface signature, as it's // pseudo-public, and used via AIDL // auto-generation...) - set_dalvik_blockguard_policy(env, strict_policy); + set_dalvik_blockguard_policy(env, newPolicy); } return; // everything was correct -> return silently } diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index 19f0f1df950b..d0318cfba096 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -371,6 +371,9 @@ <span class="zh-CN" style="display:none">应用程序版本控制</span> <span class="zh-TW" style="display:none">應用程式版本設定</span> </a></li> + <li><a href="<?cs var:toroot ?>guide/publishing/licensing.html"> + <span class="en">Licensing Your Applications</span> + </a> <span class="new">new!</span></li> <li><a href="<?cs var:toroot ?>guide/publishing/preparing.html"> <span class="en">Preparing to Publish</span> <span class="de" style="display:none">Vorbereitung auf die Veröffentlichung</span> diff --git a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd index 05f61beb86e0..c8d241c68ed7 100644 --- a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd +++ b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd @@ -256,8 +256,8 @@ page.title=Activity and Task Design Guidelines to be able go to a subsequent screen B and then use the BACK key to go back to screen A, then the screen A needs to be implemented as an activity. The one exception to this rule is if your application - <a href=#taking_over_back_key title="takes control of the BACK key" - takes control of the BACK key</a> and manages the navigation itself. + <a href="#taking_over_back_key">takes control of the BACK key</a> and manages the navigation +itself. </p> diff --git a/docs/html/guide/publishing/licensing.jd b/docs/html/guide/publishing/licensing.jd new file mode 100644 index 000000000000..e3b913585354 --- /dev/null +++ b/docs/html/guide/publishing/licensing.jd @@ -0,0 +1,2373 @@ +page.title=Licensing Your Applications +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + + <h2>Market Licensing quickview: </h2> + <ul> + <li>Licensing lets you protect your application on any device that includes Android Market.</li> + <li>Your app maintains control of how it enforces its licensing status. </li> + <li>Adding licensing to an app is straightforward, using the library available through the SDK.</li> + <li>The service is free and is available to all developers who publish on Android Market. </li> + </ul> + + <h2>In this document</h2> + <ol> + <li><a href="#account">Setting Up A Publisher Account</a></li> + <li><a href="#dev-setup">Setting Up the Development Environment</a></li> + <li><a href="#app-integration">Integrating the LVL with Your Application</a> + <ol> + <li><a href="#add-library">Including the LVL</a></li> + <li><a href="#manifest-permission">Adding the licensing permission</a></li> + <li><a href="#impl-Policy">Implementing a Policy</a></li> + <li><a href="#impl-Obfuscator">Implementing an Obfuscator</a></li> + <li><a href="#impl-lc">Checking the license</a></li> + <li><a href="#impl-DeviceLimiter">Implementing a DeviceLimiter</a></li> + </ol></li> + <li><a href="#test-env">Setting Up the Test Environment</a> + <ol> + <li><a href="#test-response">Test responses</a></li> + <li><a href="#test-acct-setup">Test accounts</a></li> + <li><a href="#acct-signin">Signing in on a device or emulator</a></li> + </ol></li> + <li><a href="#app-obfuscation">Obfuscating Your Application</a></li> + <li><a href="#app-publishing">Publishing a Licensed Application</a></li> + <li><a href="#support">Where to Get Support</a></li> + </ol> + + <h2>Appendix</h2> + <ol> + <li><a href="#lvl-summary">Summary of LVL Classes and Interfaces</a></li> + <li><a href="#server-response-codes">Server Response Codes</a></li> + <li><a href="#extras">Server Response Extras</a></li> + </ol> + +</div> +</div> + +<p>Android Market offers a licensing service that lets you enforce licensing +policies for paid applications that you publish through Android Market. With +Android Market Licensing, your applications can query Android Market at run time to +obtain their licensing status for the current user, then allow or disallow +further use as appropriate. </p> + +<p>Using the service, you can apply a flexible licensing policy on an +application-by-application basis — each application can enforce licensing +in the way most appropriate for it. If necessary, an application can apply custom +constraints based on the licensing status obtained from Android Market. +For example, an application can check the licensing status and then apply custom +constraints that allow the user to run it unlicensed for a specific number +of times, or for a specific validity period. An application can also restrict use of the +application to a specific device, in addition to any other constraints. </p> + +<p>The licensing service is a secure means of controlling access to your +applications. When an application checks the licensing status, the Market server +signs the licensing status response using a key pair that is uniquely associated +with the publisher account. Your application stores the public key in its +compiled <code>.apk</code> file and uses it to verify the licensing status +response.</p> + +<p>Any application that you publish through Android Market can use the Android +Market Licensing service. No special account or registration is needed. +Additionally, because the service uses no dedicated framework APIs, you can add +licensing to any legacy application that uses a minimum API level of 3 or +higher.</p> + +<p>To help you add licensing to your application, the Android SDK provides +library sources that you can include in your application project. The +License Verification Library (LVL) handles all of +the licensing-related communication with the Android Market client and the +licensing service. With the LVL integrated, your application can determine its +licensing status for the current user by simply calling a library checker method +and implementing a callback that receives the status.</p> + +<p>This document explains how the licensing service works and how to add it to +your application. </p> + + +<h2 id="overview">Overview</h2> + +<p>Android Market Licensing is a network-based service that lets an application +on an Android-powered device query a trusted licensing server, to determine +whether the application is licensed to the current device user. After receiving +the server response, the application can then allow or disallow further use of +the application as needed. In the service, the role of the licensing server is +to provide the license status for the current user; the application itself is +responsible for querying the server and conditionally granting access to the +application. </p> + +<h4>Application, Android Market client, and server</h4> + +<p>The licensing service is based on the capability of the Android Market server +to determine whether a given user is licensed to use a given application. The +server considers a user licensed if the user is recorded to have purchased the +application, or if the application is available for free. To properly identify +the user and determine the license status, the server requires information about +the application and user — the application and the Android Market client +work together to assemble the information and pass it to the server. </p> + +<p>In the licensing service, an application does not query the licensing server +directly, but instead calls the Android Market client over remote IPC to +initiate a license request. In the license request:</p> + +<ul> +<li>The application provides its package name and a nonce that is later used to +validate any response from the server, as well as a callback over which the +response can be returned asynchronously.</li> +<li>The Android Market client, which has greater permissions than the +application, collects the necessary information about the user and the device, +such as the device's primary Google account username, IMSI, and other +information. It then sends the license check request to the server on behalf of +the application.</li> +<li>The server evaluates the request using all available information, attempting +to establish the user's identity to a sufficient level of confidence. The server +then checks the user identity against purchase records for the application and +returns a license response, which the Android Market client returns to the +application over the IPC callback.</li> +</ul> + +<p>Notice that during a license check, the application does not manage any +network connections or use any licensing related APIs in the Android platform. +</p> + +<div class="figure" style="width:469px"> +<img src="{@docRoot}images/licensing_arch.png" alt=""/> +<p class="img-caption"><strong>Figure 1.</strong> Your application initiates a +license check through the LVL and the Android Market +client, which handles communication with the Market server.</p> +</div> + +<h4>License responses secured through public key cryptography</h4> + +<p>To ensure the integrity of each license query, the server signs the license +response data using an RSA key pair that is shared exclusively between the +server and the application publisher.</p> + +<p>The licensing service generates a single licensing key pair for each +publisher account and exposes the public key in the account's profile page. The +publisher copies the public key and embeds it in the application source code, +then compiles and publishes the <code>.apk.</code> The server retains the +private key internally and uses it to sign license responses for applications +published on that account. </p> + +<p>When the application receives a signed response, it uses the embedded public +key to verify the data. The use of public key cryptography in the licensing +service makes it possible for the application to detect responses that have been +tampered with or are spoofed.</p> + +<h4>Use of licensing in your application</h4> + +<p>To use licensing in your application, add code to the application to +initiate a license check request and handle the response when it is received. +You can choose when, and how often, you want your application to check its +license and you have full control over how it handles the response, verifies the +signed response data, and enforces access controls. </p> + +<p>To simplify the process of adding support for licensing, download and +integrate the Licensing Verification Library, described below. Integration is +straightforward.</p> + +<p>When you are finished integrating the LVL, use a test environment +provided by the publisher site to test your application's handling of server +responses. </p> + +<p>Finally, publish the application <code>.apk</code> on Market using the +normal process. If you previously used the copy-protection provided by Android +Market, you can remove it from applications that use licensing. </p> + +<h4>Licensing Verification Library simplifies implementation</h4> + +<p>The Android SDK includes a License Verification Library (LVL) that you can +download and use as the basis for your application's licensing implementation. +The LVL greatly simplifies the process of adding licensing to your application +and helps ensure a more secure, robust implementation for your application. The +LVL provides internal classes that handle most of the standard operations of a +license query, such as contacting Android Market to initiate a license request +and decrypting and validating the responses. It also exposes key interfaces that +let you easily plug in your custom code for defining licensing policy and +managing access as needed by your application. The key LVL interfaces are: </p> + +<ul> +<li>Policy — your implementation determines whether to allow access to the +application, based on the license response received from the server and any +other data available (such as from a backend server associated with your +application). The implementation can evaluate the various fields of the license +response and apply other constraints, if needed. The implementation also lets +you manage the handling of license checks that result in errors, such as network +errors.</li> +<li>LicenseCheckerCallback — your implementation manages access to the +application, based on the result of the Policy's handling of the license +response. Your implementation can manage access in any way needed, including +displaying the license result in the UI or directing the user to purchase the +application (if not currently licensed). </li> +</ul> + +<p>To help you get started with a Policy, the LVL provides two fully complete +Policy implementations that you can use without modification or adapt to your +needs:</p> + +<ul> +<li><a href="#ServerManagedPolicy">ServerManagedPolicy</a> is a flexible Policy +that uses settings provided by the licensing server to manage response caching +and access to the application while the device is offline (such as when the +user is on on an airplane). For most applications, the use of +ServerManagedPolicy is highly recommended. </li> +<li><a href="#StrictPolicy">StrictPolicy</a> is a restrictive Policy that +does not cache any response data and allows the application access <em>only</em> +when the server returns a licensed response.</li> +</ul> + +<p>The LVL is available as a downloadable component of the Android SDK. The +component includes both the LVL itself and an example application that shows how +the library should be integrated with your application and how your application +should manage response data, UI interaction, and error conditions. </p> + +<p>The LVL sources are provided as an Android <em>library project</em>, which +means that you can maintain a single set of library sources and share them +across multiple applications. A full test environment is also available through +the SDK, so you can develop and test the licensing implementation in your +applications before publishing them, even if you don't have access to a +physical device.</p> + +<h4>Requirements and limitations</h4> + +<p>Android Market Licensing is designed to let you apply license controls to +applications that you publish through Android Market and that users download +from Market. The service is not designed to let you control access to +applications that are not published through Android Market or that are run +on devices that do not offer the Android Market client. </p> + +<p>Here are some points to keep in mind as you implement licensing in your +application: </p> + +<ul> +<li>Only paid applications published through Market can use the +service. </li> +<li>An application can use the service only if the Android Market client is +installed on its host device and the device is running Android 1.5 (API level 3) +or higher.</li> +<li>To complete a license check, the licensing server must be accessible over +the network. You can implement license caching behaviors to manage access when +there is no network connectivity. </li> +<li>The security of your application's licensing controls ultimately relies on +the design of your implementation itself. The service provides the building +blocks that let you securely check licensing, but the actual enforcement and +handling of the license are factors in your control. By following the best +practices in this document, you can help ensure that your implementation will be +secure.</li> +<li>Adding licensing to an application does not affect the way the application +functions when run on a device that does not offer Android Market.</li> +<li>Licensing is currently for paid apps only, since free apps are considered +licensed for all users. If your application is already published as free, +you won't be able to upload a new version that uses licensing.</li> +</ul> + +<h4>Replacement for copy protection</h4> + +<p>Android Market Licensing is a flexible, secure mechanism for controlling +access to your applications. It effectively replaces the copy-protection +mechanism and gives you wider distribution potential for your applications. </p> + +<ul> +<li>A limitation of copy protection is that applications using it can be +installed only on compatible devices that provide a secure internal storage +environment. For example, a copy-protected application cannot be downloaded +from Market to a device that provides root access, and the application +cannot be installed to a device's SD card. </li> +<li>With Android Market licensing, you can move to a license-based model in +which access is not bound to the characteristics of the host device, but to your +publisher account on Android Market and the licensing policy that you define. +Your application can be installed and controlled on any compatible device on +any storage, including SD card.</li> +</ul> + +<p>Although no license mechanism can completely prevent all unauthorized use, +the licensing service lets you control access for most types of normal usage, +across all compatible devices, locked or unlocked, that run Android 1.5 or +higher version of the platform.</p> + +<p>The sections below describe how to add Android Market licensing to your +applications. </p> + +<h2 id="account">Setting Up a Publisher Account</h2> + +<p>Android Market licensing lets you manage access to applications that +users have downloaded from Android Market. To use licensing in an application, +you need to have a publisher account on Android Market so that you can +publish the application to users. </p> + +<p>If you don't already have a publisher account, you need to register for one +using your Google account and agree to the terms of service. Once you are +registered, you can upload applications at your convenience and begin debugging +and testing your licensing implementation. For more information about publishing +on Android Market, see <a +href="{@docRoot}guide/publishing/publishing.html">Publishing Your +Applications</a></p> + +<p>To register as an Android Market developer and set up your publisher account, +visit the Android Market publisher site:</p> + +<p style="margin-left:2em;"><a +href="http://market.android.com/publish">http://market.android.com/publish</a> +</p> + +<p>If you already have a publisher account on Android Market, use your existing +account to set up licensing. You <em>do not</em> need to register for a new +account to support licensing (and doing so is not recommended, especially if you +are adding licensing support to applications that you have already published). +In all cases, if you have published applications, you manage licensing for those +applications through the account on which the applications are published. </p> + +<p>Once your publisher account is set up, use the account to:</p> + +<ul> +<li>Obtain a public key for licensing</li> +<li>Debug and test an application's licensing implementation, prior to +publishing the application</li> +<li>Publish the applications to which you have added licensing support</li> +</ul> + +<h4>Administrative settings for licensing</h4> + +<p>Once you are signed into your publisher account, you can manage several +administrative controls for Android Market licensing. The controls are available +in the Edit Profile page, in the "Licensing" panel, shown below. The controls +let you: </p> + +<ul> +<li>Set up multiple "test accounts", identified by email address. The licensing +server allows users signed into test accounts on a device or emulator to send +license checks and receive static test reponses.</li> +<li>Obtain the account's public key for licensing. When you are implementing +licensing in an application, you must copy the public key string into the +application.</li> +<li>Configure static test responses that the server sends, when it receives a +license check for an application uploaded to the publisher account, from a user +signed in to the publisher account or a test account.</li> +</ul> + +<div style="margin-bottom:2em;"> + +<img src="{@docRoot}images/licensing_public_key.png" style="text-align:left;margin-bottom:0;" /> +<div style="margin:0 2em;padding:0"><strong>Figure 2.</strong> The Licensing +panel of your account's Edit Profile page lets you manage administrative +settings for licensing.</div> +</div> + +<p>For more information about how to work with test accounts and static test +responses, see <a href="#test-env">Setting Up a Testing Environment</a>, below. + +<h2 id="dev-setup">Setting Up the Development Environment</h2> + +<p>Once you've set up your publisher account on Android Market, the next step is +to set up your development environment for licensing. </p> + +<p>Setting up your environment for licensing involves these tasks:</p> + +<ol> +<li><a href="#download-sdk">Downloading the latest SDK</a>, if you haven't already done so </li> +<li><a href="#runtime-setup">Setting up the runtime environment</a> for development</a></li> +<li><a href="#download-lvl">Downloading the Market Licensing component</a> into your SDK </li> +<li><a href="#lvl-setup">Setting up the Licensing Verification Library</a></li> +<li><a href="#add-library">Including the LVL library project in your application</a></li> +</ol> + +<p>The sections below describe these tasks. When you are done with setup, +you can begin <a href="#app-integration">integrating the LVL into your applications</a>.</p> + +<p>To get started, you need to set up a proper runtime environment on which +you can run, debug and test your application's implementation of license +checking and enforcement. </p> + + +<h3 id="download-sdk">Downloading the latest SDK</h3> + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>Licensing sample application</h2> + +<p>To work with Android Market licensing, you need a functioning Android +application to which you can add licensing support. </p> + +<p style="margin-top:.5em;">If you are new to Android +and don't yet have a functioning application, the LVL component includes a sample +application that you can set up as a new application project. The sample provides +a complete, working example of how licensing works. For more information, see <a +href="#download-lvl">Downloading the LVL</a>.</p> + +</div> +</div> + +<p>If you haven't done so, you need to download the Android SDK before you can +develop Android applications. The SDK provides the tools that you need to build +and debug Android applications, including applications that use Android Market +licensing. For complete information, including installation intructions, see +the <a href="{@docRoot}sdk/index.html">Android SDK</a>. </p> + +<p>If you have already installed the SDK, make sure to update the +SDK tools and ADT Plugin to the latest versions. You can update the SDK tools +using the Android SDK and AVD Manager and ADT through <strong>Help</strong> > +<strong>Software Updates...</strong> in Eclipse. </p> + +<p>After you've installed the latest SDK and tools, set up your development +environment as described below. </p> + + +<h3 id="runtime-setup">Setting up the runtime environment</h3> + +<p>As described earlier, applications check licensing status not by contacting +the licensing server directly, but by binding to a service provided by the +Android Market application and initiating a license check request. The Android +Market service then handles the direct communication with the licensing server +and finally routes the response back to your application. To debug and test +licensing in your application, you need to set up a runtime environment that +includes the necessary Android Market service, so that your application is able +to send license check requests to the licensing server. </p> + +<p>There are two types of runtime environment that you can use: </p> + +<ul> +<li>An Android-powered device that includes the Android Market application, or</li> +<li>An Android emulator running the Google APIs Add-on, API level 8 (release 2) +or higher</li> +</ul> + +<p>The sections below provide more information. </p> + +<h4 id="runtime-device">Running on a device</h4> + +<p>You can use an Android-powered device as the runtime environment for +debugging and testing licensing on your application.</p> + +<p>The device you use must:</p> + +<ul> +<li>Run a standard version of the Android 1.5 or later (API level +3 or higher) platform, <em>and</em> </li> +<li>Run a system image on which the Android Market client application +is preinstalled. </li> +</ul> + +<p>If Android Market is not preinstalled in the system image, your application won't +be able to communicate with the Android Market licensing server. </p> + +<p>For general information about how to set up a device for use in developing +Android applications, see <a +href="{@docRoot}guide/developing/device.html">Developing on a Device</a>.</p> + +<h4 id="runtime-emulator">Running on an Android emulator</h4> + +<p>You can also use an Android emulator as your runtime +environment for debugging and testing licensing.</p> + +<p>Because the standard Android platforms provided in the Android SDK <em>do +not</em> include Android Market, you need to download the Google APIs Add-On +platform, API Level 8 (or higher), from the SDK repository. After downloading +the add-on, you need to create an AVD configuration that uses that system image. +</p> + +<p>The Google APIs Add-On does not include the full Android Market client. +However, it does provide: </p> + +<ul> +<li>An Android Market background service that implements the +<code>ILicensingService</code> remote interface, so that your application can +send license checks over the network to the licensing server. </li> +<li>A set of underlying account services that let you add an a Google account on +the AVD and sign in using your publisher account or test account credentials. +Signing in using your publisher or test account enables you to debug and test +your application without having publish it. For more information see <a +href="#acct-signin">Signing in to an authorized account</a>, below.</li> +</ul> + +<p>Several versions of the add-on are available in the SDK repository, but only +<strong>Google APIs Add-On, API 8 (release 2) or higher</strong> version of the +add-on includes the necessary Android Market services. This means that you +cannot use Google APIs Add-On API 7 or lower as a runtime environment for +developing licensing on an emulator.</p> + +<div style="margin-bottom:2em;"> + +<img src="{@docRoot}images/licensing_gapis_8.png" style="text-align:left;margin-bottom:0;" /> +<div style="margin:0 2em;padding:0"><strong>Figure 3.</strong> Google APIs +Add-On, API 8 (release 2) or higher lets you debug and test your licensing +implemention in an emulator.</div> +</div> + +<p>To set up an emulator for adding licensing to an application, follow +these steps: </p> + +<ol> + <li>Launch the Android SDK and AVD Manager. </li> + <li>In the <strong>Available Packages</strong> panel, select and download the +SDK component "Google APIs (Google Inc.) - API Level 8" (or higher) from the SDK +repository, as shown in the figure above. + <p>When the download is complete, use the Android SDK and AVD Manager to +create a new AVD based on that component, described next.</p></li> + <li>In the <strong>Virtual +Devices</strong> panel of the Android SDK and AVD Manager, click +<strong>New</strong> and set the configuration details for the new AVD. </li> + <li>In the dialog that appears, assign a descriptive name to the AVD and then +use the "Target" menu to choose the "Google APIs (Google Inc.) - API Level 8" as +the system image to run on the new AVD. Set the other configuration details as +needed and then click <strong>Create AVD</strong> to finish. The SDK tools +create the new AVD configuration, which then appears in the list of available +Android Virtual Devices.</li> +</ol> + +<p>If you are not familiar with AVDs or how to use them, see <a +href="{@docRoot}guide/developing/tools/avd.html">Android Virtual Devices</a>.</p> + +<h4 id="project-update">Updating your project configuration</h4> + +<p>After you set up a runtime environment that meets the requirements described +above — either on an actual device or on an emulator — make sure to +update your application project or build scripts as needed, so that your compiled +<code>.apk</code> files that use licensing are deployed into that environment. +In particular, if you are developing in Eclipse, make sure that you set up a +Run/Debug Configuration that targets the appropriate device or AVD. </p> + +<p>You do not need to make any changes to your application's +build configuration, provided that the project is already configured to compile +against a standard Android 1.5 (API level 3) or higher library. For example: + +<ul> +<li>If you have an existing application that is compiled against +the Android 1.5 library, you do not need to make any changes to your +build configuration to support licensing. The build target meets the minimum +requirements for licensing, so you would continue building +against the same version of the Android platform.</li> + +<li>Similarly, if you are building against Android 1.5 (API level 3) but +are using an emulator running the Google APIs Add-On API 8 as the application's +runtime environment, there is no need to change your application's build +configuration. </li> +</ul> + +<p>In general, adding licensing to an application should have no impact +whatsoever on the application's build configuration.</p> + + +<h3 id="download-lvl">Downloading the LVL</h3> + +<p>The License Verification Library (LVL) is a collection of helper classes that +greatly simplify the work that you need to do to add licensing to your +application. In all cases, we recommend that you download the LVL and use it as +the basis for the licensing implementation in your application.</p> + +<p>The LVL is available as a downloadable component of the Android SDK. The +component includes: </p> + +<ul> +<li>The LVL sources, stored inside an Android library project. </li> +<li>An example application called "sample" that depends on the LVL library +project. The example illustrates how an application uses the library helper +classes to check and enforce licensing.</li> +</ul> + +<p>To download the LVL component into your development environment, use the +Android SDK and AVD Manager. Launch the Android SDK and AVD Manager and then +select the "Market Licensing" component, as shown in the figure below. +Accept the terms and click <strong>Install Selected</strong> to begin the download. </p> + +<div style="margin-bottom:2em;"> + +<img src="{@docRoot}images/licensing_package.png" style="text-align:left;margin-bottom:0;" /> +<div style="margin:0 2em;padding:0"><strong>Figure 4.</strong> The Market +Licensing package contains the LVL and the LVL sample application. </div> +</div> + +<p>When the download is complete, the Android SDK and AVD Manager installs both +the LVL library project and the example application into these directories: </p> + +<p style="margin-left:2em"><code><<em>sdk</em>>/marketlicensing/library/</code> + (the LVL library project)<br /> +<code><<em>sdk</em>>/marketlicensing/sample/</code> (the example +application)</p> + +<p>If you aren't familiar with how to download components into your SDK, see the +<a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a> +document. </p> + + +<h3 id="lvl-setup">Setting Up the Licensing Verification Library</h3> + +<p>After downloading the LVL to your computer, you need to set it up in your +development environment, either as an Android library project or by +copying (or importing) the library sources directly into your existing +application package. In general, using the LVL as a library project is recommended, +since it lets you reuse your licensing code across multiple applications and +maintain it more easily over time. Note that the LVL is not designed to be +compiled separately and added to an application as a static .jar file. </p> + +<h4>Moving the library sources out location</h4> + +<p>Because you will be customizing the LVL sources to some extent, you should +make sure to <em>move or copy</em> the library sources (the entire +directory at <code><<em>sdk</em>>/marketlicensing/library/</code>) +to a working directory outside of the SDK. You can then add the sources +in the working location to your source-code management system, rather +than those in the SDK.</p> + +<p>Moving the library sources is important is because, when you later update the +Market licensing package, the SDK installs the new files to the same location as +the older files. Moving your working library files to a safe location ensures +that they won't be inadvertently overwritten when you download a new version of +the LVL.</p> + +<h4>Creating the LVL as a library project</h4> + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>Working with library projects</h2> + +<p>The LVL is provided as an Android library project, which means that you can +share its code and resources across multiple applications. </p> + +<p style="margin-top:.5em;">If you aren't familiar with library projects or how +to use them, read more in <a +href="{@docRoot}guide/developing/eclipse-adt.html#libraryProject">Developing in +Eclipse with ADT</a> or <a +href="{@docRoot}guide/developing/other-ide.html#libraryProject">Developing in +Other IDEs</a>, as appropriate for your environment.</p> + +</div> +</div> + +<p>The recommended way of using the LVL is setting it up as a new Android +<em>library project</em>. A library project is a type of development project +that holds shared Android source code and resources. Other Android application +projects can reference the library project and, at build time, include its +compiled sources in their <code>.apk</code> files. In the context of licensing, this means +that you can do most of your licensing development once, in a library project, +then include the library sources in your various application projects. In this +way, you can easily maintain a uniform implementation of licensing across all of +your projects and maintain it centrally. </p> + +<p>The LVL is provided as a configured library project — once you have +downloaded it, you can start using it right away. </p> + +<p>If you are working in Eclipse with ADT, you need to add the LVL to your +workspace as a new development project, in the same way as you would a new +application project. </p> + +<ol> +<li>Use the New Project Wizard to create a new +project from existing sources. Select the LVL's <code>library</code> directory +(the directory containing the library's AndroidManifest.xml file) as the project +root.</li> +<li>When you are creating the library project, you can select any application +name, package, and set other fields as needed. </li> +<li>For the library's build target, select Android 1.5 (API level 3) or higher.</li> +</ol> + +<p> When created, the project is +predefined as a library project in its <code>default.properties</code> file, so +no further configuration is needed. </p> + +<p>For more information about how to create an application project or work with +library projects in Eclipse, see <a +href="{@docRoot}guide/developing/eclipse-adt.html#CreatingAProject">Developing +in Eclipse with ADT</a>.</p> + +<h4>Copying the LVL sources to your application</h4> + +<p>As an alternative to adding the LVL as a library project, you can copy the +library sources directly into your application. To do so, copy (or import) the +directory +<code><<em>sdk</em>>/extras/marketlicensing/library/src/com</code> into +your application's <code>src/</code> directory.</p> + +<p>If you add the LVL sources directly to your application, you can skip the +next section and start working with the library, as described in <a +href="#app-integration"></a>.</p> + + +<h3 id="add-library">Including the LVL library project sources in your +application</h3> + +<p>If you want to use the LVL sources as a library project, you need to add a +reference to the LVL library project in your project properties. This tells +build tools to include the LVL library project sources in your application at +compile time. The process for adding a reference to a library project varies, +based on your development environment, as described below.</p> + +<p> If you are developing in Eclipse with ADT, you should already have added the +library project to your workspace, as described in the previous section. If you +haven't done that already, do it now before continuing. </p> + +<p>Next, open the application's project properties window, as shown below. +Select the "Android" properties group and click <strong>Add</strong>, then +choose the LVL library project (com_android_vending_licensing) and click +<strong>OK</strong>. For more information, see +<a href="{@docRoot}guide/developing/eclipse-adt.html#libraryProject">Developing +in Eclipse with ADT</a></p> + +<div style="margin-bottom:2em;"> + +<img src="{@docRoot}images/licensing_add_library.png" style="text-align:left;margin-bottom:0;" /> +<div style="margin:0 2em;padding:0"><strong>Figure 5.</strong> If you are +working in Eclipse with ADT, you can add the LVL library project to your +application from the application's project properties.</div> +</div> + +<p>If you are developing using the SDK command-line tools, navigate to the +directory containing your application project and open the +<code>default.properties</code> file. Add a line to the file that specifies the +<code>android.library.reference.<n></code> key and the path to the +library. For example: </p> + +<pre>android.library.reference.1=path/to/library_project</pre> + +<p>Alternatively, you can use this command to update the project +properties, including the reference to the library project:</p> + +<pre class="no-pretty-print" style="color:black">android update lib-project +--target <em><target_ID></em> \ +--path <em>path/to/my/app_project</em> \ +--library <em>path/to/my/library_project</em> +</pre> + +<p>For more information about working with library projects, see <a +href="{@docRoot}guide/developing/eclipse-adt.html#libraryProject">Developing +in Eclipse with ADT</a> or <a +href="{@docRoot}guide/developing/other-ide.html#libraryProject">Developing in +Other IDEs</a>, as appropriate for your environment.</p> + + +<h2 id="app-integration">Integrating the LVL with Your Application</h2> + +<p>Once you've followed the steps above to set up a publisher account and +development environment, you are ready to begin integrating the LVL with your +application. </p> + +<p>Integrating the LVL with your application code involves these tasks:</p> + +<ol> +<li><a href="#manifest-permission">Adding the licensing permission</a> your application's manifest.</li> +<li><a href="#impl-Policy">Implementing a Policy</a> — you can choose one of the full implementations provided in the LVL or create your own.</li> +<li><a href="#impl-Obfuscator">Implementing an Obfuscator</a>, if your Policy will cache any license response data. </li> +<li><a href="#impl-lc">Adding code to check the license</a> in your application's main Activity</li> +<li><a href="#impl-DeviceLimiter">Implementing a DeviceLimiter</a> (optional and not recommended for most applications)</li> +</ol> + +<p>The sections below describe these tasks. When you are done with the +integration, you should be able to compile your application successfully and you +can begin testing, as described in <a href="#test-env">Setting Up the Test +Environment</a>.</p> + +<p>For an overview of the full set of source files included in the LVL, see <a +href="#lvl-summary">Summary of LVL Classes and Interfaces</a>.</p> + + +<h3 id="manifest-permission">Adding the licensing permission to your +AndroidManifest.xml</h3> + +<p>To use the Android Market application for sending a license check to the +server, your application must request the proper permission, +<code>com.android.vending.CHECK_LICENSE</code>. If your application does +not declare the licensing permission but attempts to initiate a license check, +the LVL throws a security exception.</p> + +<p>To request the licensing permission in your application, declare a <a +href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><code><uses-permission></code></a> +element as a child of <code><manifest></code>, as follows: </p> + +<p style="margin-left:2em;"><code><uses-permission +android:name="com.android.vending.CHECK_LICENSE"></code></p> + +<p>For example, here's how the LVL sample application declares the permission: +</p> + +<pre><?xml version="1.0" encoding="utf-8"?> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" ..."> + ... + <!-- Devices >= 3 have version of Android Market that supports licensing. --> + <uses-sdk android:minSdkVersion="3" /> + <!-- Required permission to check licensing. --> + <uses-permission android:name="com.android.vending.CHECK_LICENSE" /> + ... +</manifest> +</pre> + +<p class="note"><strong>Note:</strong> Currently, you cannot declare the +<code>CHECK_LICENSE</code> permission in the LVL's manifest, because the SDK +Tools will not merge it into the manifests of dependent applications. Instead, +you must declare the permission in the manifest of each dependent application. +</p> + + +<h3 id="impl-Policy">Implementing a Policy</h3> + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>ServerManagedPolicy</h2> + +<p>The LVL includes a complete Policy implementation called ServerManagedPolicy +that makes use of license-management settings provided by the Android Market +server. </p> + +<p style="margin-top:.5em;">Use of ServerManagedPolicy as the basis for your +Policy is strongly recommended. For more information, see <a +href="#ServerManagedPolicy">ServerManagedPolicy</a> section, below.</p> + +</div> +</div> + +<p>Android Market licensing service does not itself determine whether a +given user with a given license should be granted access to your application. +Rather, that responsibility is left to a Policy implementation that you provide +in your application.</p> + +<p>Policy is an interface declared by the LVL that is designed to hold your +application's logic for allowing or disallowing user access, based on the result +of a license check. To use the LVL, your application <em>must</em> provide an +implementation of Policy. </p> + +<p>The Policy interface declares two methods, <code>allowAccess()</code> and +<code>processServerResponse()</code>, which are called by a LicenseChecker +instance when processing a response from the license server. It also declares an +enum called <code>LicenseResponse</code>, which specifies the license response +value passed in calls to <code>processServerResponse()</code>. </p> + +<ul> +<li><code>processServerResponse()</code> lets you preprocess the raw response +data received from the licensing server, prior to determining whether to grant +access. + +<p>A typical implementation would extract some or all fields from the license +response and store the data locally to a persistent store, such as through +{@link android.content.SharedPreferences} storage, to ensure that the data is +accessible across application invocations and device power cycles. For example, +a Policy would maintain the timestamp of last successful license check, the +retry count, the license validity period, and similar information in a +persistent store, rather than resetting the values each time the application is +launched.</p> + +<p>When storing response data locally, the Policy must ensure that the data is +obfuscated (see <a href="#impl-Obfuscator">Implementing an Obfuscator</a>, +below).</p></li> + +<li><code>allowAccess()</code> determines whether to grant the user access to +your application, based on any available license response data (from the +licensing server or from cache) or other application-specific information. For +example, your implementation of <code>allowAccess()</code> could take into +account additional criteria, such as usage or other data retrieved from a +backend server. In all cases, an implementation of <code>allowAccess()</code> +should only return <code>true</code> if there user is licensed to use the +application, as determined by the licensing server, or if there is a transient +network or system problem that prevents the license check from completing. In +such cases, your implementation can maintain a count of retry responses and +provisionally allow access until the next license check is complete.</li> + +</ul> + +<p>To simplify the process of adding licensing to your application and to +provide an illustration of how a Policy should be designed, the LVL includes +two full Policy implementations that you can use without modification:</p> + +<ul> +<li><a href="#ServerManagedPolicy">ServerManagedPolicy</a>, a flexible Policy +that uses server-provided settings and cached responses to manage access across +varied network conditions, and</li> +<li><a href="#StrictPolicy">StrictPolicy</a>, which does not cache any response +data and allows access <em>only</em> if the server returns a licensed +response.</li> +</ul> + +<p>For most applications, the use of ServerManagedPolicy is highly +recommended. ServerManagedPolicy is the LVL default and is integrated with +the LVL sample application.</p> + + +<h4 id="custom-policies">Guidelines for custom policies</h4> + +<p>In your licensing implementation, you can use one of the complete policies +provided in the LVL (ServerManagedPolicy or StrictPolicy) or you can create a +custom Policy. For any type of custom policy, there are several important design +points to understand and account for in your implementation.</p> + +<p>The licensing server applies general request limits to guard against overuse +of resources that could result in denial of service. When an application exceeds +the request limit, the licensing server returns a 503 response, which gets +passed through to your application as a general server error. This means that no +license response will be available to the user until the limit is reset, which +can affect the user for an indefinite period.</p> + +<p>If you are designing a custom Policy, we recommend that the Policy: +<ol> +<!-- <li>Limits the number of points at which your app calls for a license check +to the minimum. </li> --> +<li>Caches (and properly obfuscates) the most recent successful license response +in local persistent storage.</li> +<li>Returns the cached response for all license checks, for as long as the +cached response is valid, rather than making a request to the licensing server. +Setting the response validity accrording to the server-provided <code>VT</code> +extra is highly recommended. See <a href="#extras">Server Response Extras</a> +for more information.</li> +<li>Uses an exponential backoff period if retrying any requests the result in +errors. However, because the Android Market client automatically retries failed +requests, the Policy does not need to do so, in most cases.</li> +<li>Provides for a "grace period" that allows the user to access your +application for a limited time or number of uses, while a license check is being +retried. The grace period benefits the user by allowing access until the next +license check can be completed successfully and it benefits you by placing a +hard limit on access to your application when there is no valid license response +available.</li> +</ol> + +<p>Designing your Policy according to the guidelines listed above is critical, +because it ensures the best possible experience for users while giving you +effective control over your application even in error conditions. </p> + +<p>Note that any Policy can use settings provided by the licensing server to +help manage validity and caching, retry grace period, and more. Extracting the +server-provided settings is straightforward and making use of them is highly +recommended. See the ServerManagedPolicy implementation for an example of how to +extract and use the extras. For a list of server settings and information about +how to use them, see <a href="#extras">Server Response Extras</a> in the +Appendix of this document.</p> + +<h4 id="ServerManagedPolicy">ServerManagedPolicy</h4> + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>Server Response Extras</h2> + +<p>For certain types of licensing responses, the licensing server appends extra +settings to the responses, to help the application manage licensing effectively. +</p> + +<p style="margin-top:.5em;">See <a href="#extras">Server Response Extras</a> for +a list of settings and <code>ServerManagedPolicy.java</code> for information +about how a Policy can usethe extras.</p> + +</div> +</div> + +<p>The LVL includes a full and recommended implementation of the Policy +interface called ServerManagedPolicy. The implementation is integrated with the +LVL classes and serves as the default Policy in the library. </p> + +<p>ServerManagedPolicy provides all of the handling for license and retry +responses. It caches all of the response data locally in a +{@link android.content.SharedPreferences} file, obfuscating it with the +application's Obfuscator implementation. This ensures that the license response +data is secure and persists across device power cycles. ServerManagedPolicy +provides concrete implementations of the interface methods +<code>processServerResponse()</code> and <code>allowAccess()</code> and also +includes a set of supporting methods and types for managing license +responses.</p> + +<p>Importantly, a key feature of ServerMangedPolicy is its use of +server-provided settings as the basis for managing licensing across an +application's refund period and through varying network and error conditions. +When an application contacts the Android Market server for a license check, the +server appends several settings as key-value pairs in the license response +extras field. For example, the server provides recommended values for the +application's license validity period, retry grace period, and maximum allowable +retry count, among others. ServerManagedPolicy extracts the values from the +license response in its <code>processServerResponse()</code> method and checks +them in its <code>allowAccess()</code> method. For a list of the server-provided +settings used by ServerManagedPolicy, see <a href="#extras">Server Response +Extras</a> in the Appendix of this document.</p> + +<p>For convenience, best performance, and the benefit of using license settings +from the Android Market server, <strong>using ServerManagedPolicy as your +licensing Policy is strongly recommended</strong>. </p> + +<p>If you are concerned about the security of license response data that is +stored locally in SharedPreferences, you can use a stronger obfuscation +algorithm or design a stricter Policy that does not store license data. The LVL +includes an example of such a Policy — see <a +href="#StrictPolicy">StrictPolicy</a> for more information.</p> + +<p>To use ServerManagedPolicy, simply import it to your Activity, create an +instance, and pass a reference to the instance when constructing your +LicenseChecker. See <a href="#lc-lcc">Instantiate LicenseChecker and +LicenseCheckerCallback</a> for more information. </p> + +<h4 id="StrictPolicy">StrictPolicy</h4> + +<p>The LVL includes an alternative full implementation of the Policy interface +called StrictPolicy. The StrictPolicy implementation provides a more restrictive +Policy than ServerManagedPolicy, in that it does not allow the user to access +the application unless a license response is received from the server at the +time of access that indicates that the user is licensed.</p> + +<p>The principal feature of StrictPolicy is that it does not store <em>any</em> +license response data locally, in a persistent store. Because no data is stored, +retry requests are not tracked and cached responses can not be used to fulfill +license checks. The Policy allows access only if:</p> + +<ul> +<li>The license response is received from the licensing server, and </li> +<li>The license response indicates that the user is licensed to access the +application. </li> +</ul> + +<p>Using StrictPolicy is appropriate if your primary concern is to ensure that, +in all possible cases, no user will be allowed to access the application unless +the user is confirmed to be licensed at the time of use. Additionally, the +Policy offers slightly more security than ServerManagedPolicy — since +there is no data cached locally, there is no way a malicious user could tamper +with the cached data and obtain access to the application.</p> + +<p>At the same time, this Policy presents a challenge for normal users, since it +means that they won't be able to access the application when there is no network +(cell or wi-fi) connection available. Another side-effect is that your +application will send more license check requests to the server, since using a +cached response is not possible. Depending on network conditions, this might +prove challenging for users also.</p> + +<p>Overall, this policy represents a tradeoff of some degree of user convenience +for absolute security and control over access. Consider the tradeoff carefully +before using this Policy.</p> + +<p>To use StrictPolicy, simply import it to your Activity, create an instance, +and pass a reference to it when constructing your LicenseChecker. See +<a href="#lc-lcc">Instantiate LicenseChecker and LicenseCheckerCallback</a> +for more information. </p> + +<h3 id="impl-Obfuscator">Implementing an Obfuscator</h3> + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>AESObfuscator</h2> + +<p>The LVL includes a full Obfuscator implementation in the +<code>AESObfuscator.java</code> file. The Obfuscator uses AES encryption to +obfuscate/unobfuscate data. If you are using a Policy (such as +ServerManagedPolicy) that caches license response data, using AESObfuscator as +basis for your Obfuscator implementation is highly recommended. </p> + +</div> +</div> + +<p>A typical Policy implementation needs to save the license response data for +an application to a persistent store, so that it is accessible across +application invocations and device power cycles. For example, a Policy would +maintain the timestamp of the last successful license check, the retry count, +the license validity period, and similar information in a persistent store, +rather than resetting the values each time the application is launched. The +default Policy included in the LVL, ServerManagedPolicy, stores license response +data in a {@link android.content.SharedPreferences} instance, to ensure that the +data is persistent. </p> + +<p>Because the Policy will use stored license response data to determine whether +to allow or disallow access to the application, it <em>must</em> ensure that any +stored data is secure and can not be reused or manipulated by a root user on a +device. Specifically, the Policy must always obfuscate the data before storing +it, using a key that is unique for the application and device. Obfuscating using +a key that is both application-specific and device-specific is critical, because +it prevents the obfuscated data from being shared among applications and +devices.</p> + +<p>The LVL assists the application with storing its license response data in a +secure, persistent manner. First, it provides an <code>Obfuscator</code> +interface that lets your application supply the obfuscation algorithm of its +choice for stored data. Building on that, the LVL provides the helper class +<code>PreferenceObfuscator</code>, which handles most of the work of calling the +application's Obfuscator class and reading and writing the obfuscated data in a +SharedPreferences instance. </p> + +<p>The LVL provides a full Obfuscator implementation called +<code>AESObfuscator</code> that uses AES encryption to obfuscate data. You can +use <code>AESObfuscator</code> in your application without modification or you +can adapt it to your needs. For more information, see the next section.</p> + +<p>Alternatively, you can write a custom Obfuscator based on your own code +or use an obfuscator program such as ProGuard for additional security.</p> + +<h4 id="AESObfuscator">AESObfuscator</h4> + +<p>The LVL includes a full and recommended implementation of the Obfuscator +interface called AESObfuscator. The implementation is integrated with the +LVL sample application and serves as the default Obfuscator in the library. </p> + +<p>AESObfuscator provides secure obfuscation of data by using AES to +encrypt and decrypt the data as it is written to or read from storage. +The Obfuscator seeds the encryption using three data fields provided +by the application: </p> + +<ol> +<li>A salt — an array of random bytes to use for each (un)obfuscation. </li> +<li>An application identifier string, typically the package name of the application.</li> +<li>A device identifier string, derived from as many device-specific sources +as possible, so as to make it as unique.</li> +</ol> + +<p>To use AESObfuscator, first import it to your Activity. Declare a private +static final array to hold the salt bytes and initialize it to 20 randomly +generated bytes.</p> + +<pre> ... + // Generate 20 random bytes, and put them here. + private static final byte[] SALT = new byte[] { + -46, 65, 30, -128, -103, -57, 74, -64, 51, 88, -95, + -45, 77, -117, -36, -113, -11, 32, -64, 89 + }; + ... +</pre> + +<p>Next, declare a variable to hold a device identifier and generate a value for +it in any way needed. For example, the sample application included in the LVL +queries the system settings for the +<code>android.Settings.Secure.ANDROID_ID</code>, which is unique to each device. +</p> + +<p>Note that, depending on the APIs you use to derive device-specific +information, your application might need to request additional permissions in +order to secure device-specific information. For example, if you query the +TelephonyManager to obtain the device IMEI or related data, your application +will also need to request the <code>android.permission.READ_PHONE_STATE</code> +permission in its manifest. Before requesting permissions in this way, consider +how doing so might affect your application or its filtering of your application +on Android Market (since some permissions can cause the SDK build tools to add +the associated <code><uses-feature></code>).</p> + +<p>Finally, construct an instance of AESObfuscator, passing the salt, +application identifier, and device identifier. You can construct the instance +directly, while constructing your Policy and LicenseChecker. For example:</p> + +<pre> ... + // Construct the LicenseChecker with a Policy. + mChecker = new LicenseChecker( + this, new ServerManagedPolicy(this, + new AESObfuscator(SALT, getPackageName(), deviceId)), + BASE64_PUBLIC_KEY // Your public licensing key. + ); + ... +</pre> + +<p>For a complete example, see MainActivity in the LVL sample application.</p> + + +<h3 id="impl-lc">Checking the license from your application's main Activity</h3> + +<p>Once you've implemented a Policy for managing access to your application, the +next step is to add a license check to your application, which initiates a query +to the licensing server if needed and manages access to the application based on +the license response. All of the work of adding the license check and handling +the response takes place in your main {@link android.app.Activity} source file. +</p> + +<p>To add the license check and handle the response, you must:</p> + +<ol> + <li><a href="#imports">Add imports</a></li> + <li><a href="#lc-impl">Implement LicenseCheckerCallback</a> as a private inner class</li> + <li><a href="#thread-handler">Create a Handler</a> for posting from LicenseCheckerCallback to the UI thread</li> + <li><a href="#lc-lcc">Instantiate LicenseChecker</a> and LicenseCheckerCallback</li> + <li><a href="#check-access">Call checkAccess()</a> to initiate the license check</li> + <li><a href="#account-key">Embed your public key</a> for licensing</li> + <li><a href="#handler-cleanup">Call your LicenseChecker's onDestroy() method</a> to close IPC connections.</li> +</ol> + +<p>The sections below describe these tasks. </p> + +<h4 id="lc-overview">Overview of license check and response</h4> + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>Example: MainActivity</h2> + +<p>The sample application included with the LVL provides a full example of how +to initiate a license check and handle the result, in the +<code>MainActivity.java</code> file.</p> + +</div> +</div> + +<p>In most cases, you should add the license check to your application's main +{@link android.app.Activity}, in the <code>onCreate()</code> method. This +ensures that when the user launches your application directly, the license check +will be invoked immediately. In some cases, you can add license checks in other +locations as well. For example, if your application includes multiple Activity +components that other applications can start by {@link android.content.Intent}, +you could add license checks in those Activities.</p> + +<p>A license check consists of two main actions: </p> + +<ul> +<li>A call to a method to initiate the license check — in the LVL, this is +a call to the <code>checkAccess()</code> method of a LicenseChecker object that +you construct.</li> +<li>A callback that returns the result of the license check. In the LVL, this is +a <code>LicenseCheckerCallback</code> interface that you implement. The +interface declares two methods, <code>allow()</code> and +<code>dontAllow()</code>, which are invoked by the library based on to the +result of the license check. You implement those two methods with whatever logic +you need, to allow or disallow the user access to your application. Note that +these methods do not determine <em>whether</em> to allow access — that +determination is the responsibility of your Policy implementation. Rather, these +methods simply provide the application behaviors for <em>how</em> to allow and +disallow access (and handle application errors).</li> +</ul> + +<div style="margin-bottom:2em;"> + +<img src="{@docRoot}images/licensing_flow.png" style="text-align:left;margin-bottom:0;margin-left:3em;" /> +<div style="margin:.5em 0 1.5em 2em;padding:0"><strong>Figure 6.</strong> Overview of a +typical license check interaction.</div> +</div> + +<p>The diagram above illustrates how a typical license check takes place: </p> + +<ol> +<li>Code in the application's main Activity instantiates LicenseCheckerCallback +and LicenseChecker objects. When constructing LicenseChecker, the code passes in +{@link android.content.Context}, a Policy implementation to use, and the +publisher account's public key for licensing as parameters. </li> +<li>The code then calls the <code>checkAccess()</code> method on the +LicenseChecker object. The method implementation calls the Policy to determine +whether there is a valid license response cached locally, in +{@link android.content.SharedPreferences}. +<ul> +<li>If so, the <code>checkAccess()</code> implementation calls +<code>allow()</code>.</li> +<li>Otherwise, the LicenseChecker initiates a license check request that is sent +to the licensing server.</li> +</ul> +</li> +<li>When a response is received, LicenseChecker creates a LicenseValidator that +verifies the signed license data and extracts the fields of the response, then +passes them to your Policy for further evaluation. + <ul> + <li>If the license is valid, the Policy caches the response in +SharedPreferences and notifies the validator, which then calls the +<code>allow()</code> method on the LicenseCheckerCallback object. </li> + <li>If the license not valid, the Policy notifies the validator, which calls +the <code>dontAllow()</code> method on LicenseCheckerCallback. </li> + </ul> +</li> +<li>In case of a recoverable local or server error, such as when the network is +not available to send the request, LicenseChecker passes a RETRY response to +your Policy's <code>processServerResponse()</code> method. </li> +<li>In case of a application error, such as when the application attempts to +check the license of an invalid package name, LicenseChecker passes an error +response to the LicenseCheckerCallback's <code>applicationError()</code> +method. </li> +</ol> + +<p>Note that, in addition to initiating the license check and handling the +result, which are described in the sections below, your application also needs +to provide a <a href="#impl-Policy">Policy implementation</a> and, if the Policy +stores response data (such as ServerManagedPolicy), an <a +href="#impl-Obfuscator">Obfuscator</a> implementation. </p> + + +<h4 id="imports">Add imports</h4> + +<p>First, open the class file of the application's main Activity and import +LicenseChecker and LicenseCheckerCallback from the LVL package.</p> + +<pre> import com.android.vending.licensing.LicenseChecker; + import com.android.vending.licensing.LicenseCheckerCallback;</pre> + +<p>If you are using the default Policy implementation provided with the LVL, +ServerManagedPolicy, import it also, together with the AESObfuscator. If you are +using a custom Policy or Obfuscator, import those instead. </p> + +<pre> import com.android.vending.licensing.ServerManagedPolicy; + import com.android.vending.licensing.AESObfuscator;</pre> + +<h4 id="lc-impl">Implement LicenseCheckerCallback as a private inner class</h4> + +<p>LicenseCheckerCallback is an interface provided by the LVL for handling +result of a license check. To support licensing using the LVL, you must +implement LicenseCheckerCallback and +its methods to allow or disallow access to the application.</p> + +<p>The result of a license check is always a call to one of the +LicenseCheckerCallback methods, made based on the validation of the response +payload, the server response code itself, and any additional processing provided +by your Policy. Your application can implement the methods in any way needed. In +general, it's best to keep the methods simple, limiting them to managing UI +state and application access. If you want to add further processing of license +responses, such as by contacting a backend server or applying custom constraints, +you should consider incorporating that code into your Policy, rather than +putting it in the LicenseCheckerCallback methods. </p> + +<p>In most cases, you should declare your implementation of +LicenseCheckerCallback as a private class inside your application's main +Activity class. </p> + +<p>Implement the <code>allow()</code> and <code>dontAllow()</code> methods as +needed. To start with, you can use simple result-handling behaviors in the +methods, such as displaying the license result in a dialog. This helps you get +your application running sooner and can assist with debugging. Later, after you +have determined the exact behaviors you want, you can add more complex handling. +</p> + +<p>Some suggestions for handling unlicensed responses in +<code>dontAllow()</code> include: </p> + +<ul> +<li>Display a "Try again" dialog to the user, including a button to initiate a +new license check. </li> +<li>Display a "Purchase this application" dialog, including a button that +deep-links the user to the application's details page on Market, from which the +use can purchase the application. For more information on how to set up such +links, see <a +href="{@docRoot}guide/publishing/publishing.html#marketintent">Using Intents to +Launch the Market Application on a Device</a>. </li> +<li>Display a Toast notification that indicates that the features of the +application are limited because it is not licensed. </li> +</ul> + +<p>The example below shows how the LVL sample application implements +LicenseCheckerCallback, with methods that display the license check result in a +dialog. </p> + +<pre> private class MyLicenseCheckerCallback implements LicenseCheckerCallback { + public void allow() { + if (isFinishing()) { + // Don't update UI if Activity is finishing. + return; + } + // Should allow user access. + displayResult(getString(R.string.allow)); + } + + public void dontAllow() { + if (isFinishing()) { + // Don't update UI if Activity is finishing. + return; + } + displayResult(getString(R.string.dont_allow)); + // Should not allow access. An app can handle as needed, + // typically by informing the user that the app is not licensed + // and then shutting down the app or limiting the user to a + // restricted set of features. + // In this example, we show a dialog that takes the user to Market. + showDialog(0); + } + } +</pre> + +<p>Additionally, you should implement the <code>applicationError()</code> +method, which the LVL calls to let your application handle errors that are not +retryable. For a list of such errors, see <a +href="#server-response-codes">Server Response Codes</a> in the Appendix of this +document. You can implement the method in any way needed. In most cases, the +method should log the error code and call <code>dontAllow()</code>.</p> + +<h4 id="thread-handler">Create a Handler for posting from LicenseCheckerCallback +to the UI thread</h4> + +<p>During a license check, the LVL passes the request to the Android Market +application, which handles communication with the licensing server. The LVL +passes the request over asynchronous IPC (using {@link android.os.Binder}) so +the actual processing and network communication do not take place on a thread +managed by your application. Similarly, when the Android Market application +receives the result, it invokes a callback method over IPC, which in turn +executes in an IPC thread pool in your application's process.</p> + +<p>The LicenseChecker class manages your application's IPC communication with +the Android Market application, including the call that sends the request and +the callback that receives the response. LicenseChecker also tracks open license +requests and manages their timeouts. </p> + +<p>So that it can handle timeouts properly and also process incoming responses +without affecting your application's UI thread, LicenseChecker spawns a +background thread at instantiation. In the thread it does all processing of +license check results, whether the result is a response received from the server +or a timeout error. At the conclusion of processing, the LVL calls your +LicenseCheckerCallback methods from the background thread. </p> + +<p>To your application, this means that:</p> + +<ol> +<li>Your LicenseCheckerCallback methods will be invoked, in many cases, from a +background thread.</li> +<li>Those methods won't be able to update state or invoke any processing in the +UI thread, unless you create a Handler in the UI thread and have your callback +methods post to the Handler.</li> +</ol> + +<p>If you want your LicenseCheckerCallback methods to update the UI thread, +instantiate a {@link android.os.Handler} in the main Activity's +{@link android.app.Activity#onCreate(android.os.Bundle) onCreate()} method, +as shown below. In this example, the LVL sample application's +LicenseCheckerCallback methods (see above) call <code>displayResult()</code> to +update the UI thread through the Handler's +{@link android.os.Handler#post(java.lang.Runnable) post()} method.</p> + +<pre>private Handler mHandler; + + @Override + public void onCreate(Bundle savedInstanceState) { + ... + mHandler = new Handler(); + } +</pre> + +<p>Then, in your LicenseCheckerCallback methods, you can use Handler methods to +post Runnable or Message objects to the Handler. Here's how the sample +application included in the LVL posts a Runnable to a Handler in the UI thread +to display the license status.</p> + +<pre> private void displayResult(final String result) { + mHandler.post(new Runnable() { + public void run() { + mStatusText.setText(result); + setProgressBarIndeterminateVisibility(false); + mCheckLicenseButton.setEnabled(true); + } + }); + } +</pre> + +<h4 id="lc-lcc">Instantiate LicenseChecker and LicenseCheckerCallback</h4> + +<p>In the main Activity's +{@link android.app.Activity#onCreate(android.os.Bundle) onCreate()} method, +create private instances of LicenseCheckerCallback and LicenseChecker. You must +instantiate LicenseCheckerCallback first, because you need to pass a reference +to that instance when you call the contructor for LicenseChecker. </p> + +<p>When you instantiate LicenseChecker, you need to pass in these parameters:</p> + +<ul> +<li>The application {@link android.content.Context}</li> +<li>A reference to the Policy implementation to use for the license check. In +most cases, you would use the default Policy implementation provided by the LVL, +ServerManagedPolicy. </li> +<li>The String variable holding your publisher account's public key for +licensing. </li> +</ul> + +<p>If you are using ServerManagedPolicy, you won't need to access the class +directly, so you can instantiate it directly in the LicenseChecker constructor, +as shown in the example below. Note that you need to pass a reference to a new +Obfuscator instance when you construct ServerManagedPolicy.</p> + +<p>The example below shows the instantiation of LicenseChecker and +LicenseCheckerCallback from the <code>onCreate()</code> method of an Activity +class. </p> + +<pre>public class MainActivity extends Activity { + ... + private LicenseCheckerCallback mLicenseCheckerCallback; + private LicenseChecker mChecker; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ... + // Construct the LicenseCheckerCallback. The library calls this when done. + mLicenseCheckerCallback = new MyLicenseCheckerCallback(); + + // Construct the LicenseChecker with a Policy. + mChecker = new LicenseChecker( + this, new ServerManagedPolicy(this, + new AESObfuscator(SALT, getPackageName(), deviceId)), + BASE64_PUBLIC_KEY // Your public licensing key. + ); + ... + } +} +</pre> + + +<p>Note that LicenseChecker calls the LicenseCheckerCallback methods from the UI +thread <em>only</em> if there is valid license response cached locally. If the +license check is sent to the server, the callbacks always originate from the +background thread, even for network errors. </p> + + +<h4 id="check-access">Call checkAccess() to initiate the license check</h4> + +<p>In your main Activity, add a call to the <code>checkAccess()</code> method of the +LicenseChecker instance. In the call, pass a reference to your +LicenseCheckerCallback instance as a parameter. If you need to handle any +special UI effects or state management before the call, you might find it useful +to call <code>checkAccess()</code> from a wrapper method. For example, the LVL +sample application calls <code>checkAccess()</code> from a +<code>doCheck()</code> wrapper method:</p> + +<pre> @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ... + // Call a wrapper method that initiates the license check + doCheck(); + ... + } + ... + private void doCheck() { + mCheckLicenseButton.setEnabled(false); + setProgressBarIndeterminateVisibility(true); + mStatusText.setText(R.string.checking_license); + mChecker.checkAccess(mLicenseCheckerCallback); + } +</pre> + + +<h4 id="account-key">Embed your public key for licensing</h4> + +<p>For each publisher account, the Android Market service automatically +generates a 2048-bit RSA public/private key pair that is used exlusively for +licensing. The key pair is uniquely associated with the publisher account and is +shared across all applications that are published through the account. Although +associated with a publisher account, the key pair is <em>not</em> the same as +the key that you use to sign your applications (or derived from it).</p> + +<p>The Android Market publisher site exposes the public key for licensing to any +developer signed in to the publisher account, but it keeps the private key +hidden from all users in a secure location. When an application requests a +license check for an application published in your account, the licensing server +signs the license response using the private key of your account's key pair. +When the LVL receives the response, it uses the public key provided by the +application to verify the signature of the license response. </p> + +<p>To add licensing to an application, you must obtain your publisher account's +public key for licensing and copy it into your application. Here's how to find +your account's public key for licensing:</p> + +<ol> +<li>Go to the Android Market <a +href="http://market.android.com/publish">publisher site</a> and sign in. +Make sure that you sign in to the account from which the application you are +licensing is published (or will be published). </li> +<li>In the account home page, locate the "Edit profile" link and click it. </li> +<li>In the Edit Profile page, locate the "Licensing" pane, shown below. Your +public key for licensing is given in the "Public key" text box. </p> +</ol> + +<p>To add the public key to your application, simply copy/paste the key string +from the text box into your application as the value of the String variable +<code>BASE64_PUBLIC_KEY</code>. When you are copying, make sure that you have +selected the entire key string, without omitting any characters. </p> + +<p>Here's an example from the LVL sample application:</p> + +<pre> public class MainActivity extends Activity { + private static final String BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... "; //truncated for this example + ... + } +</pre> + +<h4 id="handler-cleanup">Call your LicenseChecker's onDestroy() method +to close IPC connections</h4> + +<p>Finally, to let the LVL clean up before your application +{@link android.content.Context} changes, add a call to the LicenseChecker's +<code>onDestroy()</code> method from your Activity's +{@link android.app.Activity#onDestroy()} implementation. The call causes the +LicenseChecker to properly close any open IPC connection to the Android Market +application's ILicensingService and removes any local references to the service +and handler.</p> + +<p>Failing to add the call the LicenseChecker's <code>onDestroy()</code> method +can lead to problems over the lifecycle of your application. For example, if the +user changes screen orientation while a license check is active, the application +{@link android.content.Context} is destroyed. If your application does not +properly close the LicenseChecker's IPC connection, your application will crash +when the response is received. Similarly, if the user exits your application +while a license check is in progress, your application will crash when the +response is received, unless your application has properly called the +LicenseChecker's <code>onDestroy()</code> method to disconnect from the service. +</p> + +<p>Here's an example from the sample application included in the LVL, where +<code>mChecker</code> is the LicenseChecker instance:</p> + +<pre> @Override + protected void onDestroy() { + super.onDestroy(); + mChecker.onDestroy(); + ... + } +</pre> + +<p>If you are extending or modifying LicenseChecker, you might also need to call +the LicenseChecker's <code>finishCheck()</code> method, to clean up any open IPC +connections.</p> + +<h3 id="impl-DeviceLimiter">Implementing a DeviceLimiter</h3> + +<p>In some cases, you might want your Policy to limit the number of actual +devices that are permitted to use a single license. This would prevent a user +from moving a licensed application onto a number of devices and using the +application on those devices under the same account ID. It would also prevent a +user from "sharing" the application by providing the account information +associated with the license to other individuals, who could then sign in to that +account on their devices and access the license to the application. </p> + +<p>The LVL supports per-device licensing by providing a +<code>DeviceLimiter</code> interface, which declares a single method, +<code>allowDeviceAccess()</code>. When a LicenseValidator is handling a response +from the licensing server, it calls <code>allowDeviceAccess()</code>, passing a +user ID string extracted from the response.</p> + +<p>If you do not want to support device limitation, <strong>no work is +required</strong> — the LicenseChecker class automatically uses a default +implementation called NullDeviceLimiter. As the name suggests, NullDeviceLimiter +is a "no-op" class whose <code>allowDeviceAccess()</code> method simply returns +a LICENSED response for all users and devices. </p> + +<div style="border-left:4px solid #FFCF00;margin:1em;padding: 0 0 0 .5em"> +<p><strong>Caution:</strong> Per-device licensing is <em>not recommended for +most applications</em> because:</p> +<ul> +<li>It requires that you provide a backend server to manage a users and devices +mapping, and </li> +<li>It could inadvertently result in a user being denied access to an +application that they have legitimately purchased on another device.</li> +</ul> +</div> + + +<h2 id="test-env">Setting Up the Testing Environment</h2> + +<p>The Android Market publisher site provides configuration tools that let you +and others test licensing on your application before it is published. As you are +implementing licensing, you can make use of the publisher site tools to test +your application's Policy and handling of different licensing responses and +error conditions.</p> + +<p>The main components of the test environment for licensing include: </p> + +<ul> +<li>A "Test response" configuration in your publisher account that lets you +control the licensing response returned, when the server processes a license +check for an uploaded application from the publisher account or a test +account.</li> +<li>An optional set of test accounts that will receive the configured test +response when checking the license of an application that you have uploaded +(regardless whether the application is published or not).</li> +<li>A runtime environment for the application that includes the Android Market +application or Google APIs Add-On, on which the user is signed in to the +publisher account or one of the test accounts. +</ul> + +<p>Setting up the test environment properly involves:</p> + +<ol> +<li><a href="#test-response">Setting static test responses</a> that are returned by the licensing server.</li> +<li><a href="#test-acct-setup">Setting up test accounts</a> as needed.</li> +<li><a href="#acct-signin">Signing in</a> properly to an emulator or device, before initiating a license check test.</li> +</ol> + +<p>The sections below provide more information.</p> + + +<h3 id="test-response">Setting test responses for license checks</h3> + +<p>Android Market provides a configuration setting in your publisher account +that lets you override the normal processing of a license check for an +application you have uploaded and return a specified response code. The setting +is for testing only and applies <em>only</em> to license checks for applications +that you have uploaded. For other users (users not signed in to test accounts), +the server always processes license checks according to normal rules. </p> + +<p>To set a test response for your account, sign in to your publisher account +and click "Edit Profile". In the Edit Profile page, locate the Test Response +menu in the Licensing panel, shown below. You can select from the full set of +valid server response codes to control the response or condition you want to +test in your application.</p> + +<p>In general, you should make sure to test your application's licensing +implementation with every response code available in the Test Response" menu. +For a description of the codes, see <a href="#server-response-codes">Server +Response Codes</a>, below.</p> + +<div style="margin-bottom:2em;" id="licensing_test_response"> + +<img src="{@docRoot}images/licensing_test_response.png" style="text-align:left;margin-bottom:0;" /> +<div style="margin:0 2em;padding:0"><strong>Figure 7.</strong> The Licensing +panel of your account's Edit Profile page, showing the Test Accounts field and the +Test Response menu.</div> +</div> + +<p>Note that the test response that you configure applies account-wide — +that is, it applies not to a single application, but to <em>all</em> +applications associated with the publisher account. If you are testing multiple +applications at once, changing the test response will affect all of those +applications on their next license check.</p> + +<p>Finally, before you can successfully send these test responses to your +application, you must sign in to the device or emulator on which the application +is installed, and from which it is querying the server. Specifically, you must +sign using either your publisher account or one of the test accounts that you +have set up. For more information about test accounts, see the next section.</p> + +<p>See <a href="#server-response-codes">Server Response Codes</a> for a list of +test responses available and their meanings. </p> + + +<h3 id="test-acct-setup">Setting up test accounts</h3> + +<p>In some cases, you might want to let multiple teams of developers test +licensing on applications that will ultimately be published through your +publisher account, but without giving them access to your publisher account's +sign-in credentials. To meet that need, the Android Market publisher site lets +you set up one or more optional <em>test accounts</em> — accounts that are +authorized to query the licensing server and receive static test responses from +your publisher account.</p> + +<p>Test accounts are standard Google accounts that you register on your +publisher account, such that they will receive the test response for +applications that you have uploaded. Developers can then sign in to their +devices or emulators using the test account credentials and initiate license +checks from installed applications. When the licensing server receives a license +check from a user of a test account, it returns the static test response +configured for the publisher account. </p> + +<p>Necessarily, there are limitations on the access and permissions given to +users signed in through test accounts, including:</p> + +<ul> +<li>Test account users can query the licensing server only for applications that +are already uploaded to the publisher account. </li> +<li>Test account users do not have permission to upload applications to your +publisher account.</li> +<li>Test account users do not have permission to set the publisher account's +static test response.</li> +</ul> + +<p>The table below summarizes the differences in capabilities, between the +publisher account, a test account, and any other account.</p> + +<p class="table-caption" id="acct-types-table"><strong>Table 1.</strong> +Differences in account types for testing licensing.</p> + +<table> +<tr> +<th>Account Type</th> +<th>Can check license before upload?</th> +<th>Can receive test response?</th> +<th>Can set test response?</th> +</tr> + +<tr> +<td>Publisher account</td> +<td>Yes</td> +<td>Yes</td> +<td>Yes</td> +</tr> + +<tr> +<td>Test account</td> +<td>No</td> +<td>Yes</td> +<td>No</td> +</tr> + +<tr> +<td>Other</td> +<td>No</td> +<td>No</td> +<td>No</td> +</tr> +</table> + +<h4 id="reg-test-acct">Registering test accounts on the publisher account</h4> + +<p>To get started, you need to register each test account in your publisher +account. As shown in <a href="#licensing_test_response">Figure 7</a>, above, you +register test accounts in the Licensing panel of your publisher account's Edit +Profile page. Simply enter the accounts as a comma-delimited list and click +<strong>Save</strong> to save your profile changes.</p> + +<p>You can use any Google account as a test account. If you want to own and +control the test accounts, you can create the accounts yourself and distribute +the credentials to your developers or testers.</p> + +<h4 id="test-app-upload">Handling application upload and distribution for test +account users</h4> + +<p>As mentioned above, users of test accounts can only receive static test +responses for applications that are uploaded to the publisher account. Since +those users do not have permission to upload applications, as the publisher you +will need to work with those users to collect apps for upload and distribute +uploaded apps for testing. You can handle collection and distribution in any way +that is convenient. </p> + +<p>Once an application is uploaded and becomes known to the licensing server, +developers and testers can continue modify the application in their local +development environment, without having to upload new versions. You only need to +upload a new version if the local application increments the +<code>versionCode</code> attribute in the manifest file. </p> + +<h4 id="test-key">Distributing your public key to test account users</h4> + +<p>The licensing server handles static test responses in the normal way, +including signing the license response data, adding extras parameters, and so +on. To support developers who are implementing licensing using test accounts, +rather than having access to the publisher account, you will need to distribute +your public key to them. Developers without access to the publisher site do not +have access to your public key, and without the key they won't be able to +verify license responses. </p> + +<p>Note that if you decide to generate a new licensing key pair for your account +for some reason, you need to notify all users of test accounts. For +testers, you can embed the new key in the application package and distribute it +to users. For developers, you will need to distribute the new key to them +directly. </p> + + +<h3 id="acct-signin">Signing in to an authorized account in the runtime +environment</h3> + +<p>The licensing service is designed to determine whether a given user is +licensed to use a given application — during a license check, the Android +Market application gathers the user ID from the primary account on the system +and sends it to the server, together with the package name of the application +and other information. However, if there is no user information available, the +license check cannot succeed, so the Android Market application terminates the +request and returns an error to the application. </p> + +<p>During testing, to ensure that your application can successfully query the +licensing server, you must make sure that you sign in to an account <em>on the +device or emulator</em> using:</p> + +<ul> +<li>The credentials of a publisher account, or</li> +<li>The credentials of a test account that is registered with a publisher +account</li> +</ul> + + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>Signing in to a Google account on an emulator</h2> + +<p>If you are testing licensing on an emulator, you need to sign in to a Google +account on the emulator. If you do not see an option to create a new Google +account, the problem might be that your AVD is running a standard Android system +image, rather than the Google APIs Add-On, API 8 (release 2) or higher. </p> + +<p style="margin-top:.5em;">For more information, see <a +href="#runtime-setup">Setting up the runtime environment</a>, above.</p> + +</div> +</div> + +<p>Signing in using a publisher account offers the advantage of letting your +applications receive static test responses even before the applications are +uploaded to the publisher site.</p> + +<p>If you are part of a larger organization or are working with external groups +on applications that will be published through your site, you will more likely +want to distribute test accounts instead, then use those to sign in during +testing. </p> + +<p>To sign in on a device or emulator, follow the steps below. The preferred +approach is to sign in as the primary account — however, if there are +other accounts already in use on the device or emulator, you can create an +additional account and sign in to it using the publisher or test account +credentials. </p> + +<ol> +<li>Open Settings > Accounts & sync</li> +<li>Select <strong>Add Account</strong> and choose to add a "Google" account. +</li> +<li>Select <strong>Next</strong> and then <strong>Sign in</strong>.</li> +<li>Enter the username and password of either the publisher account or a test +account that is registered in the publisher account.</li> +<li>Select <strong>Sign in</strong>. The system signs you in to the new +account.</li> +</ol> + +<p>Once you are signed in, you can begin testing licensing in your application +(if you have completed the LVL integration steps above). When your application +initiates a license check, it will receive a response containing the static test +response configured on the publisher account. </p> + +<p>Note that, if you are using an emulator, you will need to sign in to the +publisher account or test account each time you wipe data when restarting the +emulator.</p> + +<div style="margin:2em 1em 1em 1em;"> + +<img src="{@docRoot}images/licensing_device_signin.png" style="text-align:left;" /> +<div style="margin:.25em 1.25em;padding:0"><strong>Figure 8.</strong> Example of +setting up a Google account on a device or emulator.</div> +</div> + +<h2 id="app-obfuscation">Obfuscating Your Application</h2> + +<p>To ensure the security of your application, particularly for a paid +application that uses licensing and/or custom constraints and protections, it's +very important to obfuscate your application code. Properly obfuscating your +code makes it more difficult for a malicious user to decompile the application's +bytecode, modify it — such as by removing the license check — +and then recompile it.</p> + +<p>Several obfuscator programs are available for Android applications, including +<a href="http://proguard.sourceforge.net/">ProGuard</a>, which also offers +code-optimization features. The use of ProGuard or a similar program to obfuscate +your code is <em>strongly recommended</em> for all applications that use Android +Market Licensing. </p> + +<h2 id="app-publishing">Publishing a Licensed Application</h2> + +<p>When you are finished testing your license implementation, you are ready to +publish the application on Android Market. Follow the normal steps to <a +href="{@docRoot}guide/publishing/preparing.html">prepare</a>, <a +href="{@docRoot}guide/publishing/app-signing.html">sign</a>, and then <a +href="{@docRoot}guide/publishing/publishing.html">publish the application</a>. +</p> + +<h4>Removing Copy Protection</h4> + +<p>After uploading your licensed application, remember to remove copy protection +from the application, if it is currently used. To check and remove copy +protection, sign in to the publisher site and go the application's upload +details page. In the Publishing options section, make sure that the Copy +Protection radio button selection is "Off".</p> + +<h4>Considerations for Free Apps</h4> + +<p>Licensing is currently supported only for paid applications. If you already +published your application as free, you won't be able to upload an updated +version that includes licensing (that is, an application that uses the same +package name and that includes the <a href="#manifest-permission">licensing +permission</a>). Here are some points to keep in mind:</p> + +<ul> +<li>If you want to offer a free version of your application that provides a +reduced feature set (or that offers the full feature set for trial period), the +free version of your application must not include the licensing permission and +must use a different package name than the paid version of the app.</li> +<li>If you want to offer a paid version of your free application that uses +licensing, you can do so under a new package name.</li> +</ul> + +<h2 id="support">Where to Get Support</h2> + +<p>If you have questions or encounter problems while implementing or deploying +publishing in your applications, please use the support resources listed in the +table below. By directing your queries to the correct forum, you can get the +support you need more quickly. </p> + +<p class="table-caption"><strong>Table 2.</strong> Developer support resources +for Android Market Licensing Service.</p> + +<table> + +<tr> +<th>Support Type</th> +<th>Resource</th> +<th>Range of Topics</th> +</tr> +<tr> +<td rowspan="2">Development and testing issues</td> +<td>Google Groups: <a +href="http://groups.google.com/group/android-developers">android-developers</a> +</td> +<td rowspan="2">LVL download and integration, library projects, Policy +questions, user experience ideas, handling of responses, Obfuscator, IPC, test +environment setup</td> +</tr> +<tr> +<td>Stack Overflow: <a +href="http://stackoverflow.com/questions/tagged/android">http://stackoverflow.com/questions/tagged/android</a></td> +</tr> +<tr> +<td rowspan="2">Accounts, publishing, and deployment issues</td> +<td><a href="http://www.google.com/support/forum/p/Android+Market">Android +Market Help Forum</a></td> +<td rowspan="2">Publisher accounts, licensing key pair, test accounts, server +responses, test responses, application deployment and results</td> +</tr> +<tr> +<td><a +href="http://market.android.com/support/bin/answer.py?answer=186113">Market +Licensing Support FAQ</a></td> +</tr> +<tr> +<td>LVL issue tracker</td> +<td><a href="http://code.google.com/p/marketlicensing/issues/">Marketlicensing +project issue tracker</a></td> +<td>Bug and issue reports related specifically to the LVL source code classes +and interface implementations</td> +</tr> + +</table> + +<p>For general information about how to post to the groups listed above, see <a +href="{@docRoot}resources/community-groups.html">Developer Forums</a> document +in the Resources tab.</p> + +<h2 id="lvl-summary">Summary of LVL Classes and Interfaces</h2> + +<p>The table below lists all of the source files in the License Verification +Library (LVL) available through the Android SDK. All of the files are part of +the <code>com.android.vending.licensing</code> package.</p> + +<p class="table-caption"><strong>Table A-1.</strong> Summary of LVL library +classes and interfaces.</p> + +<div style="width:99%"> +<table width="100%"> + +<tr> +<th width="15%">Category</th> +<th width="20%">Name</th> +<th width="100%">Description</th> +</tr> + +<tr> +<td rowspan="2">License check and result</td> +<td>LicenseChecker</td> +<td>Class that you instantiate (or subclass) to initiate a license check.</td> +</tr> +<tr> +<td><em>LicenseCheckerCallback</em></td> +<td>Interface that you implement to handle result of the license check.</td> +</tr> + +<tr> +<td rowspan="3" width="15%">Policy</td> +<td width="20%"><em>Policy</em></td> +<td width="100%">Interface that you implement to determine whether to allow +access to the application, based on the license response. </td> +</tr> +<tr> +<td>ServerManagedPolicy</td> +<td width="100%">Default Policy implementation. Uses settings provided by the +licensing server to manage local storage of license data, license validity, +retry.</td> +</tr> +<tr> +<td>StrictPolicy</td> +<td>Alternative Policy implementation. Enforces licensing based on a direct +license response from the server only. No caching or request retry.</td> +</tr> + +<tr> +<td rowspan="2" width="15%">Data obfuscation <br><em>(optional)</em></td> +<td width="20%"><em>Obfuscator</em></td> +<td width="100%">Interface that you implement if you are using a Policy (such as +ServerManagedPolicy) that caches license response data in a persistent store. +Applies an obfuscation algorithm to encode and decode data being written or +read.</td> +</tr> +<tr> +<td>AESObfuscator</td> +<td>Default Obfuscator implementation that uses AES encryption/decryption +algorithm to obfuscate/unobfuscate data.</td> +</tr> + +<tr> +<td rowspan="2" width="15%">Device limitation<br><em>(optional)</em></td> +<td width="20%"><em>DeviceLimiter</em></td> +<td width="100%">Interface that you implement if you want to restrict use of an +application to a specific device. Called from LicenseValidator. Implementing +DeviceLimiter is not recommended for most applications because it requires a +backend server and may cause the user to lose access to licensed applications, +unless designed with care.</td> +</tr> +<tr> +<td>NullDeviceLimiter</td> +<td>Default DeviceLimiter implementation that is a no-op (allows access to all +devices).</td> +</tr> + +<tr> +<td rowspan="6" width="15%">Library core, no integration needed</td> +<td width="20%">ResponseData</td> +<td width="100%">Class that holds the fields of a license response.</td> +</tr> +<tr> +<td>LicenseValidator</td> +<td>Class that decrypts and verifies a response received from the licensing +server.</td> +</tr> +<tr> +<td>ValidationException</td> +<td>Class that indicates errors that occur when validating the integrity of data +managed by an Obfuscator.</td> +</tr> +<tr> +<td>PreferenceObfuscator</td> +<td>Utility class that writes/reads obfuscated data to the system's +{@link android.content.SharedPreferences} store.</td> +</tr> +<tr> +<td><em>ILicensingService</em></td> +<td>One-way IPC interface over which a license check request is passed to the +Android Market client.</td> +</tr> +<tr> +<td><em>ILicenseResultListener</em></td> +<td>One-way IPC callback implementation over which the application receives an +asynchronous response from the licensing server.</td> +</tr> + +</table> +</div> + + +<h2 id="server-response-codes">Server Response Codes</h2> + +<p>The table below lists all of the license response codes supported by the +licensing server. In general, an application should handle all of these response +codes. By default, the LicenseValidator class in the LVL provides all of the +necessary handling of these response codes for you. </p> + +<p class="table-caption"><strong>Table A-2.</strong> Summary of response codes +returned by the Android Market server in a license response.</p> + +<table> + +<tr> +<th>Response Code</th> +<th>Description</th> +<th>Signed?</th> +<th>Extras</th> +<th>Comments</th> +</tr> +<tr> +<td>LICENSED</td> +<td>The application is licensed to the user. The user has purchased the +application or the application is free.</td> +<td>Yes</td> +<td><code>VT</code>, <code>GT</code>, <code>GR</code></td> +<td><em>Allow access according to Policy constraints.</em></td> +</tr> +<tr> +<td>LICENSED_OLD_KEY</td> +<td>The application is licensed to the user, but there is an updated application +version available that is signed with a different key. </td> +<td>Yes </td> +<td><code>VT</code>, <code>GT</code>, <code>GR</code>, <code>UT</code></td> +<td><em>Optionally allow access according to Policy constraints.</em> +<p style="margin-top:.5em;">Can indicate that the key pair used by the installed +application version is invalid or compromised. The application can allow access +if needed or inform the user that an upgrade is available and limit further use +until upgrade.</p> +</td> +</tr> +<tr> +<td>NOT_LICENSED</td> +<td>The application is not licensed to the user.</td> +<td>No</td> +<td></td> +<td><em>Do not allow access.</em></td> +</tr> +<tr> +<td>ERROR_CONTACTING_SERVER</td> +<td>Local error — the Android Market application was not able to reach the +licensing server, possibly because of network availability problems. </td> +<td>No</td> +<td></td> +<td><em>Retry the license check according to Policy retry limits.</em></td> +</tr> +<tr> +<td>ERROR_SERVER_FAILURE</td> +<td>Server error — the server could not load the publisher account's key +pair for licensing.</td> +<td>No</td> +<td></td> +<td><em>Retry the license check according to Policy retry limits.</em> +</td> +</tr> +<tr> +<td>ERROR_INVALID_PACKAGE_NAME</td> +<td>Local error — the application requested a license check for a package +that is not installed on the device. </td> +<td>No </td> +<td></td> +<td><em>Do not retry the license check.</em> +<p style="margin-top:.5em;">Typically caused by a developement error.</p> +</td> +</tr> +<tr> +<td>ERROR_NON_MATCHING_UID</td> +<td>Local error — the application requested a license check for a package +whose UID (package, user ID pair) does not match that of the requesting +application. </td> +<td>No </td> +<td></td> +<td><em>Do not retry the license check.</em> +<p style="margin-top:.5em;">Typically caused by a developement error.</p> +</td> +</tr> +<tr> +<td>ERROR_NOT_MARKET_MANAGED</td> +<td>Server error — the application (package name) was not recognized by +Android Market. </td> +<td>No</td> +<td></td> +<td><em>Do not retry the license check.</em> +<p style="margin-top:.5em;">Can indicate that the application was not published +through Android Market or that there is an development error in the licensing +implementation.</p> +</td> +</tr> + +</table> + + +<h2 id="extras">Server Response Extras</h2> + +<p>The licensing server includes several settings in certain types of license +responses, to assist the application and its Policy in managing access to the +application across the 24-hour refund period and other conditions. Specifically, +the server provides recommended values for the application's license validity +period, retry grace period, maximum allowable retry count, and other settings. +The server appends the settings as key-value pairs in the license response +"extras" field. </p> + +<p>Any Policy implementation can extract the extras settings from the license +response and use them as needed. The LVL default Policy implementation, <a +href="#ServerManagedPolicy">ServerManagedPolicy</a>, serves as a working +implementation and an illustration of how to obtain, store, and use the +settings. </p> + +<p class="table-caption"><strong>Table A-3.</strong> Summary of +license-management settings supplied by the Android Market server in a license +response.</p> + +<table> +<tr> +<th>Extra</th><th>Description</th> +</tr> + +<tr> + <td>VT</td> + <td>License validity timestamp. Specifies the date/time at which the current +(cached) license response expires and must be rechecked on the licensing server. + </td> +</tr> +<tr> + <td>GT</td> + <td>Grace period timestamp. Specifies the end of the period during which a +Policy may allow access to the application, even though the response status is +RETRY. <p>The value is managed by the server, however a typical value would be 5 +or more days.</p></td> +</tr> +<tr> + <td>GR</td> + <td>Maximum retries count. Specifies how many consecutive RETRY license checks +the Policy should allow, before denying the user access to the application. +<p>The value is managed by the server, however a typical value would be "10" or +higher.</p></td> +</tr> +<tr> + <td>UT</td> + <td>Update timestamp. Specifies the day/time when the most recent update to +this application was uploaded and published. <p>The server returns this extra +only for LICENSED_OLD_KEYS responses, to allow the Policy to determine how much +time has elapsed since an update was published with new licensing keys before +denying the user access to the application. </p></td> +</tr> + +</table> + +<p>The sections below provide more information about the server-provided +settings and how to use them. </p> + +<h4>License validity period</h4> + +<p>The Android Market licensing server sets a license validity period for all +downloaded applications. The period expresses the interval of time over which an +application's license status should be considered as unchanging and cacheable by +a licensing Policy in the application. The licensing server includes the +validity period in its response to all license checks, appending an +end-of-validity timestamp to the response as an extra under the key "VT". A +Policy can extract the VT key value and use it to conditionally allow access to +the application without rechecking the license, until the validity period +expires. </p> + +<p>The license validity signals to a licensing Policy when it must recheck the +licensing status with the licensing server. It is <em>not</em> intended to imply +whether an application is actually licensed for use. That is, when an +application's license validity period expires, this does not mean that the +application is no longer licensed for use — rather, it indicates only that +the Policy must recheck the licensing status with the server. It follows that, +as long as the license validity period is not expired, it is acceptable for the +Policy to cache the initial license status locally and return the cached license +status instead of sending a new license check to the server.</p> + +<p>The licensing server manages the validity period as a means of helping the +application properly enforce licensing across the refund period offered by +Android Market for paid applications. It sets the validity period based on +whether the application was purchased and, if so, how long ago. Specifically, +the server sets a validity period as follows:</p> + +<ul> +<li>For a paid application, the server sets the initial license validity period +so that the license reponse remains valid for as long as the application is +refundable. A licensing Policy in the application may cache the +result of the initial license check and does not need to recheck the license +until the validity period has expired.</li> +<li>When an application is no longer refundable, the server +sets a longer validity period — typically a number of days. </li> +<li>For a free application, the server sets the validity period to a very high +value (<code>long.MAX_VALUE</code>). This ensures that, provided the Policy has +cached the validity timestamp locally, it will not need to recheck the +license status of the application in the future.</li> +</ul> + +<p>The ServerManagedPolicy implementation uses the extracted timestamp +(<code>mValidityTimestamp</code>) as a primary condition for determining whether +to recheck the license status with the server before allowing the user access to +the application. </p> + +<h4>Retry period and maximum retry count</h4> + +<p>In some cases, system or network conditions can prevent an application's +license check from reaching the licensing server, or prevent the server's +response from reaching the Android Market client application. For example, the +user might launch an application when there is no cell network or data +connection available — such as when on an airplane — or when the +network connection is unstable or the cell signal is weak. </p> + +<p>When network problems prevent or interrupt a license check, the Android +Market client notifies the application by returning a "RETRY" response code to +the Policy's <code>processServerResponse()</code> method. In the case of system +problems, such as when the application is unable to bind with Android Market's +ILicensingService implementation, the LicenseChecker library itself calls the +Policy <code>processServerResonse()</code> method with a "RETRY" response code. +</p> + +<p>In general, the RETRY response code is a signal to the application that an +error has occurred that has prevented a license check from completing. + +<p>The Android Market server helps an application to manage licensing under +error conditions by setting a retry "grace period" and a recommended maximum +retries count. The server includes these values in all license check responses, +appending them as extras under the keys "GT" and "GR". </p> + +<p>The application Policy can extract the GT and GR extras and use them to +conditionally allow access to the application, as follows:</p> + +<ul> +<li>For a license check that results in a RETRY response, the Policy should +cache the RETRY response code and increment a count of RETRY responses.</li> +<li>The Policy should allow the user to access the application, provided that +either the retry grace period is still active or the maximum retries count has +not been reached.</li> +</ul> + +<p>The ServerManagedPolicy uses the server-supplied GT and GR values as +described above. The example below shows the conditional handling of the retry +responses in the <code>allow()</code> method. The count of RETRY responses is +maintained in the <code>processServerResponse()</code> method, not shown. </p> + + +<pre> public boolean allowAccess() { + long ts = System.currentTimeMillis(); + if (mLastResponse == LicenseResponse.LICENSED) { + // Check if the LICENSED response occurred within the validity timeout. + if (ts <= mValidityTimestamp) { + // Cached LICENSED response is still valid. + return true; + } + } else if (mLastResponse == LicenseResponse.RETRY && + ts < mLastResponseTime + MILLIS_PER_MINUTE) { + // Only allow access if we are within the retry period or we haven't used up our + // max retries. + return (ts <= mRetryUntil || mRetryCount <= mMaxRetries); + } + return false; + }</pre> + diff --git a/docs/html/guide/publishing/preparing.jd b/docs/html/guide/publishing/preparing.jd index c1c635185c46..442c12acc2ca 100644 --- a/docs/html/guide/publishing/preparing.jd +++ b/docs/html/guide/publishing/preparing.jd @@ -39,13 +39,14 @@ Applications</a> document. </p> <ol> <li>Test your application extensively on an actual device </li> <li>Consider adding an End User License Agreement in your application</li> +<li>Consider adding licensing support</li> <li>Specify an icon and label in the application's manifest</li> <li>Turn off logging and debugging and clean up data/files</li> </ol> <p>Before you do the final compile of your application:</p> -<ol start="5"> +<ol start="6"> <li>Version your application</li> <li>Obtain a suitable cryptographic key</li> <li>Register for a Maps API Key, if your application is using MapView elements</li> @@ -53,7 +54,7 @@ Applications</a> document. </p> <p><em>Compile your application...</em></p> <p>After compiling your application:</p> -<ol start="8"> +<ol start="9"> <li>Sign your application</li> <li>Test your compiled application</li> </ol> @@ -101,7 +102,19 @@ application</h3> <p>To protect your person, organization, and intellectual property, you may want to provide an End User License Agreement (EULA) with your application. -<h3 id="iconlabel">3. Specify an icon and label in the application's manifest</h3> +<h3 id="eula">3. Consider adding support for Android Market Licensing</h3> + +<p>If you are publishing a paid application through Android Market, consider +adding support for Android Market Licensing. Licensing lets you control access +to your application based on whether the current user has purchased it. +Using Android Market Licensing is optional. + +<p>For complete information about Android Market Licensing Service and how to +use it in your application, see <a +href="{@docRoot}guide/publishing/licensing.html">Licensing Your +Applications</a>.</p> + +<h3 id="iconlabel">4. Specify an icon and label in the application's manifest</h3> <p>The icon and label that you specify in an application's manifest are important because they are displayed to users as your application's icon and @@ -116,7 +129,7 @@ display the icon and label to users. </p> <p>As regards the design of your icon, you should try to make it match as much as possible the style used by the built-in Android applications.</p> -<h3 id="logging">4. Turn off logging and debugging and clean up data/files</h3> +<h3 id="logging">5. Turn off logging and debugging and clean up data/files</h3> <p>For release, you should make sure that debug facilities are turned off and that debug and other unnecessary data/files are removed from your application @@ -133,7 +146,7 @@ code.</li> <h2 id="finalcompile">Before you do the final compile of your application</h2> -<h3 id="versionapp">5. Version your application</h3> +<h3 id="versionapp">6. Version your application</h3> <p>Before you compile your application, you must make sure that you have defined a version number for your application, specifying an appropriate value for both @@ -152,7 +165,7 @@ element in the application's manifest file, using appropriate values. </p> application, see <a href="{@docRoot}guide/publishing/versioning.html">Versioning Your Applications</a>.</p> -<h3 id="cryptokey">6. Obtain a suitable cryptographic key</h3> +<h3 id="cryptokey">7. Obtain a suitable cryptographic key</h3> <p>If you have read and followed all of the preparation steps up to this point, your application is compiled and ready for signing. Inside the .apk, the @@ -173,7 +186,7 @@ elements.</li> <li>Sign your application for release, later in the preparation process</li> </ul> -<h3 id="mapsApiKey">7. Register for a Maps API Key, if your application is using +<h3 id="mapsApiKey">8. Register for a Maps API Key, if your application is using MapView elements</h3> <div class="sidebox-wrapper"> @@ -231,7 +244,7 @@ you can compile your application for release.</p> <h2 id="post-compile">After compiling your application</h2> -<h3 id="signapp">8. Sign your application</h3> +<h3 id="signapp">9. Sign your application</h3> <p>Sign your application using your private key and then align it with the {@code zipalign} tool. Signing your application @@ -239,7 +252,7 @@ correctly is critically important. Please see <a href="{@docRoot}guide/publishing/app-signing.html">Signing Your Applications</a> for complete information. </p> -<h3 id="testapp">9. Test your compiled and signed application</h3> +<h3 id="testapp">10. Test your compiled and signed application</h3> <p>Before you release your compiled application, you should thoroughly test it on the target mobile device (and target network, if possible). In particular, diff --git a/docs/html/guide/publishing/publishing.jd b/docs/html/guide/publishing/publishing.jd index 0c087efa0120..9b470c89fbc6 100644 --- a/docs/html/guide/publishing/publishing.jd +++ b/docs/html/guide/publishing/publishing.jd @@ -18,7 +18,8 @@ page.title=Publishing Your Applications <ol> <li><a href="#overview">Publishing on Android Market</a> <ol> - <li><a href="#marketupgrade">Publishing Updates on Android Market</a> + <li><a href="#marketupgrade">Publishing Updates on Android Market</a></li> + <li><a href="#marketLicensing">Using Android Market Licensing Service</a></li> <li><a href="#marketintent">Using Intents to Launch the Market Application</a></li> </ol></li> <!-- @@ -30,6 +31,7 @@ page.title=Publishing Your Applications <h2>See also</h2> <ol> +<li><a href="{@docRoot}guide/publishing/licensing.html">Licensing Your Applications</a></li> <li><a href="{@docRoot}guide/publishing/preparing.html">Preparing to Publish</a></li> </ol> @@ -59,7 +61,7 @@ the .apk. Your application is now ready for publishing. </p> <p>The sections below provide information about publishing your Android application to mobile device users.</p> -<h2 id="market">Publishing on Android Market</h2> +<h2 id="overview">Publishing on Android Market</h2> <p>Android Market is a hosted service that makes it easy for users to find and download Android applications to their Android-powered devices, and makes it @@ -121,7 +123,26 @@ certificate do <em>not</em> match those of the existing version, Market will consider it a new application and will not offer it to users as an update.</p> +<h3 id="marketLicensing">Using Android Market Licensing Service</h3> +<p>Android Market offers a licensing service that lets you enforce licensing +policies for paid applications that you publish through Android Market. With +Android Market Licensing, your applications can query Android Market at run time +to obtain their licensing status for the current user, then allow or disallow +further use as appropriate. Using the service, you can apply a flexible +licensing policy on an application-by-application basis — each +application can enforce its licensing status in the way most appropriate +for it. </p> + +<p>Any application that you publish through Android Market can use the Android +Market Licensing Service. The service uses no dedicated framework APIs, you can +add licensing to any legacy application that uses a minimum API level of 3 or +higher.</p> + +<p>For complete information about Android Market Licensing Service and how to +use it in your application, see <a +href="{@docRoot}guide/publishing/licensing.html">Licensing Your +Applications</a>.</p> <h3 id="marketintent">Using Intents to Launch the Market Application on diff --git a/docs/html/images/licensing_add_library.png b/docs/html/images/licensing_add_library.png Binary files differnew file mode 100644 index 000000000000..90b4435ab017 --- /dev/null +++ b/docs/html/images/licensing_add_library.png diff --git a/docs/html/images/licensing_arch.png b/docs/html/images/licensing_arch.png Binary files differnew file mode 100644 index 000000000000..ba7484a185e3 --- /dev/null +++ b/docs/html/images/licensing_arch.png diff --git a/docs/html/images/licensing_device_signin.png b/docs/html/images/licensing_device_signin.png Binary files differnew file mode 100644 index 000000000000..a4f5f8825690 --- /dev/null +++ b/docs/html/images/licensing_device_signin.png diff --git a/docs/html/images/licensing_flow.png b/docs/html/images/licensing_flow.png Binary files differnew file mode 100644 index 000000000000..b33119eae436 --- /dev/null +++ b/docs/html/images/licensing_flow.png diff --git a/docs/html/images/licensing_gapis_8.png b/docs/html/images/licensing_gapis_8.png Binary files differnew file mode 100644 index 000000000000..43ad26233261 --- /dev/null +++ b/docs/html/images/licensing_gapis_8.png diff --git a/docs/html/images/licensing_package.png b/docs/html/images/licensing_package.png Binary files differnew file mode 100644 index 000000000000..5da5632672b6 --- /dev/null +++ b/docs/html/images/licensing_package.png diff --git a/docs/html/images/licensing_public_key.png b/docs/html/images/licensing_public_key.png Binary files differnew file mode 100644 index 000000000000..163020999485 --- /dev/null +++ b/docs/html/images/licensing_public_key.png diff --git a/docs/html/images/licensing_test_response.png b/docs/html/images/licensing_test_response.png Binary files differnew file mode 100644 index 000000000000..ead21523ff12 --- /dev/null +++ b/docs/html/images/licensing_test_response.png diff --git a/docs/html/resources/faq/commontasks.jd b/docs/html/resources/faq/commontasks.jd index 2f09b0019fd9..9c32e9cdf609 100644 --- a/docs/html/resources/faq/commontasks.jd +++ b/docs/html/resources/faq/commontasks.jd @@ -158,7 +158,7 @@ It is not necessary to put external JARs in the assets folder. <ul> <li>Create an {@link android.app.Dialog app.Dialog} class </li> <li>Create an {@link android.app.AlertDialog app.AlertDialog} class </li> - <li>Set the {@link android.R.style#Theme_Dialog} <em>theme</em> attribute to <code>@android:style/Theme.Dialog</code> + <li>Set the {@link android.R.style#Theme_Dialog} <em>theme</em> attribute to <code>@android:style/Theme.Dialog</code> in your AndroidManifest.xml file. For example: <pre><activity class="AddRssItem" android:label="Add an item" android:theme="@android:style/Theme.Dialog"/></pre></li> </ul> diff --git a/include/binder/Parcel.h b/include/binder/Parcel.h index fd0fc1f3067c..32c9a1d5a9b6 100644 --- a/include/binder/Parcel.h +++ b/include/binder/Parcel.h @@ -26,11 +26,12 @@ // --------------------------------------------------------------------------- namespace android { +class Flattenable; class IBinder; +class IPCThreadState; class ProcessState; class String8; class TextOutput; -class Flattenable; struct flat_binder_object; // defined in support_p/binder_module.h @@ -61,10 +62,13 @@ public: // Parses the RPC header, returning true if the interface name // in the header matches the expected interface from the caller. - // If strict_policy_out is non-NULL, the RPC header's StrictMode policy - // mask is returned. + // + // Additionally, enforceInterface does part of the work of + // propagating the StrictMode policy mask, populating the current + // IPCThreadState, which as an optimization may optionally be + // passed in. bool enforceInterface(const String16& interface, - int32_t* strict_policy_out = NULL) const; + IPCThreadState* threadState = NULL) const; bool checkInterface(IBinder*) const; void freeData(); diff --git a/include/gui/Sensor.h b/include/gui/Sensor.h index e696d6386819..2de07b1325a9 100644 --- a/include/gui/Sensor.h +++ b/include/gui/Sensor.h @@ -63,6 +63,7 @@ public: float getMaxValue() const; float getResolution() const; float getPowerUsage() const; + int32_t getMinDelay() const; // Flattenable interface virtual size_t getFlattenedSize() const; @@ -81,6 +82,7 @@ private: float mMaxValue; float mResolution; float mPower; + int32_t mMinDelay; }; // ---------------------------------------------------------------------------- diff --git a/include/gui/SensorEventQueue.h b/include/gui/SensorEventQueue.h index ad36dacea629..6581ae3092f6 100644 --- a/include/gui/SensorEventQueue.h +++ b/include/gui/SensorEventQueue.h @@ -65,7 +65,7 @@ public: status_t setEventRate(Sensor const* sensor, nsecs_t ns) const; // these are here only to support SensorManager.java - status_t enableSensor(int32_t handle, int32_t ms) const; + status_t enableSensor(int32_t handle, int32_t us) const; status_t disableSensor(int32_t handle) const; private: diff --git a/include/media/AudioEffect.h b/include/media/AudioEffect.h index e9ff8a35e7df..c967efbcf71d 100644 --- a/include/media/AudioEffect.h +++ b/include/media/AudioEffect.h @@ -382,10 +382,10 @@ public: * See EffectApi.h for details on effect command() function, valid command codes * and formats. */ - virtual status_t command(int32_t cmdCode, - int32_t cmdSize, + virtual status_t command(uint32_t cmdCode, + uint32_t cmdSize, void *cmdData, - int32_t *replySize, + uint32_t *replySize, void *replyData); @@ -429,10 +429,10 @@ private: virtual void enableStatusChanged(bool enabled) { mEffect->enableStatusChanged(enabled); } - virtual void commandExecuted(int cmdCode, - int cmdSize, + virtual void commandExecuted(uint32_t cmdCode, + uint32_t cmdSize, void *pCmdData, - int replySize, + uint32_t replySize, void *pReplyData) { mEffect->commandExecuted(cmdCode, cmdSize, pCmdData, replySize, pReplyData); } @@ -450,7 +450,11 @@ private: // IEffectClient void controlStatusChanged(bool controlGranted); void enableStatusChanged(bool enabled); - void commandExecuted(int cmdCode, int cmdSize, void *pCmdData, int replySize, void *pReplyData); + void commandExecuted(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t replySize, + void *pReplyData); void binderDied(); diff --git a/include/media/EffectApi.h b/include/media/EffectApi.h index 8c120e53b468..16fb43cf750c 100644 --- a/include/media/EffectApi.h +++ b/include/media/EffectApi.h @@ -284,10 +284,10 @@ typedef int32_t (*effect_process_t)(effect_interface_t self, // //////////////////////////////////////////////////////////////////////////////// typedef int32_t (*effect_command_t)(effect_interface_t self, - int32_t cmdCode, - int32_t cmdSize, + uint32_t cmdCode, + uint32_t cmdSize, void *pCmdData, - int32_t *replySize, + uint32_t *replySize, void *pReplyData); diff --git a/include/media/EffectBassBoostApi.h b/include/media/EffectBassBoostApi.h index b24a5f45abba..75f8d7868386 100644 --- a/include/media/EffectBassBoostApi.h +++ b/include/media/EffectBassBoostApi.h @@ -23,9 +23,10 @@ extern "C" { #endif -// TODO: include OpenSLES_IID.h instead +#ifndef OPENSL_ES_H_ static const effect_uuid_t SL_IID_BASSBOOST_ = { 0x0634f220, 0xddd4, 0x11db, 0xa0fc, { 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b } }; const effect_uuid_t * const SL_IID_BASSBOOST = &SL_IID_BASSBOOST_; +#endif //OPENSL_ES_H_ /* enumerated parameter settings for BassBoost effect */ typedef enum diff --git a/include/media/EffectEnvironmentalReverbApi.h b/include/media/EffectEnvironmentalReverbApi.h index d490f71cd2cf..2233e3fbdf75 100644 --- a/include/media/EffectEnvironmentalReverbApi.h +++ b/include/media/EffectEnvironmentalReverbApi.h @@ -23,9 +23,10 @@ extern "C" { #endif -// TODO: include OpenSLES_IID.h instead +#ifndef OPENSL_ES_H_ static const effect_uuid_t SL_IID_ENVIRONMENTALREVERB_ = { 0xc2e5d5f0, 0x94bd, 0x4763, 0x9cac, { 0x4e, 0x23, 0x4d, 0x6, 0x83, 0x9e } }; const effect_uuid_t * const SL_IID_ENVIRONMENTALREVERB = &SL_IID_ENVIRONMENTALREVERB_; +#endif //OPENSL_ES_H_ /* enumerated parameter settings for environmental reverb effect */ typedef enum @@ -45,20 +46,19 @@ typedef enum REVERB_PARAM_BYPASS } t_env_reverb_params; -//t_reverb_properties is equal to SLEnvironmentalReverbSettings defined in OpenSL ES specification. -typedef struct s_reverb_properties { +//t_reverb_settings is equal to SLEnvironmentalReverbSettings defined in OpenSL ES specification. +typedef struct s_reverb_settings { int16_t roomLevel; int16_t roomHFLevel; int32_t decayTime; int16_t decayHFRatio; int16_t reflectionsLevel; int32_t reflectionsDelay; - int32_t reverbDelay; int16_t reverbLevel; + int32_t reverbDelay; int16_t diffusion; int16_t density; - int16_t padding; -} t_reverb_properties; +} __attribute__((packed)) t_reverb_settings; #if __cplusplus diff --git a/include/media/EffectEqualizerApi.h b/include/media/EffectEqualizerApi.h index cb05b3206a6f..0492ea009a1b 100644 --- a/include/media/EffectEqualizerApi.h +++ b/include/media/EffectEqualizerApi.h @@ -19,8 +19,10 @@ #include <media/EffectApi.h> -// for the definition of SL_IID_EQUALIZER -#include "OpenSLES.h" +#ifndef OPENSL_ES_H_ +static const effect_uuid_t SL_IID_EQUALIZER_ = { 0x0bed4300, 0xddd6, 0x11db, 0x8f34, { 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b } }; +const effect_uuid_t * const SL_IID_EQUALIZER = &SL_IID_EQUALIZER_; +#endif //OPENSL_ES_H_ #if __cplusplus extern "C" { @@ -37,9 +39,16 @@ typedef enum EQ_PARAM_GET_BAND, // Gets the band that has the most effect on the given frequency. EQ_PARAM_CUR_PRESET, // Gets/Sets the current preset. EQ_PARAM_GET_NUM_OF_PRESETS, // Gets the total number of presets the equalizer supports. - EQ_PARAM_GET_PRESET_NAME // Gets the preset name based on the index. + EQ_PARAM_GET_PRESET_NAME, // Gets the preset name based on the index. + EQ_PARAM_PROPERTIES // Gets/Sets all parameters at a time. } t_equalizer_params; +//t_equalizer_settings groups all current equalizer setting for backup and restore. +typedef struct s_equalizer_settings { + uint16_t curPreset; + uint16_t numBands; + uint16_t bandLevels[]; +} t_equalizer_settings; #if __cplusplus } // extern "C" diff --git a/include/media/EffectPresetReverbApi.h b/include/media/EffectPresetReverbApi.h index 34ffffe3bbcb..53205bb751a8 100644 --- a/include/media/EffectPresetReverbApi.h +++ b/include/media/EffectPresetReverbApi.h @@ -23,10 +23,10 @@ extern "C" { #endif -// TODO: include OpenSLES_IID.h instead - +#ifndef OPENSL_ES_H_ static const effect_uuid_t SL_IID_PRESETREVERB_ = { 0x47382d60, 0xddd8, 0x11db, 0xbf3a, { 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b } }; const effect_uuid_t * const SL_IID_PRESETREVERB = &SL_IID_PRESETREVERB_; +#endif //OPENSL_ES_H_ /* enumerated parameter settings for preset reverb effect */ typedef enum diff --git a/include/media/EffectVirtualizerApi.h b/include/media/EffectVirtualizerApi.h index 601c384d54ac..c3d513161279 100644 --- a/include/media/EffectVirtualizerApi.h +++ b/include/media/EffectVirtualizerApi.h @@ -23,9 +23,10 @@ extern "C" { #endif -// TODO: include OpenSLES_IID.h instead +#ifndef OPENSL_ES_H_ static const effect_uuid_t SL_IID_VIRTUALIZER_ = { 0x37cc2c00, 0xdddd, 0x11db, 0x8577, { 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b } }; const effect_uuid_t * const SL_IID_VIRTUALIZER = &SL_IID_VIRTUALIZER_; +#endif //OPENSL_ES_H_ /* enumerated parameter settings for virtualizer effect */ typedef enum diff --git a/include/media/EffectVisualizerApi.h b/include/media/EffectVisualizerApi.h index 1155db8139d1..bef1a4fc5ce2 100644 --- a/include/media/EffectVisualizerApi.h +++ b/include/media/EffectVisualizerApi.h @@ -23,10 +23,11 @@ extern "C" { #endif -//TODO replace by openSL ES include when available +#ifndef OPENSL_ES_H_ static const effect_uuid_t SL_IID_VISUALIZATION_ = { 0xe46b26a0, 0xdddd, 0x11db, 0x8afd, { 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b } }; const effect_uuid_t * const SL_IID_VISUALIZATION = &SL_IID_VISUALIZATION_; +#endif //OPENSL_ES_H_ #define VISUALIZER_CAPTURE_SIZE_MAX 1024 // maximum capture size in samples #define VISUALIZER_CAPTURE_SIZE_MIN 128 // minimum capture size in samples diff --git a/include/media/IEffect.h b/include/media/IEffect.h index 6dad393b35c0..ff04869e06be 100644 --- a/include/media/IEffect.h +++ b/include/media/IEffect.h @@ -33,7 +33,11 @@ public: virtual status_t disable() = 0; - virtual status_t command(int cmdCode, int cmdSize, void *pCmdData, int *pReplySize, void *pReplyData) = 0; + virtual status_t command(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *pReplySize, + void *pReplyData) = 0; virtual void disconnect() = 0; diff --git a/include/media/IEffectClient.h b/include/media/IEffectClient.h index d22daf81e7f8..2f78c98f172c 100644 --- a/include/media/IEffectClient.h +++ b/include/media/IEffectClient.h @@ -31,7 +31,11 @@ public: virtual void controlStatusChanged(bool controlGranted) = 0; virtual void enableStatusChanged(bool enabled) = 0; - virtual void commandExecuted(int cmdCode, int cmdSize, void *pCmdData, int replySize, void *pReplyData) = 0; + virtual void commandExecuted(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t replySize, + void *pReplyData) = 0; }; // ---------------------------------------------------------------------------- diff --git a/include/private/surfaceflinger/SharedBufferStack.h b/include/private/surfaceflinger/SharedBufferStack.h index 633b5436d6b9..1eb178ebc130 100644 --- a/include/private/surfaceflinger/SharedBufferStack.h +++ b/include/private/surfaceflinger/SharedBufferStack.h @@ -273,7 +273,6 @@ public: void setStatus(status_t status); status_t reallocateAll(); status_t reallocateAllExcept(int buffer); - status_t assertReallocate(int buffer); int32_t getQueuedCount() const; Region getDirtyRegion(int buffer) const; @@ -356,13 +355,6 @@ private: inline StatusUpdate(SharedBufferBase* sbb, status_t status); inline ssize_t operator()(); }; - - struct ReallocateCondition : public ConditionBase { - int buf; - inline ReallocateCondition(SharedBufferBase* sbb, int buf); - inline bool operator()() const; - inline const char* name() const { return "ReallocateCondition"; } - }; }; // =========================================================================== diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h index 5be17d3d359f..dab35b3cfa5d 100644 --- a/include/ui/EventHub.h +++ b/include/ui/EventHub.h @@ -60,6 +60,31 @@ namespace android { class KeyLayoutMap; /* + * A raw event as retrieved from the EventHub. + */ +struct RawEvent { + nsecs_t when; + int32_t deviceId; + int32_t type; + int32_t scanCode; + int32_t keyCode; + int32_t value; + uint32_t flags; +}; + +/* Describes an absolute axis. */ +struct RawAbsoluteAxisInfo { + bool valid; // true if the information is valid, false otherwise + + int32_t minValue; // minimum value + int32_t maxValue; // maximum value + int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8 + int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise + + inline int32_t getRange() { return maxValue - minValue; } +}; + +/* * Input device classes. */ enum { @@ -82,7 +107,10 @@ enum { INPUT_DEVICE_CLASS_DPAD = 0x00000020, /* The input device is a gamepad (implies keyboard). */ - INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040 + INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040, + + /* The input device has switches. */ + INPUT_DEVICE_CLASS_SWITCH = 0x00000080, }; /* @@ -114,8 +142,8 @@ public: virtual String8 getDeviceName(int32_t deviceId) const = 0; - virtual int getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue, - int* outMaxValue, int* outFlat, int* outFuzz) const = 0; + virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const = 0; virtual status_t scancodeToKeycode(int32_t deviceId, int scancode, int32_t* outKeycode, uint32_t* outFlags) const = 0; @@ -131,26 +159,19 @@ public: * If the device needs to remain awake longer than that, then the caller is responsible * for taking care of it (say, by poking the power manager user activity timer). */ - virtual bool getEvent(int32_t* outDeviceId, int32_t* outType, - int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, - int32_t* outValue, nsecs_t* outWhen) = 0; + virtual bool getEvent(RawEvent* outEvent) = 0; /* * Query current input state. - * deviceId may be -1 to search for the device automatically, filtered by class. - * deviceClasses may be -1 to ignore device class while searching. */ - virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t scanCode) const = 0; - virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t keyCode) const = 0; - virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses, - int32_t sw) const = 0; + virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0; + virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0; + virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0; /* * Examine key input devices for specific framework keycode support */ - virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, + virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const = 0; }; @@ -165,33 +186,28 @@ public: virtual String8 getDeviceName(int32_t deviceId) const; - virtual int getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue, - int* outMaxValue, int* outFlat, int* outFuzz) const; - + virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const; + virtual status_t scancodeToKeycode(int32_t deviceId, int scancode, int32_t* outKeycode, uint32_t* outFlags) const; virtual void addExcludedDevice(const char* deviceName); - virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t scanCode) const; - virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t keyCode) const; - virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses, - int32_t sw) const; + virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const; + virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const; + virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const; - virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const; + virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) const; - virtual bool getEvent(int32_t* outDeviceId, int32_t* outType, - int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, - int32_t* outValue, nsecs_t* outWhen); + virtual bool getEvent(RawEvent* outEvent); protected: virtual ~EventHub(); private: bool openPlatformInput(void); - int32_t convertDeviceKey_TI_P2(int code); int open_device(const char *device); int close_device(const char *device); @@ -220,6 +236,8 @@ private: int32_t getScanCodeStateLocked(device_t* device, int32_t scanCode) const; int32_t getKeyCodeStateLocked(device_t* device, int32_t keyCode) const; int32_t getSwitchStateLocked(device_t* device, int32_t sw) const; + bool markSupportedKeyCodesLocked(device_t* device, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) const; // Protect all internal state. mutable Mutex mLock; diff --git a/include/ui/Input.h b/include/ui/Input.h index d9b109197c8b..2385973e6926 100644 --- a/include/ui/Input.h +++ b/include/ui/Input.h @@ -23,7 +23,10 @@ #include <android/input.h> #include <utils/Vector.h> +#include <utils/KeyedVector.h> #include <utils/Timers.h> +#include <utils/RefBase.h> +#include <utils/String8.h> /* * Additional private constants not defined in ndk/ui/input.h. @@ -47,21 +50,16 @@ struct AInputEvent { virtual ~AInputEvent() { } }; -namespace android { - /* - * A raw event as retrieved from the EventHub. + * Declare a concrete type for the NDK's input device forward declaration. */ -struct RawEvent { - nsecs_t when; - int32_t deviceId; - int32_t type; - int32_t scanCode; - int32_t keyCode; - int32_t value; - uint32_t flags; +struct AInputDevice { + virtual ~AInputDevice() { } }; + +namespace android { + /* * Flags that flow alongside events in the input dispatch system to help with certain * policy decisions such as waking from device sleep. @@ -424,6 +422,69 @@ private: MotionEvent mMotionEvent; }; +/* + * Describes the characteristics and capabilities of an input device. + */ +class InputDeviceInfo { +public: + InputDeviceInfo(); + InputDeviceInfo(const InputDeviceInfo& other); + ~InputDeviceInfo(); + + struct MotionRange { + float min; + float max; + float flat; + float fuzz; + }; + + void initialize(int32_t id, const String8& name); + + inline int32_t getId() const { return mId; } + inline const String8 getName() const { return mName; } + inline uint32_t getSources() const { return mSources; } + + const MotionRange* getMotionRange(int32_t rangeType) const; + + void addSource(uint32_t source); + void addMotionRange(int32_t rangeType, float min, float max, float flat, float fuzz); + void addMotionRange(int32_t rangeType, const MotionRange& range); + + inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; } + inline int32_t getKeyboardType() const { return mKeyboardType; } + +private: + int32_t mId; + String8 mName; + uint32_t mSources; + int32_t mKeyboardType; + + KeyedVector<int32_t, MotionRange> mMotionRanges; +}; + +/* + * Provides remote access to information about an input device. + * + * Note: This is essentially a wrapper for Binder calls into the Window Manager Service. + */ +class InputDeviceProxy : public RefBase, public AInputDevice { +protected: + InputDeviceProxy(); + virtual ~InputDeviceProxy(); + +public: + static void getDeviceIds(Vector<int32_t>& outIds); + + static sp<InputDeviceProxy> getDevice(int32_t id); + + inline const InputDeviceInfo* getInfo() { return & mInfo; } + + // TODO add hasKeys, keymap, etc... + +private: + InputDeviceInfo mInfo; +}; + } // namespace android diff --git a/include/ui/InputDevice.h b/include/ui/InputDevice.h deleted file mode 100644 index 3b9c70e2dcea..000000000000 --- a/include/ui/InputDevice.h +++ /dev/null @@ -1,353 +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. - */ - -#ifndef _UI_INPUT_DEVICE_H -#define _UI_INPUT_DEVICE_H - -#include <ui/EventHub.h> -#include <ui/Input.h> -#include <utils/KeyedVector.h> -#include <utils/threads.h> -#include <utils/Timers.h> -#include <utils/RefBase.h> -#include <utils/String8.h> -#include <utils/BitSet.h> - -#include <stddef.h> -#include <unistd.h> - -/* Maximum pointer id value supported. - * (This is limited by our use of BitSet32 to track pointer assignments.) */ -#define MAX_POINTER_ID 31 - -/* Maximum number of historical samples to average. */ -#define AVERAGING_HISTORY_SIZE 5 - - -namespace android { - -extern int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState); -extern int32_t rotateKeyCode(int32_t keyCode, int32_t orientation); - - -/* - * An input device structure tracks the state of a single input device. - * - * This structure is only used by ReaderThread and is not intended to be shared with - * DispatcherThread (because that would require locking). This works out fine because - * DispatcherThread is only interested in cooked event data anyways and does not need - * any of the low-level data from InputDevice. - */ -struct InputDevice { - struct AbsoluteAxisInfo { - bool valid; // set to true if axis parameters are known, false otherwise - - int32_t minValue; // minimum value - int32_t maxValue; // maximum value - int32_t range; // range of values, equal to maxValue - minValue - int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8 - int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise - }; - - struct VirtualKey { - int32_t keyCode; - int32_t scanCode; - uint32_t flags; - - // computed hit box, specified in touch screen coords based on known display size - int32_t hitLeft; - int32_t hitTop; - int32_t hitRight; - int32_t hitBottom; - - inline bool isHit(int32_t x, int32_t y) const { - return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom; - } - }; - - struct KeyboardState { - struct Current { - int32_t metaState; - nsecs_t downTime; // time of most recent key down - } current; - - void reset(); - }; - - struct TrackballState { - struct Accumulator { - enum { - FIELD_BTN_MOUSE = 1, - FIELD_REL_X = 2, - FIELD_REL_Y = 4 - }; - - uint32_t fields; - - bool btnMouse; - int32_t relX; - int32_t relY; - - inline void clear() { - fields = 0; - } - - inline bool isDirty() { - return fields != 0; - } - } accumulator; - - struct Current { - bool down; - nsecs_t downTime; - } current; - - struct Precalculated { - float xScale; - float yScale; - float xPrecision; - float yPrecision; - } precalculated; - - void reset(); - }; - - struct SingleTouchScreenState { - struct Accumulator { - enum { - FIELD_BTN_TOUCH = 1, - FIELD_ABS_X = 2, - FIELD_ABS_Y = 4, - FIELD_ABS_PRESSURE = 8, - FIELD_ABS_TOOL_WIDTH = 16 - }; - - uint32_t fields; - - bool btnTouch; - int32_t absX; - int32_t absY; - int32_t absPressure; - int32_t absToolWidth; - - inline void clear() { - fields = 0; - } - - inline bool isDirty() { - return fields != 0; - } - } accumulator; - - struct Current { - bool down; - int32_t x; - int32_t y; - int32_t pressure; - int32_t size; - } current; - - void reset(); - }; - - struct MultiTouchScreenState { - struct Accumulator { - enum { - FIELD_ABS_MT_POSITION_X = 1, - FIELD_ABS_MT_POSITION_Y = 2, - FIELD_ABS_MT_TOUCH_MAJOR = 4, - FIELD_ABS_MT_TOUCH_MINOR = 8, - FIELD_ABS_MT_WIDTH_MAJOR = 16, - FIELD_ABS_MT_WIDTH_MINOR = 32, - FIELD_ABS_MT_ORIENTATION = 64, - FIELD_ABS_MT_TRACKING_ID = 128 - }; - - uint32_t pointerCount; - struct Pointer { - uint32_t fields; - - int32_t absMTPositionX; - int32_t absMTPositionY; - int32_t absMTTouchMajor; - int32_t absMTTouchMinor; - int32_t absMTWidthMajor; - int32_t absMTWidthMinor; - int32_t absMTOrientation; - int32_t absMTTrackingId; - - inline void clear() { - fields = 0; - } - } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks - - inline void clear() { - pointerCount = 0; - pointers[0].clear(); - } - - inline bool isDirty() { - return pointerCount != 0; - } - } accumulator; - - void reset(); - }; - - struct PointerData { - uint32_t id; - int32_t x; - int32_t y; - int32_t pressure; - int32_t size; - int32_t touchMajor; - int32_t touchMinor; - int32_t toolMajor; - int32_t toolMinor; - int32_t orientation; - }; - - struct TouchData { - uint32_t pointerCount; - PointerData pointers[MAX_POINTERS]; - BitSet32 idBits; - uint32_t idToIndex[MAX_POINTER_ID + 1]; - - void copyFrom(const TouchData& other); - - inline void clear() { - pointerCount = 0; - idBits.clear(); - } - }; - - // common state used for both single-touch and multi-touch screens after the initial - // touch decoding has been performed - struct TouchScreenState { - Vector<VirtualKey> virtualKeys; - - struct Parameters { - bool useBadTouchFilter; - bool useJumpyTouchFilter; - bool useAveragingTouchFilter; - - AbsoluteAxisInfo xAxis; - AbsoluteAxisInfo yAxis; - AbsoluteAxisInfo pressureAxis; - AbsoluteAxisInfo sizeAxis; - AbsoluteAxisInfo orientationAxis; - } parameters; - - // The touch data of the current sample being processed. - TouchData currentTouch; - - // The touch data of the previous sample that was processed. This is updated - // incrementally while the current sample is being processed. - TouchData lastTouch; - - // The time the primary pointer last went down. - nsecs_t downTime; - - struct CurrentVirtualKeyState { - enum Status { - STATUS_UP, - STATUS_DOWN, - STATUS_CANCELED - }; - - Status status; - nsecs_t downTime; - int32_t keyCode; - int32_t scanCode; - } currentVirtualKey; - - struct AveragingTouchFilterState { - // Individual history tracks are stored by pointer id - uint32_t historyStart[MAX_POINTERS]; - uint32_t historyEnd[MAX_POINTERS]; - struct { - struct { - int32_t x; - int32_t y; - int32_t pressure; - } pointers[MAX_POINTERS]; - } historyData[AVERAGING_HISTORY_SIZE]; - } averagingTouchFilter; - - struct JumpTouchFilterState { - int32_t jumpyPointsDropped; - } jumpyTouchFilter; - - struct Precalculated { - int32_t xOrigin; - float xScale; - - int32_t yOrigin; - float yScale; - - int32_t pressureOrigin; - float pressureScale; - - int32_t sizeOrigin; - float sizeScale; - - float orientationScale; - } precalculated; - - void reset(); - - bool applyBadTouchFilter(); - bool applyJumpyTouchFilter(); - void applyAveragingTouchFilter(); - void calculatePointerIds(); - - bool isPointInsideDisplay(int32_t x, int32_t y) const; - const InputDevice::VirtualKey* findVirtualKeyHit() const; - }; - - InputDevice(int32_t id, uint32_t classes, String8 name); - - int32_t id; - uint32_t classes; - String8 name; - bool ignored; - - KeyboardState keyboard; - TrackballState trackball; - TouchScreenState touchScreen; - union { - SingleTouchScreenState singleTouchScreen; - MultiTouchScreenState multiTouchScreen; - }; - - void reset(); - - inline bool isKeyboard() const { return classes & INPUT_DEVICE_CLASS_KEYBOARD; } - inline bool isAlphaKey() const { return classes & INPUT_DEVICE_CLASS_ALPHAKEY; } - inline bool isTrackball() const { return classes & INPUT_DEVICE_CLASS_TRACKBALL; } - inline bool isDPad() const { return classes & INPUT_DEVICE_CLASS_DPAD; } - inline bool isSingleTouchScreen() const { return (classes - & (INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT)) - == INPUT_DEVICE_CLASS_TOUCHSCREEN; } - inline bool isMultiTouchScreen() const { return classes - & INPUT_DEVICE_CLASS_TOUCHSCREEN_MT; } - inline bool isTouchScreen() const { return classes - & (INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT); } -}; - -} // namespace android - -#endif // _UI_INPUT_DEVICE_H diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h index 674852aeb64d..d3495fee5dcb 100644 --- a/include/ui/InputDispatcher.h +++ b/include/ui/InputDispatcher.h @@ -55,6 +55,22 @@ enum { INPUT_EVENT_INJECTION_TIMED_OUT = 3 }; +/* + * Constants used to determine the input event injection synchronization mode. + */ +enum { + /* Injection is asynchronous and is assumed always to be successful. */ + INPUT_EVENT_INJECTION_SYNC_NONE = 0, + + /* Waits for previous events to be dispatched so that the input dispatcher can determine + * whether input event injection willbe permitted based on the current input focus. + * Does not wait for the input event to finish processing. */ + INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1, + + /* Waits for the input event to be completely processed. */ + INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED = 2, +}; + /* * An input target specifies how an input event is to be dispatched to a particular window @@ -176,15 +192,14 @@ public: float xPrecision, float yPrecision, nsecs_t downTime) = 0; /* Injects an input event and optionally waits for sync. - * This method may block even if sync is false because it must wait for previous events - * to be dispatched before it can determine whether input event injection will be - * permitted based on the current input focus. + * The synchronization mode determines whether the method blocks while waiting for + * input injection to proceed. * Returns one of the INPUT_EVENT_INJECTION_XXX constants. * * This method may be called on any thread (usually by the input manager). */ virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) = 0; + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0; /* Preempts input dispatch in progress by making pending synchronous * dispatches asynchronous instead. This method is generally called during a focus @@ -241,7 +256,7 @@ public: float xPrecision, float yPrecision, nsecs_t downTime); virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis); + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis); virtual void preemptInputDispatch(); @@ -267,11 +282,13 @@ private: int32_t type; nsecs_t eventTime; - int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING - int32_t injectorPid; // -1 if not injected - int32_t injectorUid; // -1 if not injected + int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING + bool injectionIsAsync; // set to true if injection is not waiting for the result + int32_t injectorPid; // -1 if not injected + int32_t injectorUid; // -1 if not injected bool dispatchInProgress; // initially false, set to true while dispatching + int32_t pendingSyncDispatches; // the number of synchronous dispatches in progress inline bool isInjected() { return injectorPid >= 0; } }; @@ -340,6 +357,10 @@ private: // headMotionSample will be initialized to tailMotionSample and tailMotionSample // will be set to NULL. MotionSample* tailMotionSample; + + inline bool isSyncTarget() { + return targetFlags & InputTarget::FLAG_SYNC; + } }; // A command entry captures state and behavior for an action to be performed in the @@ -497,8 +518,7 @@ private: // Since there can only ever be at most one such target at a time, if there is one, // it must be at the tail because nothing else can be enqueued after it. inline bool hasPendingSyncTarget() { - return ! outboundQueue.isEmpty() - && (outboundQueue.tail.prev->targetFlags & InputTarget::FLAG_SYNC); + return ! outboundQueue.isEmpty() && outboundQueue.tail.prev->isSyncTarget(); } // Gets the time since the current event was originally obtained from the input driver. @@ -559,11 +579,12 @@ private: // Event injection and synchronization. Condition mInjectionResultAvailableCondition; - Condition mFullySynchronizedCondition; - bool isFullySynchronizedLocked(); EventEntry* createEntryFromInputEventLocked(const InputEvent* event); void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult); + Condition mInjectionSyncFinishedCondition; + void decrementPendingSyncDispatchesLocked(EventEntry* entry); + // Key repeat tracking. // XXX Move this up to the input reader instead. struct KeyRepeatState { diff --git a/include/ui/InputManager.h b/include/ui/InputManager.h index e7552381162c..4012c69135f1 100644 --- a/include/ui/InputManager.h +++ b/include/ui/InputManager.h @@ -79,13 +79,12 @@ public: virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0; /* Injects an input event and optionally waits for sync. - * This method may block even if sync is false because it must wait for previous events - * to be dispatched before it can determine whether input event injection will be - * permitted based on the current input focus. + * The synchronization mode determines whether the method blocks while waiting for + * input injection to proceed. * Returns one of the INPUT_EVENT_INJECTION_XXX constants. */ virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) = 0; + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0; /* Preempts input dispatch in progress by making pending synchronous * dispatches asynchronous instead. This method is generally called during a focus @@ -96,22 +95,28 @@ public: virtual void preemptInputDispatch() = 0; /* Gets input device configuration. */ - virtual void getInputConfiguration(InputConfiguration* outConfiguration) const = 0; + virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0; - /* - * Queries current input state. - * deviceId may be -1 to search for the device automatically, filtered by class. - * deviceClasses may be -1 to ignore device class while searching. + /* Gets information about the specified input device. + * Returns OK if the device information was obtained or NAME_NOT_FOUND if there + * was no such device. */ - virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t scanCode) const = 0; - virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t keyCode) const = 0; - virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses, - int32_t sw) const = 0; + virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0; + + /* Gets the list of all registered device ids. */ + virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0; + + /* Queries current input state. */ + virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t scanCode) = 0; + virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t keyCode) = 0; + virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, + int32_t sw) = 0; /* Determines whether physical keys exist for the given framework-domain key codes. */ - virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const = 0; + virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, + size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0; }; class InputManager : public InputManagerInterface { @@ -136,18 +141,21 @@ public: virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel); virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis); + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis); virtual void preemptInputDispatch(); - virtual void getInputConfiguration(InputConfiguration* outConfiguration) const; - virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t scanCode) const; - virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t keyCode) const; - virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses, - int32_t sw) const; - virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const; + virtual void getInputConfiguration(InputConfiguration* outConfiguration); + virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo); + virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds); + virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t scanCode); + virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t keyCode); + virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, + int32_t sw); + virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, + size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags); private: sp<InputReaderInterface> mReader; diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h index 14bea6504c9e..d7ec8ea64dfb 100644 --- a/include/ui/InputReader.h +++ b/include/ui/InputReader.h @@ -19,7 +19,6 @@ #include <ui/EventHub.h> #include <ui/Input.h> -#include <ui/InputDevice.h> #include <ui/InputDispatcher.h> #include <utils/KeyedVector.h> #include <utils/threads.h> @@ -33,6 +32,10 @@ namespace android { +class InputDevice; +class InputMapper; + + /* * Input reader policy interface. * @@ -68,14 +71,6 @@ public: // The input dispatcher should perform special filtering in preparation for // a pending app switch. ACTION_APP_SWITCH_COMING = 0x00000002, - - // The input dispatcher should add POLICY_FLAG_WOKE_HERE to the policy flags it - // passes through the dispatch pipeline. - ACTION_WOKE_HERE = 0x00000004, - - // The input dispatcher should add POLICY_FLAG_BRIGHT_HERE to the policy flags it - // passes through the dispatch pipeline. - ACTION_BRIGHT_HERE = 0x00000008, }; /* Describes a virtual key. */ @@ -101,38 +96,30 @@ public: /* Intercepts a key event. * The policy can use this method as an opportunity to perform power management functions - * and early event preprocessing. + * and early event preprocessing such as updating policy flags. * * Returns a policy action constant such as ACTION_DISPATCH. */ virtual int32_t interceptKey(nsecs_t when, int32_t deviceId, - bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) = 0; + bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags) = 0; - /* Intercepts a trackball event. + /* Intercepts a switch event. * The policy can use this method as an opportunity to perform power management functions - * and early event preprocessing. + * and early event preprocessing such as updating policy flags. * - * Returns a policy action constant such as ACTION_DISPATCH. + * Switches are not dispatched to applications so this method should + * usually return ACTION_NONE. */ - virtual int32_t interceptTrackball(nsecs_t when, bool buttonChanged, bool buttonDown, - bool rolled) = 0; + virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue, + uint32_t& policyFlags) = 0; - /* Intercepts a touch event. + /* Intercepts a generic touch, trackball or other event. * The policy can use this method as an opportunity to perform power management functions - * and early event preprocessing. + * and early event preprocessing such as updating policy flags. * * Returns a policy action constant such as ACTION_DISPATCH. */ - virtual int32_t interceptTouch(nsecs_t when) = 0; - - /* Intercepts a switch event. - * The policy can use this method as an opportunity to perform power management functions - * and early event preprocessing. - * - * Switches are not dispatched to applications so this method should - * usually return ACTION_NONE. - */ - virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue) = 0; + virtual int32_t interceptGeneric(nsecs_t when, uint32_t& policyFlags) = 0; /* Determines whether to turn on some hacks we have to improve the touch interaction with a * certain device whose screen currently is not all that good. @@ -167,32 +154,52 @@ public: */ virtual void loopOnce() = 0; - /* Gets the current virtual key. Returns false if not down. + /* Gets the current input device configuration. * * This method may be called on any thread (usually by the input manager). */ - virtual bool getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const = 0; + virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0; - /* Gets the current input device configuration. + /* Gets information about the specified input device. + * Returns OK if the device information was obtained or NAME_NOT_FOUND if there + * was no such device. * * This method may be called on any thread (usually by the input manager). */ - virtual void getCurrentInputConfiguration(InputConfiguration* outConfiguration) const = 0; + virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0; - /* - * Query current input state. - * deviceId may be -1 to search for the device automatically, filtered by class. - * deviceClasses may be -1 to ignore device class while searching. - */ - virtual int32_t getCurrentScanCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t scanCode) const = 0; - virtual int32_t getCurrentKeyCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t keyCode) const = 0; - virtual int32_t getCurrentSwitchState(int32_t deviceId, int32_t deviceClasses, - int32_t sw) const = 0; + /* Gets the list of all registered device ids. */ + virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0; + + /* Query current input state. */ + virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t scanCode) = 0; + virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t keyCode) = 0; + virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, + int32_t sw) = 0; /* Determine whether physical keys exist for the given framework-domain key codes. */ - virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const = 0; + virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, + size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0; +}; + + +/* Internal interface used by individual input devices to access global input device state + * and parameters maintained by the input reader. + */ +class InputReaderContext { +protected: + InputReaderContext() { } + virtual ~InputReaderContext() { } + +public: + virtual void updateGlobalMetaState() = 0; + virtual int32_t getGlobalMetaState() = 0; + + virtual InputReaderPolicyInterface* getPolicy() = 0; + virtual InputDispatcherInterface* getDispatcher() = 0; + virtual EventHubInterface* getEventHub() = 0; }; @@ -201,10 +208,11 @@ public: * event filtering in low power states, are controlled by a separate policy object. * * IMPORTANT INVARIANT: - * Because the policy can potentially block or cause re-entrance into the input reader, - * the input reader never calls into the policy while holding its internal locks. + * Because the policy and dispatcher can potentially block or cause re-entrance into + * the input reader, the input reader never calls into other components while holding + * an exclusive internal lock. */ -class InputReader : public InputReaderInterface { +class InputReader : public InputReaderInterface, private InputReaderContext { public: InputReader(const sp<EventHubInterface>& eventHub, const sp<InputReaderPolicyInterface>& policy, @@ -213,107 +221,69 @@ public: virtual void loopOnce(); - virtual bool getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const; + virtual void getInputConfiguration(InputConfiguration* outConfiguration); - virtual void getCurrentInputConfiguration(InputConfiguration* outConfiguration) const; + virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo); + virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds); - virtual int32_t getCurrentScanCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t scanCode) const; - virtual int32_t getCurrentKeyCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t keyCode) const; - virtual int32_t getCurrentSwitchState(int32_t deviceId, int32_t deviceClasses, - int32_t sw) const; + virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t scanCode); + virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t keyCode); + virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, + int32_t sw); - virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const; + virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask, + size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags); private: - // Lock that must be acquired while manipulating state that may be concurrently accessed - // from other threads by input state query methods. It should be held for as short a - // time as possible. - // - // Exported state: - // - global virtual key code and scan code - // - device list and immutable properties of devices such as id, name, and class - // (but not other internal device state) - mutable Mutex mExportedStateLock; - - // current virtual key information (lock mExportedStateLock) - int32_t mExportedVirtualKeyCode; - int32_t mExportedVirtualScanCode; - - // current input configuration (lock mExportedStateLock) - InputConfiguration mExportedInputConfiguration; - - // combined key meta state - int32_t mGlobalMetaState; - sp<EventHubInterface> mEventHub; sp<InputReaderPolicyInterface> mPolicy; sp<InputDispatcherInterface> mDispatcher; + virtual InputReaderPolicyInterface* getPolicy() { return mPolicy.get(); } + virtual InputDispatcherInterface* getDispatcher() { return mDispatcher.get(); } + virtual EventHubInterface* getEventHub() { return mEventHub.get(); } + + // This reader/writer lock guards the list of input devices. + // The writer lock must be held whenever the list of input devices is modified + // and then promptly released. + // The reader lock must be held whenever the list of input devices is traversed or an + // input device in the list is accessed. + // This lock only protects the registry and prevents inadvertent deletion of device objects + // that are in use. Individual devices are responsible for guarding their own internal state + // as needed for concurrent operation. + RWLock mDeviceRegistryLock; KeyedVector<int32_t, InputDevice*> mDevices; - // display properties needed to translate touch screen coordinates into display coordinates - int32_t mDisplayOrientation; - int32_t mDisplayWidth; - int32_t mDisplayHeight; - - // low-level input event decoding + // low-level input event decoding and device management void process(const RawEvent* rawEvent); - void handleDeviceAdded(const RawEvent* rawEvent); - void handleDeviceRemoved(const RawEvent* rawEvent); - void handleSync(const RawEvent* rawEvent); - void handleKey(const RawEvent* rawEvent); - void handleRelativeMotion(const RawEvent* rawEvent); - void handleAbsoluteMotion(const RawEvent* rawEvent); - void handleSwitch(const RawEvent* rawEvent); - - // input policy processing and dispatch - void onKey(nsecs_t when, InputDevice* device, bool down, - int32_t keyCode, int32_t scanCode, uint32_t policyFlags); - void onSwitch(nsecs_t when, InputDevice* device, int32_t switchCode, int32_t switchValue); - void onSingleTouchScreenStateChanged(nsecs_t when, InputDevice* device); - void onMultiTouchScreenStateChanged(nsecs_t when, InputDevice* device); - void onTouchScreenChanged(nsecs_t when, InputDevice* device, bool havePointerIds); - void onTrackballStateChanged(nsecs_t when, InputDevice* device); - void onConfigurationChanged(nsecs_t when); - - bool applyStandardInputDispatchPolicyActions(nsecs_t when, - int32_t policyActions, uint32_t* policyFlags); - - bool consumeVirtualKeyTouches(nsecs_t when, InputDevice* device, uint32_t policyFlags); - void dispatchVirtualKey(nsecs_t when, InputDevice* device, uint32_t policyFlags, - int32_t keyEventAction, int32_t keyEventFlags); - void dispatchTouches(nsecs_t when, InputDevice* device, uint32_t policyFlags); - void dispatchTouch(nsecs_t when, InputDevice* device, uint32_t policyFlags, - InputDevice::TouchData* touch, BitSet32 idBits, uint32_t changedId, - int32_t motionEventAction); - - // display - void resetDisplayProperties(); - bool refreshDisplayProperties(); - - // device management - InputDevice* getDevice(int32_t deviceId); - InputDevice* getNonIgnoredDevice(int32_t deviceId); + void addDevice(nsecs_t when, int32_t deviceId); - void removeDevice(nsecs_t when, InputDevice* device); - void configureDevice(InputDevice* device); - void configureDeviceForCurrentDisplaySize(InputDevice* device); - void configureVirtualKeys(InputDevice* device); - void configureAbsoluteAxisInfo(InputDevice* device, int axis, const char* name, - InputDevice::AbsoluteAxisInfo* out); + void removeDevice(nsecs_t when, int32_t deviceId); + InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes); void configureExcludedDevices(); - // global meta state management for all devices - void resetGlobalMetaState(); - int32_t globalMetaState(); + void consumeEvent(const RawEvent* rawEvent); + + void handleConfigurationChanged(nsecs_t when); - // virtual key management - void updateExportedVirtualKeyState(); + // state management for all devices + Mutex mStateLock; - // input configuration management - void updateExportedInputConfiguration(); + int32_t mGlobalMetaState; + virtual void updateGlobalMetaState(); + virtual int32_t getGlobalMetaState(); + + InputConfiguration mInputConfiguration; + void updateInputConfiguration(); + + // state queries + typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code); + int32_t getState(int32_t deviceId, uint32_t sourceMask, int32_t code, + GetStateFunc getStateFunc); + bool markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); }; @@ -329,6 +299,527 @@ private: virtual bool threadLoop(); }; + +/* Represents the state of a single input device. */ +class InputDevice { +public: + InputDevice(InputReaderContext* context, int32_t id, const String8& name); + ~InputDevice(); + + inline InputReaderContext* getContext() { return mContext; } + inline int32_t getId() { return mId; } + inline const String8& getName() { return mName; } + inline uint32_t getSources() { return mSources; } + + inline bool isIgnored() { return mMappers.isEmpty(); } + + void addMapper(InputMapper* mapper); + void configure(); + void reset(); + void process(const RawEvent* rawEvent); + + void getDeviceInfo(InputDeviceInfo* outDeviceInfo); + int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); + bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + + int32_t getMetaState(); + +private: + InputReaderContext* mContext; + int32_t mId; + + Vector<InputMapper*> mMappers; + + String8 mName; + uint32_t mSources; + + typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code); + int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc); +}; + + +/* An input mapper transforms raw input events into cooked event data. + * A single input device can have multiple associated input mappers in order to interpret + * different classes of events. + */ +class InputMapper { +public: + InputMapper(InputDevice* device); + virtual ~InputMapper(); + + inline InputDevice* getDevice() { return mDevice; } + inline int32_t getDeviceId() { return mDevice->getId(); } + inline const String8 getDeviceName() { return mDevice->getName(); } + inline InputReaderContext* getContext() { return mContext; } + inline InputReaderPolicyInterface* getPolicy() { return mContext->getPolicy(); } + inline InputDispatcherInterface* getDispatcher() { return mContext->getDispatcher(); } + inline EventHubInterface* getEventHub() { return mContext->getEventHub(); } + + virtual uint32_t getSources() = 0; + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void configure(); + virtual void reset(); + virtual void process(const RawEvent* rawEvent) = 0; + + virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); + virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + + virtual int32_t getMetaState(); + +protected: + InputDevice* mDevice; + InputReaderContext* mContext; + + bool applyStandardPolicyActions(nsecs_t when, int32_t policyActions); +}; + + +class SwitchInputMapper : public InputMapper { +public: + SwitchInputMapper(InputDevice* device); + virtual ~SwitchInputMapper(); + + virtual uint32_t getSources(); + virtual void process(const RawEvent* rawEvent); + + virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode); + +private: + void processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue); +}; + + +class KeyboardInputMapper : public InputMapper { +public: + KeyboardInputMapper(InputDevice* device, int32_t associatedDisplayId, uint32_t sources, + int32_t keyboardType); + virtual ~KeyboardInputMapper(); + + virtual uint32_t getSources(); + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void reset(); + virtual void process(const RawEvent* rawEvent); + + virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + + virtual int32_t getMetaState(); + +private: + struct KeyDown { + int32_t keyCode; + int32_t scanCode; + }; + + int32_t mAssociatedDisplayId; + uint32_t mSources; + int32_t mKeyboardType; + + Vector<KeyDown> mKeyDowns; // keys that are down + int32_t mMetaState; + nsecs_t mDownTime; // time of most recent key down + + void initialize(); + + bool isKeyboardOrGamepadKey(int32_t scanCode); + void processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode, + uint32_t policyFlags); + + ssize_t findKeyDown(int32_t scanCode); +}; + + +class TrackballInputMapper : public InputMapper { +public: + TrackballInputMapper(InputDevice* device, int32_t associatedDisplayId); + virtual ~TrackballInputMapper(); + + virtual uint32_t getSources(); + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void reset(); + virtual void process(const RawEvent* rawEvent); + +private: + // Amount that trackball needs to move in order to generate a key event. + static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6; + + int32_t mAssociatedDisplayId; + + struct Accumulator { + enum { + FIELD_BTN_MOUSE = 1, + FIELD_REL_X = 2, + FIELD_REL_Y = 4 + }; + + uint32_t fields; + + bool btnMouse; + int32_t relX; + int32_t relY; + + inline void clear() { + fields = 0; + } + + inline bool isDirty() { + return fields != 0; + } + } mAccumulator; + + bool mDown; + nsecs_t mDownTime; + + float mXScale; + float mYScale; + float mXPrecision; + float mYPrecision; + + void initialize(); + + void sync(nsecs_t when); +}; + + +class TouchInputMapper : public InputMapper { +public: + TouchInputMapper(InputDevice* device, int32_t associatedDisplayId); + virtual ~TouchInputMapper(); + + virtual uint32_t getSources(); + virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo); + virtual void configure(); + virtual void reset(); + + virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); + virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); + virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags); + +protected: + /* Maximum pointer id value supported. + * (This is limited by our use of BitSet32 to track pointer assignments.) */ + static const uint32_t MAX_POINTER_ID = 31; + + struct VirtualKey { + int32_t keyCode; + int32_t scanCode; + uint32_t flags; + + // computed hit box, specified in touch screen coords based on known display size + int32_t hitLeft; + int32_t hitTop; + int32_t hitRight; + int32_t hitBottom; + + inline bool isHit(int32_t x, int32_t y) const { + return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom; + } + }; + + struct PointerData { + uint32_t id; + int32_t x; + int32_t y; + int32_t pressure; + int32_t size; + int32_t touchMajor; + int32_t touchMinor; + int32_t toolMajor; + int32_t toolMinor; + int32_t orientation; + }; + + struct TouchData { + uint32_t pointerCount; + PointerData pointers[MAX_POINTERS]; + BitSet32 idBits; + uint32_t idToIndex[MAX_POINTER_ID + 1]; + + void copyFrom(const TouchData& other) { + pointerCount = other.pointerCount; + idBits = other.idBits; + + for (uint32_t i = 0; i < pointerCount; i++) { + pointers[i] = other.pointers[i]; + idToIndex[i] = other.idToIndex[i]; + } + } + + inline void clear() { + pointerCount = 0; + idBits.clear(); + } + }; + + int32_t mAssociatedDisplayId; + Vector<VirtualKey> mVirtualKeys; + + // Immutable configuration parameters. + struct Parameters { + bool useBadTouchFilter; + bool useJumpyTouchFilter; + bool useAveragingTouchFilter; + } mParameters; + + // Raw axis information. + struct Axes { + RawAbsoluteAxisInfo x; + RawAbsoluteAxisInfo y; + RawAbsoluteAxisInfo pressure; + RawAbsoluteAxisInfo size; + RawAbsoluteAxisInfo touchMajor; + RawAbsoluteAxisInfo touchMinor; + RawAbsoluteAxisInfo toolMajor; + RawAbsoluteAxisInfo toolMinor; + RawAbsoluteAxisInfo orientation; + } mAxes; + + // The surface orientation and width and height set by configureSurface(). + int32_t mSurfaceOrientation; + int32_t mSurfaceWidth, mSurfaceHeight; + + // Translation and scaling factors, orientation-independent. + int32_t mXOrigin; + float mXScale; + float mXPrecision; + + int32_t mYOrigin; + float mYScale; + float mYPrecision; + + int32_t mPressureOrigin; + float mPressureScale; + + int32_t mSizeOrigin; + float mSizeScale; + + float mOrientationScale; + + // Oriented motion ranges for input device info. + struct OrientedRanges { + InputDeviceInfo::MotionRange x; + InputDeviceInfo::MotionRange y; + InputDeviceInfo::MotionRange pressure; + InputDeviceInfo::MotionRange size; + InputDeviceInfo::MotionRange touchMajor; + InputDeviceInfo::MotionRange touchMinor; + InputDeviceInfo::MotionRange toolMajor; + InputDeviceInfo::MotionRange toolMinor; + InputDeviceInfo::MotionRange orientation; + } mOrientedRanges; + + // Oriented dimensions and precision. + float mOrientedSurfaceWidth, mOrientedSurfaceHeight; + float mOrientedXPrecision, mOrientedYPrecision; + + // The touch data of the current sample being processed. + TouchData mCurrentTouch; + + // The touch data of the previous sample that was processed. This is updated + // incrementally while the current sample is being processed. + TouchData mLastTouch; + + // The time the primary pointer last went down. + nsecs_t mDownTime; + + struct CurrentVirtualKeyState { + bool down; + nsecs_t downTime; + int32_t keyCode; + int32_t scanCode; + } mCurrentVirtualKey; + + // Lock for virtual key state. + Mutex mVirtualKeyLock; // methods use "Lvk" suffix + + virtual void configureAxes(); + virtual bool configureSurface(); + virtual void configureVirtualKeys(); + + enum TouchResult { + // Dispatch the touch normally. + DISPATCH_TOUCH, + // Do not dispatch the touch, but keep tracking the current stroke. + SKIP_TOUCH, + // Do not dispatch the touch, and drop all information associated with the current stoke + // so the next movement will appear as a new down. + DROP_STROKE + }; + + void syncTouch(nsecs_t when, bool havePointerIds); + +private: + /* Maximum number of historical samples to average. */ + static const uint32_t AVERAGING_HISTORY_SIZE = 5; + + /* Slop distance for jumpy pointer detection. + * The vertical range of the screen divided by this is our epsilon value. */ + static const uint32_t JUMPY_EPSILON_DIVISOR = 212; + + /* Number of jumpy points to drop for touchscreens that need it. */ + static const uint32_t JUMPY_TRANSITION_DROPS = 3; + static const uint32_t JUMPY_DROP_LIMIT = 3; + + /* Maximum squared distance for averaging. + * If moving farther than this, turn of averaging to avoid lag in response. */ + static const uint64_t AVERAGING_DISTANCE_LIMIT = 75 * 75; + + struct AveragingTouchFilterState { + // Individual history tracks are stored by pointer id + uint32_t historyStart[MAX_POINTERS]; + uint32_t historyEnd[MAX_POINTERS]; + struct { + struct { + int32_t x; + int32_t y; + int32_t pressure; + } pointers[MAX_POINTERS]; + } historyData[AVERAGING_HISTORY_SIZE]; + } mAveragingTouchFilter; + + struct JumpTouchFilterState { + uint32_t jumpyPointsDropped; + } mJumpyTouchFilter; + + struct PointerDistanceHeapElement { + uint32_t currentPointerIndex : 8; + uint32_t lastPointerIndex : 8; + uint64_t distance : 48; // squared distance + }; + + void initialize(); + + TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags); + void dispatchTouches(nsecs_t when, uint32_t policyFlags); + void dispatchTouch(nsecs_t when, uint32_t policyFlags, TouchData* touch, + BitSet32 idBits, uint32_t changedId, int32_t motionEventAction); + + bool isPointInsideSurface(int32_t x, int32_t y); + const VirtualKey* findVirtualKeyHitLvk(int32_t x, int32_t y); + + bool applyBadTouchFilter(); + bool applyJumpyTouchFilter(); + void applyAveragingTouchFilter(); + void calculatePointerIds(); +}; + + +class SingleTouchInputMapper : public TouchInputMapper { +public: + SingleTouchInputMapper(InputDevice* device, int32_t associatedDisplayId); + virtual ~SingleTouchInputMapper(); + + virtual void reset(); + virtual void process(const RawEvent* rawEvent); + +protected: + virtual void configureAxes(); + +private: + struct Accumulator { + enum { + FIELD_BTN_TOUCH = 1, + FIELD_ABS_X = 2, + FIELD_ABS_Y = 4, + FIELD_ABS_PRESSURE = 8, + FIELD_ABS_TOOL_WIDTH = 16 + }; + + uint32_t fields; + + bool btnTouch; + int32_t absX; + int32_t absY; + int32_t absPressure; + int32_t absToolWidth; + + inline void clear() { + fields = 0; + } + + inline bool isDirty() { + return fields != 0; + } + } mAccumulator; + + bool mDown; + int32_t mX; + int32_t mY; + int32_t mPressure; + int32_t mSize; + + void initialize(); + + void sync(nsecs_t when); +}; + + +class MultiTouchInputMapper : public TouchInputMapper { +public: + MultiTouchInputMapper(InputDevice* device, int32_t associatedDisplayId); + virtual ~MultiTouchInputMapper(); + + virtual void reset(); + virtual void process(const RawEvent* rawEvent); + +protected: + virtual void configureAxes(); + +private: + struct Accumulator { + enum { + FIELD_ABS_MT_POSITION_X = 1, + FIELD_ABS_MT_POSITION_Y = 2, + FIELD_ABS_MT_TOUCH_MAJOR = 4, + FIELD_ABS_MT_TOUCH_MINOR = 8, + FIELD_ABS_MT_WIDTH_MAJOR = 16, + FIELD_ABS_MT_WIDTH_MINOR = 32, + FIELD_ABS_MT_ORIENTATION = 64, + FIELD_ABS_MT_TRACKING_ID = 128 + }; + + uint32_t pointerCount; + struct Pointer { + uint32_t fields; + + int32_t absMTPositionX; + int32_t absMTPositionY; + int32_t absMTTouchMajor; + int32_t absMTTouchMinor; + int32_t absMTWidthMajor; + int32_t absMTWidthMinor; + int32_t absMTOrientation; + int32_t absMTTrackingId; + + inline void clear() { + fields = 0; + } + } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks + + inline void clear() { + pointerCount = 0; + pointers[0].clear(); + } + + inline bool isDirty() { + return pointerCount != 0; + } + } mAccumulator; + + void initialize(); + + void sync(nsecs_t when); +}; + } // namespace android #endif // _UI_INPUT_READER_H diff --git a/include/utils/Asset.h b/include/utils/Asset.h index 5908bccedd64..2a09095bdefe 100644 --- a/include/utils/Asset.h +++ b/include/utils/Asset.h @@ -61,15 +61,6 @@ public: ACCESS_BUFFER, } AccessMode; - enum { - /* data larger than this does not get uncompressed into a buffer */ -#ifdef HAVE_ANDROID_OS - UNCOMPRESS_DATA_MAX = 1 * 1024 * 1024 -#else - UNCOMPRESS_DATA_MAX = 2 * 1024 * 1024 -#endif - }; - /* * Read data from the current offset. Returns the actual number of * bytes read, 0 on EOF, or -1 on error. @@ -317,6 +308,8 @@ private: FileMap* mMap; // for memory-mapped input int mFd; // for file input + class StreamingZipInflater* mZipInflater; // for streaming large compressed assets + unsigned char* mBuf; // for getBuffer() }; diff --git a/include/utils/StreamingZipInflater.h b/include/utils/StreamingZipInflater.h new file mode 100644 index 000000000000..16867d8db26d --- /dev/null +++ b/include/utils/StreamingZipInflater.h @@ -0,0 +1,82 @@ +/* + * 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 __LIBS_STREAMINGZIPINFLATER_H +#define __LIBS_STREAMINGZIPINFLATER_H + +#include <unistd.h> +#include <inttypes.h> +#include <zlib.h> + +namespace android { + +class StreamingZipInflater { +public: + static const size_t INPUT_CHUNK_SIZE = 64 * 1024; + static const size_t OUTPUT_CHUNK_SIZE = 64 * 1024; + + // Flavor that pages in the compressed data from a fd + StreamingZipInflater(int fd, off_t compDataStart, size_t uncompSize, size_t compSize); + + // Flavor that gets the compressed data from an in-memory buffer + StreamingZipInflater(class FileMap* dataMap, size_t uncompSize); + + ~StreamingZipInflater(); + + // read 'count' bytes of uncompressed data from the current position. outBuf may + // be NULL, in which case the data is consumed and discarded. + ssize_t read(void* outBuf, size_t count); + + // seeking backwards requires uncompressing fom the beginning, so is very + // expensive. seeking forwards only requires uncompressing from the current + // position to the destination. + off_t seekAbsolute(off_t absoluteInputPosition); + +private: + void initInflateState(); + int readNextChunk(); + + // where to find the uncompressed data + int mFd; + off_t mInFileStart; // where the compressed data lives in the file + class FileMap* mDataMap; + + z_stream mInflateState; + bool mStreamNeedsInit; + + // output invariants for this asset + uint8_t* mOutBuf; // output buf for decompressed bytes + size_t mOutBufSize; // allocated size of mOutBuf + size_t mOutTotalSize; // total uncompressed size of the blob + + // current output state bookkeeping + off_t mOutCurPosition; // current position in total offset + size_t mOutLastDecoded; // last decoded byte + 1 in mOutbuf + size_t mOutDeliverable; // next undelivered byte of decoded output in mOutBuf + + // input invariants + uint8_t* mInBuf; + size_t mInBufSize; // allocated size of mInBuf; + size_t mInTotalSize; // total size of compressed data for this blob + + // input state bookkeeping + size_t mInNextChunkOffset; // offset from start of blob at which the next input chunk lies + // the z_stream contains state about input block consumption +}; + +} + +#endif diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp index 60babad379fd..18f75df2ea2f 100644 --- a/libs/binder/Parcel.cpp +++ b/libs/binder/Parcel.cpp @@ -458,13 +458,13 @@ bool Parcel::checkInterface(IBinder* binder) const } bool Parcel::enforceInterface(const String16& interface, - int32_t* strict_policy_out) const + IPCThreadState* threadState) const { - int32_t strict_policy = readInt32(); - IPCThreadState::self()->setStrictModePolicy(strict_policy); - if (strict_policy_out != NULL) { - *strict_policy_out = strict_policy; + int32_t strictPolicy = readInt32(); + if (threadState == NULL) { + threadState = IPCThreadState::self(); } + threadState->setStrictModePolicy(strictPolicy); const String16 str(readString16()); if (str == interface) { return true; diff --git a/libs/gui/Sensor.cpp b/libs/gui/Sensor.cpp index cb85df9bb04d..b1f37ffed2a8 100644 --- a/libs/gui/Sensor.cpp +++ b/libs/gui/Sensor.cpp @@ -32,7 +32,7 @@ namespace android { Sensor::Sensor() : mHandle(0), mType(0), mMinValue(0), mMaxValue(0), mResolution(0), - mPower(0) + mPower(0), mMinDelay(0) { } @@ -46,6 +46,7 @@ Sensor::Sensor(struct sensor_t const* hwSensor) mMaxValue = hwSensor->maxRange; // FIXME: maxValue mResolution = hwSensor->resolution; mPower = hwSensor->power; + mMinDelay = hwSensor->minDelay; } Sensor::~Sensor() @@ -84,12 +85,17 @@ float Sensor::getPowerUsage() const { return mPower; } +int32_t Sensor::getMinDelay() const { + return mMinDelay; +} + size_t Sensor::getFlattenedSize() const { return sizeof(int32_t) + ((mName.length() + 3) & ~3) + sizeof(int32_t) + ((mVendor.length() + 3) & ~3) + sizeof(int32_t) * 2 + - sizeof(float) * 4; + sizeof(float) * 4 + + sizeof(int32_t); } size_t Sensor::getFdCount() const @@ -132,6 +138,7 @@ status_t Sensor::flatten(void* buffer, size_t size, offset += write(buffer, offset, mMaxValue); offset += write(buffer, offset, mResolution); offset += write(buffer, offset, mPower); + offset += write(buffer, offset, mMinDelay); return NO_ERROR; } @@ -169,6 +176,7 @@ status_t Sensor::unflatten(void const* buffer, size_t size, offset += read(buffer, offset, &mMaxValue); offset += read(buffer, offset, &mResolution); offset += read(buffer, offset, &mPower); + offset += read(buffer, offset, &mMinDelay); return NO_ERROR; } diff --git a/libs/gui/SensorEventQueue.cpp b/libs/gui/SensorEventQueue.cpp index 4b468424912b..3396f25e714a 100644 --- a/libs/gui/SensorEventQueue.cpp +++ b/libs/gui/SensorEventQueue.cpp @@ -114,10 +114,10 @@ status_t SensorEventQueue::disableSensor(Sensor const* sensor) const { return mSensorEventConnection->enableDisable(sensor->getHandle(), false); } -status_t SensorEventQueue::enableSensor(int32_t handle, int32_t ms) const { +status_t SensorEventQueue::enableSensor(int32_t handle, int32_t us) const { status_t err = mSensorEventConnection->enableDisable(handle, true); if (err == NO_ERROR) { - mSensorEventConnection->setEventRate(handle, ms2ns(ms)); + mSensorEventConnection->setEventRate(handle, us2ns(us)); } return err; } diff --git a/libs/surfaceflinger_client/SharedBufferStack.cpp b/libs/surfaceflinger_client/SharedBufferStack.cpp index d67a589ac90a..156a7db3c190 100644 --- a/libs/surfaceflinger_client/SharedBufferStack.cpp +++ b/libs/surfaceflinger_client/SharedBufferStack.cpp @@ -243,21 +243,6 @@ bool SharedBufferClient::LockCondition::operator()() const { (stack.queued > 0 && stack.inUse != buf)); } -SharedBufferServer::ReallocateCondition::ReallocateCondition( - SharedBufferBase* sbb, int buf) : ConditionBase(sbb), buf(buf) { -} -bool SharedBufferServer::ReallocateCondition::operator()() const { - int32_t head = stack.head; - if (uint32_t(head) >= SharedBufferStack::NUM_BUFFER_MAX) { - // if stack.head is messed up, we cannot allow the server to - // crash (since stack.head is mapped on the client side) - stack.status = BAD_VALUE; - return false; - } - // TODO: we should also check that buf has been dequeued - return (buf != stack.index[head]); -} - // ---------------------------------------------------------------------------- SharedBufferClient::QueueUpdate::QueueUpdate(SharedBufferBase* sbb) @@ -558,21 +543,6 @@ int32_t SharedBufferServer::getQueuedCount() const return stack.queued; } -status_t SharedBufferServer::assertReallocate(int buf) -{ - /* - * NOTE: it's safe to hold mLock for read while waiting for - * the ReallocateCondition because that condition is not updated - * by the thread that holds mLock for write. - */ - RWLock::AutoRLock _l(mLock); - - // TODO: need to validate "buf" - ReallocateCondition condition(this, buf); - status_t err = waitForCondition(condition); - return err; -} - Region SharedBufferServer::getDirtyRegion(int buf) const { SharedBufferStack& stack( *mSharedStack ); diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk index 4243bbf47288..9f493483bf95 100644 --- a/libs/ui/Android.mk +++ b/libs/ui/Android.mk @@ -12,7 +12,6 @@ LOCAL_SRC_FILES:= \ KeyLayoutMap.cpp \ KeyCharacterMap.cpp \ Input.cpp \ - InputDevice.cpp \ InputDispatcher.cpp \ InputManager.cpp \ InputReader.cpp \ diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp index 33dd3732bb6d..124f7b3cd217 100644 --- a/libs/ui/EventHub.cpp +++ b/libs/ui/EventHub.cpp @@ -137,9 +137,14 @@ uint32_t EventHub::getDeviceClasses(int32_t deviceId) const return device->classes; } -int EventHub::getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue, - int* outMaxValue, int* outFlat, int* outFuzz) const -{ +status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const { + outAxisInfo->valid = false; + outAxisInfo->minValue = 0; + outAxisInfo->maxValue = 0; + outAxisInfo->flat = 0; + outAxisInfo->fuzz = 0; + AutoMutex _l(mLock); device_t* device = getDevice(deviceId); if (device == NULL) return -1; @@ -147,38 +152,28 @@ int EventHub::getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue, struct input_absinfo info; if(ioctl(mFDs[id_to_index(device->id)].fd, EVIOCGABS(axis), &info)) { - LOGE("Error reading absolute controller %d for device %s fd %d\n", + LOGW("Error reading absolute controller %d for device %s fd %d\n", axis, device->name.string(), mFDs[id_to_index(device->id)].fd); - return -1; + return -errno; } - *outMinValue = info.minimum; - *outMaxValue = info.maximum; - *outFlat = info.flat; - *outFuzz = info.fuzz; - return 0; + + if (info.minimum != info.maximum) { + outAxisInfo->valid = true; + outAxisInfo->minValue = info.minimum; + outAxisInfo->maxValue = info.maximum; + outAxisInfo->flat = info.flat; + outAxisInfo->fuzz = info.fuzz; + } + return OK; } -int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t scanCode) const { +int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const { if (scanCode >= 0 && scanCode <= KEY_MAX) { AutoMutex _l(mLock); - if (deviceId == -1) { - for (int i = 0; i < mNumDevicesById; i++) { - device_t* device = mDevicesById[i].device; - if (device != NULL && (device->classes & deviceClasses) != 0) { - int32_t result = getScanCodeStateLocked(device, scanCode); - if (result >= AKEY_STATE_DOWN) { - return result; - } - } - } - return AKEY_STATE_UP; - } else { - device_t* device = getDevice(deviceId); - if (device != NULL) { - return getScanCodeStateLocked(device, scanCode); - } + device_t* device = getDevice(deviceId); + if (device != NULL) { + return getScanCodeStateLocked(device, scanCode); } } return AKEY_STATE_UNKNOWN; @@ -194,25 +189,12 @@ int32_t EventHub::getScanCodeStateLocked(device_t* device, int32_t scanCode) con return AKEY_STATE_UNKNOWN; } -int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t keyCode) const { +int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const { + AutoMutex _l(mLock); - if (deviceId == -1) { - for (int i = 0; i < mNumDevicesById; i++) { - device_t* device = mDevicesById[i].device; - if (device != NULL && (device->classes & deviceClasses) != 0) { - int32_t result = getKeyCodeStateLocked(device, keyCode); - if (result >= AKEY_STATE_DOWN) { - return result; - } - } - } - return AKEY_STATE_UP; - } else { - device_t* device = getDevice(deviceId); - if (device != NULL) { - return getKeyCodeStateLocked(device, keyCode); - } + device_t* device = getDevice(deviceId); + if (device != NULL) { + return getKeyCodeStateLocked(device, keyCode); } return AKEY_STATE_UNKNOWN; } @@ -243,24 +225,15 @@ int32_t EventHub::getKeyCodeStateLocked(device_t* device, int32_t keyCode) const return AKEY_STATE_UNKNOWN; } -int32_t EventHub::getSwitchState(int32_t deviceId, int32_t deviceClasses, int32_t sw) const { +int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const { #ifdef EV_SW if (sw >= 0 && sw <= SW_MAX) { AutoMutex _l(mLock); - if (deviceId == -1) { - deviceId = mSwitches[sw]; - if (deviceId == 0) { - return AKEY_STATE_UNKNOWN; - } - } - device_t* device = getDevice(deviceId); - if (device == NULL) { - return AKEY_STATE_UNKNOWN; + if (device != NULL) { + return getSwitchStateLocked(device, sw); } - - return getSwitchStateLocked(device, sw); } #endif return AKEY_STATE_UNKNOWN; @@ -276,6 +249,42 @@ int32_t EventHub::getSwitchStateLocked(device_t* device, int32_t sw) const { return AKEY_STATE_UNKNOWN; } +bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) const { + AutoMutex _l(mLock); + + device_t* device = getDevice(deviceId); + if (device != NULL) { + return markSupportedKeyCodesLocked(device, numCodes, keyCodes, outFlags); + } + return false; +} + +bool EventHub::markSupportedKeyCodesLocked(device_t* device, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) const { + if (device->layoutMap == NULL || device->keyBitmask == NULL) { + return false; + } + + Vector<int32_t> scanCodes; + for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) { + scanCodes.clear(); + + status_t err = device->layoutMap->findScancodes(keyCodes[codeIndex], &scanCodes); + if (! err) { + // check the possible scan codes identified by the layout map against the + // map of codes actually emitted by the driver + for (size_t sc = 0; sc < scanCodes.size(); sc++) { + if (test_bit(scanCodes[sc], device->keyBitmask)) { + outFlags[codeIndex] = 1; + break; + } + } + } + } + return true; +} + status_t EventHub::scancodeToKeycode(int32_t deviceId, int scancode, int32_t* outKeycode, uint32_t* outFlags) const { @@ -324,17 +333,15 @@ EventHub::device_t* EventHub::getDevice(int32_t deviceId) const return NULL; } -bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, - int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, - int32_t* outValue, nsecs_t* outWhen) +bool EventHub::getEvent(RawEvent* outEvent) { - *outDeviceId = 0; - *outType = 0; - *outScancode = 0; - *outKeycode = 0; - *outFlags = 0; - *outValue = 0; - *outWhen = 0; + outEvent->deviceId = 0; + outEvent->type = 0; + outEvent->scanCode = 0; + outEvent->keyCode = 0; + outEvent->flags = 0; + outEvent->value = 0; + outEvent->when = 0; status_t err; @@ -359,20 +366,27 @@ bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, LOGV("Reporting device closed: id=0x%x, name=%s\n", device->id, device->path.string()); mClosingDevices = device->next; - *outDeviceId = device->id; - if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0; - *outType = DEVICE_REMOVED; + if (device->id == mFirstKeyboardId) { + outEvent->deviceId = 0; + } else { + outEvent->deviceId = device->id; + } + outEvent->type = DEVICE_REMOVED; delete device; return true; } + if (mOpeningDevices != NULL) { device_t* device = mOpeningDevices; LOGV("Reporting device opened: id=0x%x, name=%s\n", device->id, device->path.string()); mOpeningDevices = device->next; - *outDeviceId = device->id; - if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0; - *outType = DEVICE_ADDED; + if (device->id == mFirstKeyboardId) { + outEvent->deviceId = 0; + } else { + outEvent->deviceId = device->id; + } + outEvent->type = DEVICE_ADDED; return true; } @@ -399,27 +413,36 @@ bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, if(mFDs[i].revents & POLLIN) { res = read(mFDs[i].fd, &iev, sizeof(iev)); if (res == sizeof(iev)) { + device_t* device = mDevices[i]; LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d", - mDevices[i]->path.string(), + device->path.string(), (int) iev.time.tv_sec, (int) iev.time.tv_usec, iev.type, iev.code, iev.value); - *outDeviceId = mDevices[i]->id; - if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0; - *outType = iev.type; - *outScancode = iev.code; + if (device->id == mFirstKeyboardId) { + outEvent->deviceId = 0; + } else { + outEvent->deviceId = device->id; + } + outEvent->type = iev.type; + outEvent->scanCode = iev.code; if (iev.type == EV_KEY) { - err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags); - LOGV("iev.code=%d outKeycode=%d outFlags=0x%08x err=%d\n", - iev.code, *outKeycode, *outFlags, err); + err = device->layoutMap->map(iev.code, + & outEvent->keyCode, & outEvent->flags); + LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n", + iev.code, outEvent->keyCode, outEvent->flags, err); if (err != 0) { - *outKeycode = AKEYCODE_UNKNOWN; - *outFlags = 0; + outEvent->keyCode = AKEYCODE_UNKNOWN; + outEvent->flags = 0; } } else { - *outKeycode = iev.code; + outEvent->keyCode = iev.code; } - *outValue = iev.value; - *outWhen = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec); + outEvent->value = iev.value; + + // Use an event timestamp in the same timebase as + // java.lang.System.nanoTime() and android.os.SystemClock.uptimeMillis() + // as expected by the rest of the system. + outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); return true; } else { if (res<0) { @@ -479,37 +502,6 @@ bool EventHub::openPlatformInput(void) return true; } -/* - * Inspect the known devices to determine whether physical keys exist for the given - * framework-domain key codes. - */ -bool EventHub::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { - for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) { - outFlags[codeIndex] = 0; - - // check each available hardware device for support for this keycode - Vector<int32_t> scanCodes; - for (int n = 0; (n < mFDCount) && (outFlags[codeIndex] == 0); n++) { - if (mDevices[n]) { - status_t err = mDevices[n]->layoutMap->findScancodes( - keyCodes[codeIndex], &scanCodes); - if (!err) { - // check the possible scan codes identified by the layout map against the - // map of codes actually emitted by the driver - for (size_t sc = 0; sc < scanCodes.size(); sc++) { - if (test_bit(scanCodes[sc], mDevices[n]->keyBitmask)) { - outFlags[codeIndex] = 1; - break; - } - } - } - } - } - } - - return true; -} - // ---------------------------------------------------------------------------- static bool containsNonZeroByte(const uint8_t* array, uint32_t startIndex, uint32_t endIndex) { @@ -715,16 +707,21 @@ int EventHub::open_device(const char *deviceName) // figure out the switches this device reports uint8_t sw_bitmask[sizeof_bit_array(SW_MAX + 1)]; memset(sw_bitmask, 0, sizeof(sw_bitmask)); + bool hasSwitches = false; if (ioctl(fd, EVIOCGBIT(EV_SW, sizeof(sw_bitmask)), sw_bitmask) >= 0) { for (int i=0; i<EV_SW; i++) { //LOGI("Device 0x%x sw %d: has=%d", device->id, i, test_bit(i, sw_bitmask)); if (test_bit(i, sw_bitmask)) { + hasSwitches = true; if (mSwitches[i] == 0) { mSwitches[i] = device->id; } } } } + if (hasSwitches) { + device->classes |= INPUT_DEVICE_CLASS_SWITCH; + } #endif if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) { diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp index 5253c72aa382..5fbaf0961bfb 100644 --- a/libs/ui/Input.cpp +++ b/libs/ui/Input.cpp @@ -168,4 +168,63 @@ void MotionEvent::offsetLocation(float xOffset, float yOffset) { mYOffset += yOffset; } +// class InputDeviceInfo + +InputDeviceInfo::InputDeviceInfo() { + initialize(-1, String8("uninitialized device info")); +} + +InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) : + mId(other.mId), mName(other.mName), mSources(other.mSources), + mKeyboardType(other.mKeyboardType), + mMotionRanges(other.mMotionRanges) { +} + +InputDeviceInfo::~InputDeviceInfo() { +} + +void InputDeviceInfo::initialize(int32_t id, const String8& name) { + mId = id; + mName = name; + mSources = 0; + mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE; + mMotionRanges.clear(); +} + +const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange(int32_t rangeType) const { + ssize_t index = mMotionRanges.indexOfKey(rangeType); + return index >= 0 ? & mMotionRanges.valueAt(index) : NULL; +} + +void InputDeviceInfo::addSource(uint32_t source) { + mSources |= source; +} + +void InputDeviceInfo::addMotionRange(int32_t rangeType, float min, float max, + float flat, float fuzz) { + MotionRange range = { min, max, flat, fuzz }; + addMotionRange(rangeType, range); +} + +void InputDeviceInfo::addMotionRange(int32_t rangeType, const MotionRange& range) { + mMotionRanges.add(rangeType, range); +} + +// class InputDeviceProxy + +InputDeviceProxy::InputDeviceProxy() { +} + +InputDeviceProxy::~InputDeviceProxy() { +} + +void InputDeviceProxy::getDeviceIds(Vector<int32_t>& outIds) { + // TODO use Binder +} + +sp<InputDeviceProxy> InputDeviceProxy::getDevice(int32_t id) { + // TODO use Binder + return NULL; +} + } // namespace android diff --git a/libs/ui/InputDevice.cpp b/libs/ui/InputDevice.cpp deleted file mode 100644 index b2a4d6c8fdd5..000000000000 --- a/libs/ui/InputDevice.cpp +++ /dev/null @@ -1,729 +0,0 @@ -// -// Copyright 2010 The Android Open Source Project -// -// The input reader. -// -#define LOG_TAG "InputDevice" - -//#define LOG_NDEBUG 0 - -// Log debug messages for each raw event received from the EventHub. -#define DEBUG_RAW_EVENTS 0 - -// Log debug messages about touch screen filtering hacks. -#define DEBUG_HACKS 0 - -// Log debug messages about virtual key processing. -#define DEBUG_VIRTUAL_KEYS 0 - -// Log debug messages about pointers. -#define DEBUG_POINTERS 0 - -// Log debug messages about pointer assignment calculations. -#define DEBUG_POINTER_ASSIGNMENT 0 - -#include <cutils/log.h> -#include <ui/InputDevice.h> - -#include <stddef.h> -#include <unistd.h> -#include <errno.h> -#include <limits.h> - -/* Slop distance for jumpy pointer detection. - * The vertical range of the screen divided by this is our epsilon value. */ -#define JUMPY_EPSILON_DIVISOR 212 - -/* Number of jumpy points to drop for touchscreens that need it. */ -#define JUMPY_TRANSITION_DROPS 3 -#define JUMPY_DROP_LIMIT 3 - -/* Maximum squared distance for averaging. - * If moving farther than this, turn of averaging to avoid lag in response. */ -#define AVERAGING_DISTANCE_LIMIT (75 * 75) - - -namespace android { - -// --- Static Functions --- - -template<typename T> -inline static T abs(const T& value) { - return value < 0 ? - value : value; -} - -template<typename T> -inline static T min(const T& a, const T& b) { - return a < b ? a : b; -} - -template<typename T> -inline static void swap(T& a, T& b) { - T temp = a; - a = b; - b = temp; -} - - -// --- InputDevice --- - -InputDevice::InputDevice(int32_t id, uint32_t classes, String8 name) : - id(id), classes(classes), name(name), ignored(false) { -} - -void InputDevice::reset() { - if (isKeyboard()) { - keyboard.reset(); - } - - if (isTrackball()) { - trackball.reset(); - } - - if (isMultiTouchScreen()) { - multiTouchScreen.reset(); - } else if (isSingleTouchScreen()) { - singleTouchScreen.reset(); - } - - if (isTouchScreen()) { - touchScreen.reset(); - } -} - - -// --- InputDevice::TouchData --- - -void InputDevice::TouchData::copyFrom(const TouchData& other) { - pointerCount = other.pointerCount; - idBits = other.idBits; - - for (uint32_t i = 0; i < pointerCount; i++) { - pointers[i] = other.pointers[i]; - idToIndex[i] = other.idToIndex[i]; - } -} - - -// --- InputDevice::KeyboardState --- - -void InputDevice::KeyboardState::reset() { - current.metaState = AMETA_NONE; - current.downTime = 0; -} - - -// --- InputDevice::TrackballState --- - -void InputDevice::TrackballState::reset() { - accumulator.clear(); - current.down = false; - current.downTime = 0; -} - - -// --- InputDevice::TouchScreenState --- - -void InputDevice::TouchScreenState::reset() { - lastTouch.clear(); - downTime = 0; - currentVirtualKey.status = CurrentVirtualKeyState::STATUS_UP; - - for (uint32_t i = 0; i < MAX_POINTERS; i++) { - averagingTouchFilter.historyStart[i] = 0; - averagingTouchFilter.historyEnd[i] = 0; - } - - jumpyTouchFilter.jumpyPointsDropped = 0; -} - -struct PointerDistanceHeapElement { - uint32_t currentPointerIndex : 8; - uint32_t lastPointerIndex : 8; - uint64_t distance : 48; // squared distance -}; - -void InputDevice::TouchScreenState::calculatePointerIds() { - uint32_t currentPointerCount = currentTouch.pointerCount; - uint32_t lastPointerCount = lastTouch.pointerCount; - - if (currentPointerCount == 0) { - // No pointers to assign. - currentTouch.idBits.clear(); - } else if (lastPointerCount == 0) { - // All pointers are new. - currentTouch.idBits.clear(); - for (uint32_t i = 0; i < currentPointerCount; i++) { - currentTouch.pointers[i].id = i; - currentTouch.idToIndex[i] = i; - currentTouch.idBits.markBit(i); - } - } else if (currentPointerCount == 1 && lastPointerCount == 1) { - // Only one pointer and no change in count so it must have the same id as before. - uint32_t id = lastTouch.pointers[0].id; - currentTouch.pointers[0].id = id; - currentTouch.idToIndex[id] = 0; - currentTouch.idBits.value = BitSet32::valueForBit(id); - } else { - // General case. - // We build a heap of squared euclidean distances between current and last pointers - // associated with the current and last pointer indices. Then, we find the best - // match (by distance) for each current pointer. - PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS]; - - uint32_t heapSize = 0; - for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount; - currentPointerIndex++) { - for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount; - lastPointerIndex++) { - int64_t deltaX = currentTouch.pointers[currentPointerIndex].x - - lastTouch.pointers[lastPointerIndex].x; - int64_t deltaY = currentTouch.pointers[currentPointerIndex].y - - lastTouch.pointers[lastPointerIndex].y; - - uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); - - // Insert new element into the heap (sift up). - heap[heapSize].currentPointerIndex = currentPointerIndex; - heap[heapSize].lastPointerIndex = lastPointerIndex; - heap[heapSize].distance = distance; - heapSize += 1; - } - } - - // Heapify - for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) { - startIndex -= 1; - for (uint32_t parentIndex = startIndex; ;) { - uint32_t childIndex = parentIndex * 2 + 1; - if (childIndex >= heapSize) { - break; - } - - if (childIndex + 1 < heapSize - && heap[childIndex + 1].distance < heap[childIndex].distance) { - childIndex += 1; - } - - if (heap[parentIndex].distance <= heap[childIndex].distance) { - break; - } - - swap(heap[parentIndex], heap[childIndex]); - parentIndex = childIndex; - } - } - -#if DEBUG_POINTER_ASSIGNMENT - LOGD("calculatePointerIds - initial distance min-heap: size=%d", heapSize); - for (size_t i = 0; i < heapSize; i++) { - LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld", - i, heap[i].currentPointerIndex, heap[i].lastPointerIndex, - heap[i].distance); - } -#endif - - // Pull matches out by increasing order of distance. - // To avoid reassigning pointers that have already been matched, the loop keeps track - // of which last and current pointers have been matched using the matchedXXXBits variables. - // It also tracks the used pointer id bits. - BitSet32 matchedLastBits(0); - BitSet32 matchedCurrentBits(0); - BitSet32 usedIdBits(0); - bool first = true; - for (uint32_t i = min(currentPointerCount, lastPointerCount); i > 0; i--) { - for (;;) { - if (first) { - // The first time through the loop, we just consume the root element of - // the heap (the one with smallest distance). - first = false; - } else { - // Previous iterations consumed the root element of the heap. - // Pop root element off of the heap (sift down). - heapSize -= 1; - assert(heapSize > 0); - - // Sift down. - heap[0] = heap[heapSize]; - for (uint32_t parentIndex = 0; ;) { - uint32_t childIndex = parentIndex * 2 + 1; - if (childIndex >= heapSize) { - break; - } - - if (childIndex + 1 < heapSize - && heap[childIndex + 1].distance < heap[childIndex].distance) { - childIndex += 1; - } - - if (heap[parentIndex].distance <= heap[childIndex].distance) { - break; - } - - swap(heap[parentIndex], heap[childIndex]); - parentIndex = childIndex; - } - -#if DEBUG_POINTER_ASSIGNMENT - LOGD("calculatePointerIds - reduced distance min-heap: size=%d", heapSize); - for (size_t i = 0; i < heapSize; i++) { - LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld", - i, heap[i].currentPointerIndex, heap[i].lastPointerIndex, - heap[i].distance); - } -#endif - } - - uint32_t currentPointerIndex = heap[0].currentPointerIndex; - if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched - - uint32_t lastPointerIndex = heap[0].lastPointerIndex; - if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched - - matchedCurrentBits.markBit(currentPointerIndex); - matchedLastBits.markBit(lastPointerIndex); - - uint32_t id = lastTouch.pointers[lastPointerIndex].id; - currentTouch.pointers[currentPointerIndex].id = id; - currentTouch.idToIndex[id] = currentPointerIndex; - usedIdBits.markBit(id); - -#if DEBUG_POINTER_ASSIGNMENT - LOGD("calculatePointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld", - lastPointerIndex, currentPointerIndex, id, heap[0].distance); -#endif - break; - } - } - - // Assign fresh ids to new pointers. - if (currentPointerCount > lastPointerCount) { - for (uint32_t i = currentPointerCount - lastPointerCount; ;) { - uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit(); - uint32_t id = usedIdBits.firstUnmarkedBit(); - - currentTouch.pointers[currentPointerIndex].id = id; - currentTouch.idToIndex[id] = currentPointerIndex; - usedIdBits.markBit(id); - -#if DEBUG_POINTER_ASSIGNMENT - LOGD("calculatePointerIds - assigned: cur=%d, id=%d", - currentPointerIndex, id); -#endif - - if (--i == 0) break; // done - matchedCurrentBits.markBit(currentPointerIndex); - } - } - - // Fix id bits. - currentTouch.idBits = usedIdBits; - } -} - -/* Special hack for devices that have bad screen data: if one of the - * points has moved more than a screen height from the last position, - * then drop it. */ -bool InputDevice::TouchScreenState::applyBadTouchFilter() { - // This hack requires valid axis parameters. - if (! parameters.yAxis.valid) { - return false; - } - - uint32_t pointerCount = currentTouch.pointerCount; - - // Nothing to do if there are no points. - if (pointerCount == 0) { - return false; - } - - // Don't do anything if a finger is going down or up. We run - // here before assigning pointer IDs, so there isn't a good - // way to do per-finger matching. - if (pointerCount != lastTouch.pointerCount) { - return false; - } - - // We consider a single movement across more than a 7/16 of - // the long size of the screen to be bad. This was a magic value - // determined by looking at the maximum distance it is feasible - // to actually move in one sample. - int32_t maxDeltaY = parameters.yAxis.range * 7 / 16; - - // XXX The original code in InputDevice.java included commented out - // code for testing the X axis. Note that when we drop a point - // we don't actually restore the old X either. Strange. - // The old code also tries to track when bad points were previously - // detected but it turns out that due to the placement of a "break" - // at the end of the loop, we never set mDroppedBadPoint to true - // so it is effectively dead code. - // Need to figure out if the old code is busted or just overcomplicated - // but working as intended. - - // Look through all new points and see if any are farther than - // acceptable from all previous points. - for (uint32_t i = pointerCount; i-- > 0; ) { - int32_t y = currentTouch.pointers[i].y; - int32_t closestY = INT_MAX; - int32_t closestDeltaY = 0; - -#if DEBUG_HACKS - LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y); -#endif - - for (uint32_t j = pointerCount; j-- > 0; ) { - int32_t lastY = lastTouch.pointers[j].y; - int32_t deltaY = abs(y - lastY); - -#if DEBUG_HACKS - LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d", - j, lastY, deltaY); -#endif - - if (deltaY < maxDeltaY) { - goto SkipSufficientlyClosePoint; - } - if (deltaY < closestDeltaY) { - closestDeltaY = deltaY; - closestY = lastY; - } - } - - // Must not have found a close enough match. -#if DEBUG_HACKS - LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d", - i, y, closestY, closestDeltaY, maxDeltaY); -#endif - - currentTouch.pointers[i].y = closestY; - return true; // XXX original code only corrects one point - - SkipSufficientlyClosePoint: ; - } - - // No change. - return false; -} - -/* Special hack for devices that have bad screen data: drop points where - * the coordinate value for one axis has jumped to the other pointer's location. - */ -bool InputDevice::TouchScreenState::applyJumpyTouchFilter() { - // This hack requires valid axis parameters. - if (! parameters.yAxis.valid) { - return false; - } - - uint32_t pointerCount = currentTouch.pointerCount; - if (lastTouch.pointerCount != pointerCount) { -#if DEBUG_HACKS - LOGD("JumpyTouchFilter: Different pointer count %d -> %d", - lastTouch.pointerCount, pointerCount); - for (uint32_t i = 0; i < pointerCount; i++) { - LOGD(" Pointer %d (%d, %d)", i, - currentTouch.pointers[i].x, currentTouch.pointers[i].y); - } -#endif - - if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) { - if (lastTouch.pointerCount == 1 && pointerCount == 2) { - // Just drop the first few events going from 1 to 2 pointers. - // They're bad often enough that they're not worth considering. - currentTouch.pointerCount = 1; - jumpyTouchFilter.jumpyPointsDropped += 1; - -#if DEBUG_HACKS - LOGD("JumpyTouchFilter: Pointer 2 dropped"); -#endif - return true; - } else if (lastTouch.pointerCount == 2 && pointerCount == 1) { - // The event when we go from 2 -> 1 tends to be messed up too - currentTouch.pointerCount = 2; - currentTouch.pointers[0] = lastTouch.pointers[0]; - currentTouch.pointers[1] = lastTouch.pointers[1]; - jumpyTouchFilter.jumpyPointsDropped += 1; - -#if DEBUG_HACKS - for (int32_t i = 0; i < 2; i++) { - LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i, - currentTouch.pointers[i].x, currentTouch.pointers[i].y); - } -#endif - return true; - } - } - // Reset jumpy points dropped on other transitions or if limit exceeded. - jumpyTouchFilter.jumpyPointsDropped = 0; - -#if DEBUG_HACKS - LOGD("JumpyTouchFilter: Transition - drop limit reset"); -#endif - return false; - } - - // We have the same number of pointers as last time. - // A 'jumpy' point is one where the coordinate value for one axis - // has jumped to the other pointer's location. No need to do anything - // else if we only have one pointer. - if (pointerCount < 2) { - return false; - } - - if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) { - int jumpyEpsilon = parameters.yAxis.range / JUMPY_EPSILON_DIVISOR; - - // We only replace the single worst jumpy point as characterized by pointer distance - // in a single axis. - int32_t badPointerIndex = -1; - int32_t badPointerReplacementIndex = -1; - int32_t badPointerDistance = INT_MIN; // distance to be corrected - - for (uint32_t i = pointerCount; i-- > 0; ) { - int32_t x = currentTouch.pointers[i].x; - int32_t y = currentTouch.pointers[i].y; - -#if DEBUG_HACKS - LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y); -#endif - - // Check if a touch point is too close to another's coordinates - bool dropX = false, dropY = false; - for (uint32_t j = 0; j < pointerCount; j++) { - if (i == j) { - continue; - } - - if (abs(x - currentTouch.pointers[j].x) <= jumpyEpsilon) { - dropX = true; - break; - } - - if (abs(y - currentTouch.pointers[j].y) <= jumpyEpsilon) { - dropY = true; - break; - } - } - if (! dropX && ! dropY) { - continue; // not jumpy - } - - // Find a replacement candidate by comparing with older points on the - // complementary (non-jumpy) axis. - int32_t distance = INT_MIN; // distance to be corrected - int32_t replacementIndex = -1; - - if (dropX) { - // X looks too close. Find an older replacement point with a close Y. - int32_t smallestDeltaY = INT_MAX; - for (uint32_t j = 0; j < pointerCount; j++) { - int32_t deltaY = abs(y - lastTouch.pointers[j].y); - if (deltaY < smallestDeltaY) { - smallestDeltaY = deltaY; - replacementIndex = j; - } - } - distance = abs(x - lastTouch.pointers[replacementIndex].x); - } else { - // Y looks too close. Find an older replacement point with a close X. - int32_t smallestDeltaX = INT_MAX; - for (uint32_t j = 0; j < pointerCount; j++) { - int32_t deltaX = abs(x - lastTouch.pointers[j].x); - if (deltaX < smallestDeltaX) { - smallestDeltaX = deltaX; - replacementIndex = j; - } - } - distance = abs(y - lastTouch.pointers[replacementIndex].y); - } - - // If replacing this pointer would correct a worse error than the previous ones - // considered, then use this replacement instead. - if (distance > badPointerDistance) { - badPointerIndex = i; - badPointerReplacementIndex = replacementIndex; - badPointerDistance = distance; - } - } - - // Correct the jumpy pointer if one was found. - if (badPointerIndex >= 0) { -#if DEBUG_HACKS - LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)", - badPointerIndex, - lastTouch.pointers[badPointerReplacementIndex].x, - lastTouch.pointers[badPointerReplacementIndex].y); -#endif - - currentTouch.pointers[badPointerIndex].x = - lastTouch.pointers[badPointerReplacementIndex].x; - currentTouch.pointers[badPointerIndex].y = - lastTouch.pointers[badPointerReplacementIndex].y; - jumpyTouchFilter.jumpyPointsDropped += 1; - return true; - } - } - - jumpyTouchFilter.jumpyPointsDropped = 0; - return false; -} - -/* Special hack for devices that have bad screen data: aggregate and - * compute averages of the coordinate data, to reduce the amount of - * jitter seen by applications. */ -void InputDevice::TouchScreenState::applyAveragingTouchFilter() { - for (uint32_t currentIndex = 0; currentIndex < currentTouch.pointerCount; currentIndex++) { - uint32_t id = currentTouch.pointers[currentIndex].id; - int32_t x = currentTouch.pointers[currentIndex].x; - int32_t y = currentTouch.pointers[currentIndex].y; - int32_t pressure = currentTouch.pointers[currentIndex].pressure; - - if (lastTouch.idBits.hasBit(id)) { - // Pointer was down before and is still down now. - // Compute average over history trace. - uint32_t start = averagingTouchFilter.historyStart[id]; - uint32_t end = averagingTouchFilter.historyEnd[id]; - - int64_t deltaX = x - averagingTouchFilter.historyData[end].pointers[id].x; - int64_t deltaY = y - averagingTouchFilter.historyData[end].pointers[id].y; - uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); - -#if DEBUG_HACKS - LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld", - id, distance); -#endif - - if (distance < AVERAGING_DISTANCE_LIMIT) { - // Increment end index in preparation for recording new historical data. - end += 1; - if (end > AVERAGING_HISTORY_SIZE) { - end = 0; - } - - // If the end index has looped back to the start index then we have filled - // the historical trace up to the desired size so we drop the historical - // data at the start of the trace. - if (end == start) { - start += 1; - if (start > AVERAGING_HISTORY_SIZE) { - start = 0; - } - } - - // Add the raw data to the historical trace. - averagingTouchFilter.historyStart[id] = start; - averagingTouchFilter.historyEnd[id] = end; - averagingTouchFilter.historyData[end].pointers[id].x = x; - averagingTouchFilter.historyData[end].pointers[id].y = y; - averagingTouchFilter.historyData[end].pointers[id].pressure = pressure; - - // Average over all historical positions in the trace by total pressure. - int32_t averagedX = 0; - int32_t averagedY = 0; - int32_t totalPressure = 0; - for (;;) { - int32_t historicalX = averagingTouchFilter.historyData[start].pointers[id].x; - int32_t historicalY = averagingTouchFilter.historyData[start].pointers[id].y; - int32_t historicalPressure = averagingTouchFilter.historyData[start] - .pointers[id].pressure; - - averagedX += historicalX * historicalPressure; - averagedY += historicalY * historicalPressure; - totalPressure += historicalPressure; - - if (start == end) { - break; - } - - start += 1; - if (start > AVERAGING_HISTORY_SIZE) { - start = 0; - } - } - - averagedX /= totalPressure; - averagedY /= totalPressure; - -#if DEBUG_HACKS - LOGD("AveragingTouchFilter: Pointer id %d - " - "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure, - averagedX, averagedY); -#endif - - currentTouch.pointers[currentIndex].x = averagedX; - currentTouch.pointers[currentIndex].y = averagedY; - } else { -#if DEBUG_HACKS - LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id); -#endif - } - } else { -#if DEBUG_HACKS - LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id); -#endif - } - - // Reset pointer history. - averagingTouchFilter.historyStart[id] = 0; - averagingTouchFilter.historyEnd[id] = 0; - averagingTouchFilter.historyData[0].pointers[id].x = x; - averagingTouchFilter.historyData[0].pointers[id].y = y; - averagingTouchFilter.historyData[0].pointers[id].pressure = pressure; - } -} - -bool InputDevice::TouchScreenState::isPointInsideDisplay(int32_t x, int32_t y) const { - if (! parameters.xAxis.valid || ! parameters.yAxis.valid) { - // Assume all points on a touch screen without valid axis parameters are - // inside the display. - return true; - } - - return x >= parameters.xAxis.minValue - && x <= parameters.xAxis.maxValue - && y >= parameters.yAxis.minValue - && y <= parameters.yAxis.maxValue; -} - -const InputDevice::VirtualKey* InputDevice::TouchScreenState::findVirtualKeyHit() const { - int32_t x = currentTouch.pointers[0].x; - int32_t y = currentTouch.pointers[0].y; - for (size_t i = 0; i < virtualKeys.size(); i++) { - const InputDevice::VirtualKey& virtualKey = virtualKeys[i]; - -#if DEBUG_VIRTUAL_KEYS - LOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, " - "left=%d, top=%d, right=%d, bottom=%d", - x, y, - virtualKey.keyCode, virtualKey.scanCode, - virtualKey.hitLeft, virtualKey.hitTop, - virtualKey.hitRight, virtualKey.hitBottom); -#endif - - if (virtualKey.isHit(x, y)) { - return & virtualKey; - } - } - - return NULL; -} - - -// --- InputDevice::SingleTouchScreenState --- - -void InputDevice::SingleTouchScreenState::reset() { - accumulator.clear(); - current.down = false; - current.x = 0; - current.y = 0; - current.pressure = 0; - current.size = 0; -} - - -// --- InputDevice::MultiTouchScreenState --- - -void InputDevice::MultiTouchScreenState::reset() { - accumulator.clear(); -} - -} // namespace android diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp index a55864be1c5e..b53f140f63fe 100644 --- a/libs/ui/InputDispatcher.cpp +++ b/libs/ui/InputDispatcher.cpp @@ -184,11 +184,6 @@ void InputDispatcher::dispatchOnce() { // Run any deferred commands. skipPoll |= runCommandsLockedInterruptible(); - - // Wake up synchronization waiters, if needed. - if (isFullySynchronizedLocked()) { - mFullySynchronizedCondition.broadcast(); - } } // release lock // If we dispatched anything, don't poll just now. Wait for the next iteration. @@ -560,6 +555,10 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, dispatchEntry->headMotionSample = NULL; dispatchEntry->tailMotionSample = NULL; + if (dispatchEntry->isSyncTarget()) { + eventEntry->pendingSyncDispatches += 1; + } + // Handle the case where we could not stream a new motion sample because the consumer has // already consumed the motion event (otherwise the corresponding dispatch entry would // still be in the outbound queue for this connection). We set the head motion sample @@ -789,6 +788,9 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, } // Finished. connection->outboundQueue.dequeueAtHead(); + if (dispatchEntry->isSyncTarget()) { + decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry); + } mAllocator.releaseDispatchEntry(dispatchEntry); } else { // If the head is not in progress, then we must have already dequeued the in @@ -854,6 +856,9 @@ void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime, if (! connection->outboundQueue.isEmpty()) { do { DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead(); + if (dispatchEntry->isSyncTarget()) { + decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry); + } mAllocator.releaseDispatchEntry(dispatchEntry); } while (! connection->outboundQueue.isEmpty()); @@ -1097,7 +1102,7 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t Connection* connection = mActiveConnections.itemAt(i); if (! connection->outboundQueue.isEmpty()) { DispatchEntry* dispatchEntry = connection->outboundQueue.tail.prev; - if (dispatchEntry->targetFlags & InputTarget::FLAG_SYNC) { + if (dispatchEntry->isSyncTarget()) { if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) { goto NoBatchingOrStreaming; } @@ -1148,11 +1153,11 @@ NoBatchingOrStreaming:; } int32_t InputDispatcher::injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) { + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { #if DEBUG_INBOUND_EVENT_DETAILS LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, " - "sync=%d, timeoutMillis=%d", - event->getType(), injectorPid, injectorUid, sync, timeoutMillis); + "syncMode=%d, timeoutMillis=%d", + event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis); #endif nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis); @@ -1167,6 +1172,10 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, injectedEntry->injectorPid = injectorPid; injectedEntry->injectorUid = injectorUid; + if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) { + injectedEntry->injectionIsAsync = true; + } + wasEmpty = mInboundQueue.isEmpty(); mInboundQueue.enqueueAtTail(injectedEntry); @@ -1180,37 +1189,59 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, { // acquire lock AutoMutex _l(mLock); - for (;;) { - injectionResult = injectedEntry->injectionResult; - if (injectionResult != INPUT_EVENT_INJECTION_PENDING) { - break; - } - - nsecs_t remainingTimeout = endTime - now(); - if (remainingTimeout <= 0) { - injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; - sync = false; - break; - } - - mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout); - } + if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) { + injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; + } else { + for (;;) { + injectionResult = injectedEntry->injectionResult; + if (injectionResult != INPUT_EVENT_INJECTION_PENDING) { + break; + } - if (sync) { - while (! isFullySynchronizedLocked()) { nsecs_t remainingTimeout = endTime - now(); if (remainingTimeout <= 0) { +#if DEBUG_INJECTION + LOGD("injectInputEvent - Timed out waiting for injection result " + "to become available."); +#endif injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; break; } - mFullySynchronizedCondition.waitRelative(mLock, remainingTimeout); + mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout); + } + + if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED + && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) { + while (injectedEntry->pendingSyncDispatches != 0) { +#if DEBUG_INJECTION + LOGD("injectInputEvent - Waiting for %d pending synchronous dispatches.", + injectedEntry->pendingSyncDispatches); +#endif + nsecs_t remainingTimeout = endTime - now(); + if (remainingTimeout <= 0) { +#if DEBUG_INJECTION + LOGD("injectInputEvent - Timed out waiting for pending synchronous " + "dispatches to finish."); +#endif + injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; + break; + } + + mInjectionSyncFinishedCondition.waitRelative(mLock, remainingTimeout); + } } } mAllocator.releaseEventEntry(injectedEntry); } // release lock +#if DEBUG_INJECTION + LOGD("injectInputEvent - Finished with result %d. " + "injectorPid=%d, injectorUid=%d", + injectionResult, injectorPid, injectorUid); +#endif + return injectionResult; } @@ -1222,13 +1253,35 @@ void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t inject injectionResult, entry->injectorPid, entry->injectorUid); #endif + if (entry->injectionIsAsync) { + // Log the outcome since the injector did not wait for the injection result. + switch (injectionResult) { + case INPUT_EVENT_INJECTION_SUCCEEDED: + LOGV("Asynchronous input event injection succeeded."); + break; + case INPUT_EVENT_INJECTION_FAILED: + LOGW("Asynchronous input event injection failed."); + break; + case INPUT_EVENT_INJECTION_PERMISSION_DENIED: + LOGW("Asynchronous input event injection permission denied."); + break; + case INPUT_EVENT_INJECTION_TIMED_OUT: + LOGW("Asynchronous input event injection timed out."); + break; + } + } + entry->injectionResult = injectionResult; mInjectionResultAvailableCondition.broadcast(); } } -bool InputDispatcher::isFullySynchronizedLocked() { - return mInboundQueue.isEmpty() && mActiveConnections.isEmpty(); +void InputDispatcher::decrementPendingSyncDispatchesLocked(EventEntry* entry) { + entry->pendingSyncDispatches -= 1; + + if (entry->isInjected() && entry->pendingSyncDispatches == 0) { + mInjectionSyncFinishedCondition.broadcast(); + } } InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked( @@ -1498,8 +1551,10 @@ void InputDispatcher::Allocator::initializeEventEntry(EventEntry* entry, int32_t entry->dispatchInProgress = false; entry->eventTime = eventTime; entry->injectionResult = INPUT_EVENT_INJECTION_PENDING; + entry->injectionIsAsync = false; entry->injectorPid = -1; entry->injectorUid = -1; + entry->pendingSyncDispatches = 0; } InputDispatcher::ConfigurationChangedEntry* diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp index e1d15a4ba977..ed4f07b277b7 100644 --- a/libs/ui/InputManager.cpp +++ b/libs/ui/InputManager.cpp @@ -81,34 +81,43 @@ status_t InputManager::unregisterInputChannel(const sp<InputChannel>& inputChann } int32_t InputManager::injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) { - return mDispatcher->injectInputEvent(event, injectorPid, injectorUid, sync, timeoutMillis); + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { + return mDispatcher->injectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis); } void InputManager::preemptInputDispatch() { mDispatcher->preemptInputDispatch(); } -void InputManager::getInputConfiguration(InputConfiguration* outConfiguration) const { - mReader->getCurrentInputConfiguration(outConfiguration); +void InputManager::getInputConfiguration(InputConfiguration* outConfiguration) { + mReader->getInputConfiguration(outConfiguration); } -int32_t InputManager::getScanCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t scanCode) const { - return mReader->getCurrentScanCodeState(deviceId, deviceClasses, scanCode); +status_t InputManager::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) { + return mReader->getInputDeviceInfo(deviceId, outDeviceInfo); } -int32_t InputManager::getKeyCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t keyCode) const { - return mReader->getCurrentKeyCodeState(deviceId, deviceClasses, keyCode); +void InputManager::getInputDeviceIds(Vector<int32_t>& outDeviceIds) { + mReader->getInputDeviceIds(outDeviceIds); } -int32_t InputManager::getSwitchState(int32_t deviceId, int32_t deviceClasses, int32_t sw) const { - return mReader->getCurrentSwitchState(deviceId, deviceClasses, sw); +int32_t InputManager::getScanCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t scanCode) { + return mReader->getScanCodeState(deviceId, sourceMask, scanCode); } -bool InputManager::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { - return mReader->hasKeys(numCodes, keyCodes, outFlags); +int32_t InputManager::getKeyCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t keyCode) { + return mReader->getKeyCodeState(deviceId, sourceMask, keyCode); +} + +int32_t InputManager::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) { + return mReader->getSwitchState(deviceId, sourceMask, sw); +} + +bool InputManager::hasKeys(int32_t deviceId, uint32_t sourceMask, + size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) { + return mReader->hasKeys(deviceId, sourceMask, numCodes, keyCodes, outFlags); } } // namespace android diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp index 30e391ff61d6..56e29778ee93 100644 --- a/libs/ui/InputReader.cpp +++ b/libs/ui/InputReader.cpp @@ -31,10 +31,6 @@ #include <limits.h> #include <math.h> -/** Amount that trackball needs to move in order to generate a key event. */ -#define TRACKBALL_MOVEMENT_THRESHOLD 6 - - namespace android { // --- Static Functions --- @@ -115,17 +111,21 @@ int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { return keyCode; } +static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) { + return (sources & sourceMask & ~ AINPUT_SOURCE_CLASS_MASK) != 0; +} + // --- InputReader --- InputReader::InputReader(const sp<EventHubInterface>& eventHub, const sp<InputReaderPolicyInterface>& policy, const sp<InputDispatcherInterface>& dispatcher) : - mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher) { + mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher), + mGlobalMetaState(0) { configureExcludedDevices(); - resetGlobalMetaState(); - resetDisplayProperties(); - updateExportedVirtualKeyState(); + updateGlobalMetaState(); + updateInputConfiguration(); } InputReader::~InputReader() { @@ -136,12 +136,7 @@ InputReader::~InputReader() { void InputReader::loopOnce() { RawEvent rawEvent; - mEventHub->getEvent(& rawEvent.deviceId, & rawEvent.type, & rawEvent.scanCode, - & rawEvent.keyCode, & rawEvent.flags, & rawEvent.value, & rawEvent.when); - - // Replace the event timestamp so it is in same timebase as java.lang.System.nanoTime() - // and android.os.SystemClock.uptimeMillis() as expected by the rest of the system. - rawEvent.when = systemTime(SYSTEM_TIME_MONOTONIC); + mEventHub->getEvent(& rawEvent); #if DEBUG_RAW_EVENTS LOGD("Input event: device=0x%x type=0x%x scancode=%d keycode=%d value=%d", @@ -155,621 +150,1371 @@ void InputReader::loopOnce() { void InputReader::process(const RawEvent* rawEvent) { switch (rawEvent->type) { case EventHubInterface::DEVICE_ADDED: - handleDeviceAdded(rawEvent); + addDevice(rawEvent->when, rawEvent->deviceId); break; case EventHubInterface::DEVICE_REMOVED: - handleDeviceRemoved(rawEvent); + removeDevice(rawEvent->when, rawEvent->deviceId); break; - case EV_SYN: - handleSync(rawEvent); + default: + consumeEvent(rawEvent); break; + } +} - case EV_KEY: - handleKey(rawEvent); - break; +void InputReader::addDevice(nsecs_t when, int32_t deviceId) { + String8 name = mEventHub->getDeviceName(deviceId); + uint32_t classes = mEventHub->getDeviceClasses(deviceId); - case EV_REL: - handleRelativeMotion(rawEvent); - break; + InputDevice* device = createDevice(deviceId, name, classes); + device->configure(); - case EV_ABS: - handleAbsoluteMotion(rawEvent); - break; + bool added = false; + { // acquire device registry writer lock + RWLock::AutoWLock _wl(mDeviceRegistryLock); - case EV_SW: - handleSwitch(rawEvent); - break; + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex < 0) { + mDevices.add(deviceId, device); + added = true; + } + } // release device registry writer lock + + if (! added) { + LOGW("Ignoring spurious device added event for deviceId %d.", deviceId); + delete device; + return; } + + if (device->isIgnored()) { + LOGI("Device added: id=0x%x, name=%s (ignored non-input device)", + deviceId, name.string()); + } else { + LOGI("Device added: id=0x%x, name=%s, sources=%08x", + deviceId, name.string(), device->getSources()); + } + + handleConfigurationChanged(when); } -void InputReader::handleDeviceAdded(const RawEvent* rawEvent) { - InputDevice* device = getDevice(rawEvent->deviceId); - if (device) { - LOGW("Ignoring spurious device added event for deviceId %d.", rawEvent->deviceId); +void InputReader::removeDevice(nsecs_t when, int32_t deviceId) { + bool removed = false; + InputDevice* device = NULL; + { // acquire device registry writer lock + RWLock::AutoWLock _wl(mDeviceRegistryLock); + + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + device = mDevices.valueAt(deviceIndex); + mDevices.removeItemsAt(deviceIndex, 1); + removed = true; + } + } // release device registry writer lock + + if (! removed) { + LOGW("Ignoring spurious device removed event for deviceId %d.", deviceId); return; } - addDevice(rawEvent->when, rawEvent->deviceId); + device->reset(); + + if (device->isIgnored()) { + LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)", + device->getId(), device->getName().string()); + } else { + LOGI("Device removed: id=0x%x, name=%s, sources=%08x", + device->getId(), device->getName().string(), device->getSources()); + } + + delete device; + + handleConfigurationChanged(when); } -void InputReader::handleDeviceRemoved(const RawEvent* rawEvent) { - InputDevice* device = getDevice(rawEvent->deviceId); - if (! device) { - LOGW("Ignoring spurious device removed event for deviceId %d.", rawEvent->deviceId); - return; +InputDevice* InputReader::createDevice(int32_t deviceId, const String8& name, uint32_t classes) { + InputDevice* device = new InputDevice(this, deviceId, name); + + const int32_t associatedDisplayId = 0; // FIXME: hardcoded for current single-display devices + + // Switch-like devices. + if (classes & INPUT_DEVICE_CLASS_SWITCH) { + device->addMapper(new SwitchInputMapper(device)); } - removeDevice(rawEvent->when, device); + // Keyboard-like devices. + uint32_t keyboardSources = 0; + int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC; + if (classes & INPUT_DEVICE_CLASS_KEYBOARD) { + keyboardSources |= AINPUT_SOURCE_KEYBOARD; + } + if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) { + keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC; + } + if (classes & INPUT_DEVICE_CLASS_DPAD) { + keyboardSources |= AINPUT_SOURCE_DPAD; + } + if (classes & INPUT_DEVICE_CLASS_GAMEPAD) { + keyboardSources |= AINPUT_SOURCE_GAMEPAD; + } + + if (keyboardSources != 0) { + device->addMapper(new KeyboardInputMapper(device, + associatedDisplayId, keyboardSources, keyboardType)); + } + + // Trackball-like devices. + if (classes & INPUT_DEVICE_CLASS_TRACKBALL) { + device->addMapper(new TrackballInputMapper(device, associatedDisplayId)); + } + + // Touchscreen-like devices. + if (classes & INPUT_DEVICE_CLASS_TOUCHSCREEN_MT) { + device->addMapper(new MultiTouchInputMapper(device, associatedDisplayId)); + } else if (classes & INPUT_DEVICE_CLASS_TOUCHSCREEN) { + device->addMapper(new SingleTouchInputMapper(device, associatedDisplayId)); + } + + return device; } -void InputReader::handleSync(const RawEvent* rawEvent) { - InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); - if (! device) return; +void InputReader::consumeEvent(const RawEvent* rawEvent) { + int32_t deviceId = rawEvent->deviceId; - if (rawEvent->scanCode == SYN_MT_REPORT) { - // MultiTouch Sync: The driver has returned all data for *one* of the pointers. - // We drop pointers with pressure <= 0 since that indicates they are not down. - if (device->isMultiTouchScreen()) { - uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount; + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); - if (device->multiTouchScreen.accumulator.pointers[pointerIndex].fields) { - if (pointerIndex == MAX_POINTERS) { - LOGW("MultiTouch device driver returned more than maximum of %d pointers.", - MAX_POINTERS); - } else { - pointerIndex += 1; - device->multiTouchScreen.accumulator.pointerCount = pointerIndex; + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex < 0) { + LOGW("Discarding event for unknown deviceId %d.", deviceId); + return; + } + + InputDevice* device = mDevices.valueAt(deviceIndex); + if (device->isIgnored()) { + //LOGD("Discarding event for ignored deviceId %d.", deviceId); + return; + } + + device->process(rawEvent); + } // release device registry reader lock +} + +void InputReader::handleConfigurationChanged(nsecs_t when) { + // Reset global meta state because it depends on the list of all configured devices. + updateGlobalMetaState(); + + // Update input configuration. + updateInputConfiguration(); + + // Enqueue configuration changed. + mDispatcher->notifyConfigurationChanged(when); +} + +void InputReader::configureExcludedDevices() { + Vector<String8> excludedDeviceNames; + mPolicy->getExcludedDeviceNames(excludedDeviceNames); + + for (size_t i = 0; i < excludedDeviceNames.size(); i++) { + mEventHub->addExcludedDevice(excludedDeviceNames[i]); + } +} + +void InputReader::updateGlobalMetaState() { + { // acquire state lock + AutoMutex _l(mStateLock); + + mGlobalMetaState = 0; + + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + mGlobalMetaState |= device->getMetaState(); + } + } // release device registry reader lock + } // release state lock +} + +int32_t InputReader::getGlobalMetaState() { + { // acquire state lock + AutoMutex _l(mStateLock); + + return mGlobalMetaState; + } // release state lock +} + +void InputReader::updateInputConfiguration() { + { // acquire state lock + AutoMutex _l(mStateLock); + + int32_t touchScreenConfig = InputConfiguration::TOUCHSCREEN_NOTOUCH; + int32_t keyboardConfig = InputConfiguration::KEYBOARD_NOKEYS; + int32_t navigationConfig = InputConfiguration::NAVIGATION_NONAV; + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + InputDeviceInfo deviceInfo; + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + device->getDeviceInfo(& deviceInfo); + uint32_t sources = deviceInfo.getSources(); + + if ((sources & AINPUT_SOURCE_TOUCHSCREEN) == AINPUT_SOURCE_TOUCHSCREEN) { + touchScreenConfig = InputConfiguration::TOUCHSCREEN_FINGER; + } + if ((sources & AINPUT_SOURCE_TRACKBALL) == AINPUT_SOURCE_TRACKBALL) { + navigationConfig = InputConfiguration::NAVIGATION_TRACKBALL; + } else if ((sources & AINPUT_SOURCE_DPAD) == AINPUT_SOURCE_DPAD) { + navigationConfig = InputConfiguration::NAVIGATION_DPAD; + } + if (deviceInfo.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) { + keyboardConfig = InputConfiguration::KEYBOARD_QWERTY; } } + } // release device registry reader lock + + mInputConfiguration.touchScreen = touchScreenConfig; + mInputConfiguration.keyboard = keyboardConfig; + mInputConfiguration.navigation = navigationConfig; + } // release state lock +} + +void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) { + { // acquire state lock + AutoMutex _l(mStateLock); + + *outConfiguration = mInputConfiguration; + } // release state lock +} + +status_t InputReader::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) { + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); - device->multiTouchScreen.accumulator.pointers[pointerIndex].clear(); + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex < 0) { + return NAME_NOT_FOUND; } - } else if (rawEvent->scanCode == SYN_REPORT) { - // General Sync: The driver has returned all data for the current event update. - if (device->isMultiTouchScreen()) { - if (device->multiTouchScreen.accumulator.isDirty()) { - onMultiTouchScreenStateChanged(rawEvent->when, device); - device->multiTouchScreen.accumulator.clear(); + + InputDevice* device = mDevices.valueAt(deviceIndex); + if (device->isIgnored()) { + return NAME_NOT_FOUND; + } + + device->getDeviceInfo(outDeviceInfo); + return OK; + } // release device registy reader lock +} + +void InputReader::getInputDeviceIds(Vector<int32_t>& outDeviceIds) { + outDeviceIds.clear(); + + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + size_t numDevices = mDevices.size(); + for (size_t i = 0; i < numDevices; i++) { + InputDevice* device = mDevices.valueAt(i); + if (! device->isIgnored()) { + outDeviceIds.add(device->getId()); } - } else if (device->isSingleTouchScreen()) { - if (device->singleTouchScreen.accumulator.isDirty()) { - onSingleTouchScreenStateChanged(rawEvent->when, device); - device->singleTouchScreen.accumulator.clear(); + } + } // release device registy reader lock +} + +int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t keyCode) { + return getState(deviceId, sourceMask, keyCode, & InputDevice::getKeyCodeState); +} + +int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask, + int32_t scanCode) { + return getState(deviceId, sourceMask, scanCode, & InputDevice::getScanCodeState); +} + +int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) { + return getState(deviceId, sourceMask, switchCode, & InputDevice::getSwitchState); +} + +int32_t InputReader::getState(int32_t deviceId, uint32_t sourceMask, int32_t code, + GetStateFunc getStateFunc) { + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + int32_t result = AKEY_STATE_UNKNOWN; + if (deviceId >= 0) { + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + InputDevice* device = mDevices.valueAt(deviceIndex); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + result = (device->*getStateFunc)(sourceMask, code); + } + } + } else { + size_t numDevices = mDevices.size(); + for (size_t i = 0; i < numDevices; i++) { + InputDevice* device = mDevices.valueAt(i); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + result = (device->*getStateFunc)(sourceMask, code); + if (result >= AKEY_STATE_DOWN) { + return result; + } + } } } + return result; + } // release device registy reader lock +} + +bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask, + size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) { + memset(outFlags, 0, numCodes); + return markSupportedKeyCodes(deviceId, sourceMask, numCodes, keyCodes, outFlags); +} - if (device->trackball.accumulator.isDirty()) { - onTrackballStateChanged(rawEvent->when, device); - device->trackball.accumulator.clear(); +bool InputReader::markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + bool result = false; + if (deviceId >= 0) { + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + InputDevice* device = mDevices.valueAt(deviceIndex); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + result = device->markSupportedKeyCodes(sourceMask, + numCodes, keyCodes, outFlags); + } + } + } else { + size_t numDevices = mDevices.size(); + for (size_t i = 0; i < numDevices; i++) { + InputDevice* device = mDevices.valueAt(i); + if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { + result |= device->markSupportedKeyCodes(sourceMask, + numCodes, keyCodes, outFlags); + } + } } - } + return result; + } // release device registy reader lock } -void InputReader::handleKey(const RawEvent* rawEvent) { - InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); - if (! device) return; - bool down = rawEvent->value != 0; - int32_t scanCode = rawEvent->scanCode; +// --- InputReaderThread --- - if (device->isSingleTouchScreen()) { - switch (rawEvent->scanCode) { - case BTN_TOUCH: - device->singleTouchScreen.accumulator.fields |= - InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH; - device->singleTouchScreen.accumulator.btnTouch = down; - return; - } +InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) : + Thread(/*canCallJava*/ true), mReader(reader) { +} + +InputReaderThread::~InputReaderThread() { +} + +bool InputReaderThread::threadLoop() { + mReader->loopOnce(); + return true; +} + + +// --- InputDevice --- + +InputDevice::InputDevice(InputReaderContext* context, int32_t id, const String8& name) : + mContext(context), mId(id), mName(name), mSources(0) { +} + +InputDevice::~InputDevice() { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + delete mMappers[i]; } + mMappers.clear(); +} - if (device->isTrackball()) { - switch (rawEvent->scanCode) { - case BTN_MOUSE: - device->trackball.accumulator.fields |= - InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE; - device->trackball.accumulator.btnMouse = down; +void InputDevice::addMapper(InputMapper* mapper) { + mMappers.add(mapper); +} - // Process the trackball change now since we may not receive a sync immediately. - onTrackballStateChanged(rawEvent->when, device); - device->trackball.accumulator.clear(); - return; - } +void InputDevice::configure() { + mSources = 0; + + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->configure(); + mSources |= mapper->getSources(); + } +} + +void InputDevice::reset() { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->reset(); } +} - if (device->isKeyboard()) { - int32_t keyCode = rawEvent->keyCode; - onKey(rawEvent->when, device, down, keyCode, scanCode, rawEvent->flags); +void InputDevice::process(const RawEvent* rawEvent) { + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->process(rawEvent); } } -void InputReader::handleRelativeMotion(const RawEvent* rawEvent) { - InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); - if (! device) return; +void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) { + outDeviceInfo->initialize(mId, mName); - if (device->isTrackball()) { - switch (rawEvent->scanCode) { - case REL_X: - device->trackball.accumulator.fields |= - InputDevice::TrackballState::Accumulator::FIELD_REL_X; - device->trackball.accumulator.relX = rawEvent->value; - break; - case REL_Y: - device->trackball.accumulator.fields |= - InputDevice::TrackballState::Accumulator::FIELD_REL_Y; - device->trackball.accumulator.relY = rawEvent->value; - break; - } + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->populateDeviceInfo(outDeviceInfo); } } -void InputReader::handleAbsoluteMotion(const RawEvent* rawEvent) { - InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); - if (! device) return; +int32_t InputDevice::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + return getState(sourceMask, keyCode, & InputMapper::getKeyCodeState); +} - if (device->isMultiTouchScreen()) { - uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount; - InputDevice::MultiTouchScreenState::Accumulator::Pointer* pointer = - & device->multiTouchScreen.accumulator.pointers[pointerIndex]; +int32_t InputDevice::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + return getState(sourceMask, scanCode, & InputMapper::getScanCodeState); +} - switch (rawEvent->scanCode) { - case ABS_MT_POSITION_X: - pointer->fields |= - InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X; - pointer->absMTPositionX = rawEvent->value; - break; - case ABS_MT_POSITION_Y: - pointer->fields |= - InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y; - pointer->absMTPositionY = rawEvent->value; - break; - case ABS_MT_TOUCH_MAJOR: - pointer->fields |= - InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR; - pointer->absMTTouchMajor = rawEvent->value; - break; - case ABS_MT_TOUCH_MINOR: - pointer->fields |= - InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MINOR; - pointer->absMTTouchMinor = rawEvent->value; - break; - case ABS_MT_WIDTH_MAJOR: - pointer->fields |= - InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR; - pointer->absMTWidthMajor = rawEvent->value; - break; - case ABS_MT_WIDTH_MINOR: - pointer->fields |= - InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MINOR; - pointer->absMTWidthMinor = rawEvent->value; - break; - case ABS_MT_ORIENTATION: - pointer->fields |= - InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_ORIENTATION; - pointer->absMTOrientation = rawEvent->value; - break; - case ABS_MT_TRACKING_ID: - pointer->fields |= - InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TRACKING_ID; - pointer->absMTTrackingId = rawEvent->value; - break; +int32_t InputDevice::getSwitchState(uint32_t sourceMask, int32_t switchCode) { + return getState(sourceMask, switchCode, & InputMapper::getSwitchState); +} + +int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc) { + int32_t result = AKEY_STATE_UNKNOWN; + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + if (sourcesMatchMask(mapper->getSources(), sourceMask)) { + result = (mapper->*getStateFunc)(sourceMask, code); + if (result >= AKEY_STATE_DOWN) { + return result; + } } - } else if (device->isSingleTouchScreen()) { - switch (rawEvent->scanCode) { - case ABS_X: - device->singleTouchScreen.accumulator.fields |= - InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X; - device->singleTouchScreen.accumulator.absX = rawEvent->value; - break; - case ABS_Y: - device->singleTouchScreen.accumulator.fields |= - InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y; - device->singleTouchScreen.accumulator.absY = rawEvent->value; - break; - case ABS_PRESSURE: - device->singleTouchScreen.accumulator.fields |= - InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE; - device->singleTouchScreen.accumulator.absPressure = rawEvent->value; - break; - case ABS_TOOL_WIDTH: - device->singleTouchScreen.accumulator.fields |= - InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH; - device->singleTouchScreen.accumulator.absToolWidth = rawEvent->value; - break; + } + return result; +} + +bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + bool result = false; + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + if (sourcesMatchMask(mapper->getSources(), sourceMask)) { + result |= mapper->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags); } } + return result; +} + +int32_t InputDevice::getMetaState() { + int32_t result = 0; + size_t numMappers = mMappers.size(); + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + result |= mapper->getMetaState(); + } + return result; +} + + +// --- InputMapper --- + +InputMapper::InputMapper(InputDevice* device) : + mDevice(device), mContext(device->getContext()) { +} + +InputMapper::~InputMapper() { +} + +void InputMapper::populateDeviceInfo(InputDeviceInfo* info) { + info->addSource(getSources()); +} + +void InputMapper::configure() { +} + +void InputMapper::reset() { +} + +int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + return AKEY_STATE_UNKNOWN; +} + +int32_t InputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + return AKEY_STATE_UNKNOWN; +} + +int32_t InputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) { + return AKEY_STATE_UNKNOWN; +} + +bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + return false; +} + +int32_t InputMapper::getMetaState() { + return 0; +} + +bool InputMapper::applyStandardPolicyActions(nsecs_t when, int32_t policyActions) { + if (policyActions & InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING) { + getDispatcher()->notifyAppSwitchComing(when); + } + + return policyActions & InputReaderPolicyInterface::ACTION_DISPATCH; } -void InputReader::handleSwitch(const RawEvent* rawEvent) { - InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); - if (! device) return; - onSwitch(rawEvent->when, device, rawEvent->scanCode, rawEvent->value); +// --- SwitchInputMapper --- + +SwitchInputMapper::SwitchInputMapper(InputDevice* device) : + InputMapper(device) { } -void InputReader::onKey(nsecs_t when, InputDevice* device, - bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) { - /* Refresh display properties so we can rotate key codes according to display orientation */ +SwitchInputMapper::~SwitchInputMapper() { +} - if (! refreshDisplayProperties()) { - return; +uint32_t SwitchInputMapper::getSources() { + return 0; +} + +void SwitchInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_SW: + processSwitch(rawEvent->when, rawEvent->scanCode, rawEvent->value); + break; } +} - /* Update device state */ +void SwitchInputMapper::processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue) { + uint32_t policyFlags = 0; + int32_t policyActions = getPolicy()->interceptSwitch( + when, switchCode, switchValue, policyFlags); - int32_t oldMetaState = device->keyboard.current.metaState; - int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState); - if (oldMetaState != newMetaState) { - device->keyboard.current.metaState = newMetaState; - resetGlobalMetaState(); + applyStandardPolicyActions(when, policyActions); +} + +int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) { + return getEventHub()->getSwitchState(getDeviceId(), switchCode); +} + + +// --- KeyboardInputMapper --- + +KeyboardInputMapper::KeyboardInputMapper(InputDevice* device, int32_t associatedDisplayId, + uint32_t sources, int32_t keyboardType) : + InputMapper(device), mAssociatedDisplayId(associatedDisplayId), mSources(sources), + mKeyboardType(keyboardType) { + initialize(); +} + +KeyboardInputMapper::~KeyboardInputMapper() { +} + +void KeyboardInputMapper::initialize() { + mMetaState = AMETA_NONE; + mDownTime = 0; +} + +uint32_t KeyboardInputMapper::getSources() { + return mSources; +} + +void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + info->setKeyboardType(mKeyboardType); +} + +void KeyboardInputMapper::reset() { + // Synthesize key up event on reset if keys are currently down. + while (! mKeyDowns.isEmpty()) { + const KeyDown& keyDown = mKeyDowns.top(); + nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC); + processKey(when, false, keyDown.keyCode, keyDown.scanCode, 0); + } + + InputMapper::reset(); + + // Reinitialize. + initialize(); + getContext()->updateGlobalMetaState(); +} + +void KeyboardInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_KEY: { + int32_t scanCode = rawEvent->scanCode; + if (isKeyboardOrGamepadKey(scanCode)) { + processKey(rawEvent->when, rawEvent->value != 0, rawEvent->keyCode, scanCode, + rawEvent->flags); + } + break; + } } +} - // FIXME if we send a down event about a rotated key press we should ensure that we send - // a corresponding up event about the rotated key press even if the orientation - // has changed in the meantime - keyCode = rotateKeyCode(keyCode, mDisplayOrientation); +bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) { + return scanCode < BTN_MOUSE + || scanCode >= KEY_OK + || (scanCode >= BTN_GAMEPAD && scanCode < BTN_DIGI); +} +void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode, + uint32_t policyFlags) { if (down) { - device->keyboard.current.downTime = when; + // Rotate key codes according to orientation. + if (mAssociatedDisplayId >= 0) { + int32_t orientation; + if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) { + return; + } + + keyCode = rotateKeyCode(keyCode, orientation); + } + + // Add key down. + ssize_t keyDownIndex = findKeyDown(scanCode); + if (keyDownIndex >= 0) { + // key repeat, be sure to use same keycode as before in case of rotation + keyCode = mKeyDowns.top().keyCode; + } else { + // key down + mKeyDowns.push(); + KeyDown& keyDown = mKeyDowns.editTop(); + keyDown.keyCode = keyCode; + keyDown.scanCode = scanCode; + } + } else { + // Remove key down. + ssize_t keyDownIndex = findKeyDown(scanCode); + if (keyDownIndex >= 0) { + // key up, be sure to use same keycode as before in case of rotation + keyCode = mKeyDowns.top().keyCode; + mKeyDowns.removeAt(size_t(keyDownIndex)); + } else { + // key was not actually down + LOGI("Dropping key up from device %s because the key was not down. " + "keyCode=%d, scanCode=%d", + getDeviceName().string(), keyCode, scanCode); + return; + } } - /* Apply policy */ + int32_t oldMetaState = mMetaState; + int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState); + if (oldMetaState != newMetaState) { + mMetaState = newMetaState; + getContext()->updateGlobalMetaState(); + } - int32_t policyActions = mPolicy->interceptKey(when, device->id, - down, keyCode, scanCode, policyFlags); + /* Apply policy. */ - if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) { + int32_t policyActions = getPolicy()->interceptKey(when, + getDeviceId(), down, keyCode, scanCode, policyFlags); + + if (! applyStandardPolicyActions(when, policyActions)) { return; // event dropped } - /* Enqueue key event for dispatch */ + /* Enqueue key event for dispatch. */ int32_t keyEventAction; if (down) { - device->keyboard.current.downTime = when; + mDownTime = when; keyEventAction = AKEY_EVENT_ACTION_DOWN; } else { keyEventAction = AKEY_EVENT_ACTION_UP; } int32_t keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM; - if (policyActions & InputReaderPolicyInterface::ACTION_WOKE_HERE) { + if (policyFlags & POLICY_FLAG_WOKE_HERE) { keyEventFlags = keyEventFlags | AKEY_EVENT_FLAG_WOKE_HERE; } - mDispatcher->notifyKey(when, device->id, AINPUT_SOURCE_KEYBOARD, policyFlags, + getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags, keyEventAction, keyEventFlags, keyCode, scanCode, - device->keyboard.current.metaState, - device->keyboard.current.downTime); + mMetaState, mDownTime); } -void InputReader::onSwitch(nsecs_t when, InputDevice* device, int32_t switchCode, - int32_t switchValue) { - int32_t policyActions = mPolicy->interceptSwitch(when, switchCode, switchValue); +ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) { + size_t n = mKeyDowns.size(); + for (size_t i = 0; i < n; i++) { + if (mKeyDowns[i].scanCode == scanCode) { + return i; + } + } + return -1; +} - uint32_t policyFlags = 0; - applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags); +int32_t KeyboardInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + return getEventHub()->getKeyCodeState(getDeviceId(), keyCode); } -void InputReader::onMultiTouchScreenStateChanged(nsecs_t when, - InputDevice* device) { - static const uint32_t REQUIRED_FIELDS = - InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X - | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y - | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR - | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR; +int32_t KeyboardInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + return getEventHub()->getScanCodeState(getDeviceId(), scanCode); +} - /* Refresh display properties so we can map touch screen coords into display coords */ +bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + return getEventHub()->markSupportedKeyCodes(getDeviceId(), numCodes, keyCodes, outFlags); +} - if (! refreshDisplayProperties()) { - return; +int32_t KeyboardInputMapper::getMetaState() { + return mMetaState; +} + + +// --- TrackballInputMapper --- + +TrackballInputMapper::TrackballInputMapper(InputDevice* device, int32_t associatedDisplayId) : + InputMapper(device), mAssociatedDisplayId(associatedDisplayId) { + mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + + initialize(); +} + +TrackballInputMapper::~TrackballInputMapper() { +} + +uint32_t TrackballInputMapper::getSources() { + return AINPUT_SOURCE_TRACKBALL; +} + +void TrackballInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + info->addMotionRange(AINPUT_MOTION_RANGE_X, -1.0f, 1.0f, 0.0f, mXScale); + info->addMotionRange(AINPUT_MOTION_RANGE_Y, -1.0f, 1.0f, 0.0f, mYScale); +} + +void TrackballInputMapper::initialize() { + mAccumulator.clear(); + + mDown = false; + mDownTime = 0; +} + +void TrackballInputMapper::reset() { + // Synthesize trackball button up event on reset if trackball button is currently down. + if (mDown) { + nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC); + mAccumulator.fields |= Accumulator::FIELD_BTN_MOUSE; + mAccumulator.btnMouse = false; + sync(when); } - /* Update device state */ + InputMapper::reset(); - InputDevice::MultiTouchScreenState* in = & device->multiTouchScreen; - InputDevice::TouchData* out = & device->touchScreen.currentTouch; + // Reinitialize. + initialize(); +} - uint32_t inCount = in->accumulator.pointerCount; - uint32_t outCount = 0; - bool havePointerIds = true; +void TrackballInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_KEY: + switch (rawEvent->scanCode) { + case BTN_MOUSE: + mAccumulator.fields |= Accumulator::FIELD_BTN_MOUSE; + mAccumulator.btnMouse = rawEvent->value != 0; - out->clear(); + sync(rawEvent->when); + mAccumulator.clear(); + break; + } + break; - for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) { - uint32_t fields = in->accumulator.pointers[inIndex].fields; + case EV_REL: + switch (rawEvent->scanCode) { + case REL_X: + mAccumulator.fields |= Accumulator::FIELD_REL_X; + mAccumulator.relX = rawEvent->value; + break; + case REL_Y: + mAccumulator.fields |= Accumulator::FIELD_REL_Y; + mAccumulator.relY = rawEvent->value; + break; + } + break; - if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) { -#if DEBUG_POINTERS - LOGD("Pointers: Missing required multitouch pointer fields: index=%d, fields=%d", - inIndex, fields); - continue; -#endif + case EV_SYN: + switch (rawEvent->scanCode) { + case SYN_REPORT: + if (mAccumulator.isDirty()) { + sync(rawEvent->when); + mAccumulator.clear(); + } + break; } + break; + } +} - if (in->accumulator.pointers[inIndex].absMTTouchMajor <= 0) { - // Pointer is not down. Drop it. - continue; +void TrackballInputMapper::sync(nsecs_t when) { + /* Get display properties so for rotation based on display orientation. */ + + int32_t orientation; + if (mAssociatedDisplayId >= 0) { + if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) { + return; } + } else { + orientation = InputReaderPolicyInterface::ROTATION_0; + } - out->pointers[outCount].x = in->accumulator.pointers[inIndex].absMTPositionX; - out->pointers[outCount].y = in->accumulator.pointers[inIndex].absMTPositionY; + /* Update saved trackball state */ - out->pointers[outCount].touchMajor = in->accumulator.pointers[inIndex].absMTTouchMajor; - out->pointers[outCount].touchMinor = (fields - & InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MINOR) != 0 - ? in->accumulator.pointers[inIndex].absMTTouchMinor - : in->accumulator.pointers[inIndex].absMTTouchMajor; + uint32_t fields = mAccumulator.fields; + bool downChanged = fields & Accumulator::FIELD_BTN_MOUSE; - out->pointers[outCount].toolMajor = in->accumulator.pointers[inIndex].absMTWidthMajor; - out->pointers[outCount].toolMinor = (fields - & InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MINOR) != 0 - ? in->accumulator.pointers[inIndex].absMTWidthMinor - : in->accumulator.pointers[inIndex].absMTWidthMajor; + if (downChanged) { + if (mAccumulator.btnMouse) { + mDown = true; + mDownTime = when; + } else { + mDown = false; + } + } - out->pointers[outCount].orientation = (fields - & InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_ORIENTATION) != 0 - ? in->accumulator.pointers[inIndex].absMTOrientation : 0; + /* Apply policy */ - // Derive an approximation of pressure and size. - // FIXME assignment of pressure may be incorrect, probably better to let - // pressure = touch / width. Later on we pass width to MotionEvent as a size, which - // isn't quite right either. Should be using touch for that. - out->pointers[outCount].pressure = in->accumulator.pointers[inIndex].absMTTouchMajor; - out->pointers[outCount].size = in->accumulator.pointers[inIndex].absMTWidthMajor; + uint32_t policyFlags = 0; + int32_t policyActions = getPolicy()->interceptGeneric(when, policyFlags); - if (havePointerIds) { - if (fields & InputDevice::MultiTouchScreenState::Accumulator:: - FIELD_ABS_MT_TRACKING_ID) { - uint32_t id = uint32_t(in->accumulator.pointers[inIndex].absMTTrackingId); + if (! applyStandardPolicyActions(when, policyActions)) { + return; // event dropped + } - if (id > MAX_POINTER_ID) { -#if DEBUG_POINTERS - LOGD("Pointers: Ignoring driver provided pointer id %d because " - "it is larger than max supported id %d for optimizations", - id, MAX_POINTER_ID); -#endif - havePointerIds = false; - } - else { - out->pointers[outCount].id = id; - out->idToIndex[id] = outCount; - out->idBits.markBit(id); - } - } else { - havePointerIds = false; - } - } + /* Enqueue motion event for dispatch. */ - outCount += 1; + int32_t motionEventAction; + if (downChanged) { + motionEventAction = mDown ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP; + } else { + motionEventAction = AMOTION_EVENT_ACTION_MOVE; + } + + int32_t pointerId = 0; + PointerCoords pointerCoords; + pointerCoords.x = fields & Accumulator::FIELD_REL_X + ? mAccumulator.relX * mXScale : 0; + pointerCoords.y = fields & Accumulator::FIELD_REL_Y + ? mAccumulator.relY * mYScale : 0; + pointerCoords.pressure = 1.0f; // XXX Consider making this 1.0f if down, 0 otherwise. + pointerCoords.size = 0; + pointerCoords.touchMajor = 0; + pointerCoords.touchMinor = 0; + pointerCoords.toolMajor = 0; + pointerCoords.toolMinor = 0; + pointerCoords.orientation = 0; + + float temp; + switch (orientation) { + case InputReaderPolicyInterface::ROTATION_90: + temp = pointerCoords.x; + pointerCoords.x = pointerCoords.y; + pointerCoords.y = - temp; + break; + + case InputReaderPolicyInterface::ROTATION_180: + pointerCoords.x = - pointerCoords.x; + pointerCoords.y = - pointerCoords.y; + break; + + case InputReaderPolicyInterface::ROTATION_270: + temp = pointerCoords.x; + pointerCoords.x = - pointerCoords.y; + pointerCoords.y = temp; + break; } - out->pointerCount = outCount; + int32_t metaState = mContext->getGlobalMetaState(); + getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TRACKBALL, policyFlags, + motionEventAction, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + 1, & pointerId, & pointerCoords, mXPrecision, mYPrecision, mDownTime); +} + - onTouchScreenChanged(when, device, havePointerIds); +// --- TouchInputMapper --- + +TouchInputMapper::TouchInputMapper(InputDevice* device, int32_t associatedDisplayId) : + InputMapper(device), mAssociatedDisplayId(associatedDisplayId), + mSurfaceOrientation(-1), mSurfaceWidth(-1), mSurfaceHeight(-1) { + initialize(); } -void InputReader::onSingleTouchScreenStateChanged(nsecs_t when, - InputDevice* device) { - /* Refresh display properties so we can map touch screen coords into display coords */ +TouchInputMapper::~TouchInputMapper() { +} - if (! refreshDisplayProperties()) { - return; +uint32_t TouchInputMapper::getSources() { + return mAssociatedDisplayId >= 0 ? AINPUT_SOURCE_TOUCHSCREEN : AINPUT_SOURCE_TOUCHPAD; +} + +void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { + InputMapper::populateDeviceInfo(info); + + // FIXME: Should ensure the surface information is up to date so that orientation changes + // are noticed immediately. Unfortunately we will need to add some extra locks here + // to prevent race conditions. + // configureSurface(); + + info->addMotionRange(AINPUT_MOTION_RANGE_X, mOrientedRanges.x); + info->addMotionRange(AINPUT_MOTION_RANGE_Y, mOrientedRanges.y); + info->addMotionRange(AINPUT_MOTION_RANGE_PRESSURE, mOrientedRanges.pressure); + info->addMotionRange(AINPUT_MOTION_RANGE_SIZE, mOrientedRanges.size); + info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MAJOR, mOrientedRanges.touchMajor); + info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MINOR, mOrientedRanges.touchMinor); + info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MAJOR, mOrientedRanges.toolMajor); + info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MINOR, mOrientedRanges.toolMinor); + info->addMotionRange(AINPUT_MOTION_RANGE_ORIENTATION, mOrientedRanges.orientation); +} + +void TouchInputMapper::initialize() { + mLastTouch.clear(); + mDownTime = 0; + mCurrentVirtualKey.down = false; + + for (uint32_t i = 0; i < MAX_POINTERS; i++) { + mAveragingTouchFilter.historyStart[i] = 0; + mAveragingTouchFilter.historyEnd[i] = 0; } - /* Update device state */ + mJumpyTouchFilter.jumpyPointsDropped = 0; +} - InputDevice::SingleTouchScreenState* in = & device->singleTouchScreen; - InputDevice::TouchData* out = & device->touchScreen.currentTouch; +void TouchInputMapper::configure() { + InputMapper::configure(); - uint32_t fields = in->accumulator.fields; + // Configure basic parameters. + mParameters.useBadTouchFilter = getPolicy()->filterTouchEvents(); + mParameters.useAveragingTouchFilter = getPolicy()->filterTouchEvents(); + mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents(); - if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH) { - in->current.down = in->accumulator.btnTouch; + // Configure absolute axis information. + configureAxes(); + + // Configure pressure factors. + if (mAxes.pressure.valid) { + mPressureOrigin = mAxes.pressure.minValue; + mPressureScale = 1.0f / mAxes.pressure.getRange(); + } else { + mPressureOrigin = 0; + mPressureScale = 1.0f; } - if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X) { - in->current.x = in->accumulator.absX; + mOrientedRanges.pressure.min = 0.0f; + mOrientedRanges.pressure.max = 1.0f; + mOrientedRanges.pressure.flat = 0.0f; + mOrientedRanges.pressure.fuzz = mPressureScale; + + // Configure size factors. + if (mAxes.size.valid) { + mSizeOrigin = mAxes.size.minValue; + mSizeScale = 1.0f / mAxes.size.getRange(); + } else { + mSizeOrigin = 0; + mSizeScale = 1.0f; } - if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y) { - in->current.y = in->accumulator.absY; + mOrientedRanges.size.min = 0.0f; + mOrientedRanges.size.max = 1.0f; + mOrientedRanges.size.flat = 0.0f; + mOrientedRanges.size.fuzz = mSizeScale; + + // Configure orientation factors. + if (mAxes.orientation.valid && mAxes.orientation.maxValue > 0) { + mOrientationScale = float(M_PI_2) / mAxes.orientation.maxValue; + } else { + mOrientationScale = 0.0f; } - if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE) { - in->current.pressure = in->accumulator.absPressure; + mOrientedRanges.orientation.min = - M_PI_2; + mOrientedRanges.orientation.max = M_PI_2; + mOrientedRanges.orientation.flat = 0; + mOrientedRanges.orientation.fuzz = mOrientationScale; + + // Configure surface dimensions and orientation. + configureSurface(); +} + +void TouchInputMapper::configureAxes() { + mAxes.x.valid = false; + mAxes.y.valid = false; + mAxes.pressure.valid = false; + mAxes.size.valid = false; + mAxes.touchMajor.valid = false; + mAxes.touchMinor.valid = false; + mAxes.toolMajor.valid = false; + mAxes.toolMinor.valid = false; + mAxes.orientation.valid = false; +} + +bool TouchInputMapper::configureSurface() { + // Update orientation and dimensions if needed. + int32_t orientation; + int32_t width, height; + if (mAssociatedDisplayId >= 0) { + if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, & width, & height, & orientation)) { + return false; + } + } else { + orientation = InputReaderPolicyInterface::ROTATION_0; + width = mAxes.x.getRange(); + height = mAxes.y.getRange(); } - if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH) { - in->current.size = in->accumulator.absToolWidth; + bool orientationChanged = mSurfaceOrientation != orientation; + if (orientationChanged) { + mSurfaceOrientation = orientation; } - out->clear(); + bool sizeChanged = mSurfaceWidth != width || mSurfaceHeight != height; + if (sizeChanged) { + mSurfaceWidth = width; + mSurfaceHeight = height; + + // Compute size-dependent translation and scaling factors and place virtual keys. + if (mAxes.x.valid && mAxes.y.valid) { + mXOrigin = mAxes.x.minValue; + mYOrigin = mAxes.y.minValue; + + LOGI("Device configured: id=0x%x, name=%s (display size was changed)", + getDeviceId(), getDeviceName().string()); + + mXScale = float(width) / mAxes.x.getRange(); + mYScale = float(height) / mAxes.y.getRange(); + mXPrecision = 1.0f / mXScale; + mYPrecision = 1.0f / mYScale; + + configureVirtualKeys(); + } else { + mXOrigin = 0; + mYOrigin = 0; + mXScale = 1.0f; + mYScale = 1.0f; + mXPrecision = 1.0f; + mYPrecision = 1.0f; + } + + // Configure touch and tool area ranges. + float diagonal = sqrt(float(width * width + height * height)); + float diagonalFuzz = sqrt(mXScale * mXScale + mYScale * mYScale); + + mOrientedRanges.touchMajor.min = 0.0f; + mOrientedRanges.touchMajor.max = diagonal; + mOrientedRanges.touchMajor.flat = 0.0f; + mOrientedRanges.touchMajor.fuzz = diagonalFuzz; + mOrientedRanges.touchMinor = mOrientedRanges.touchMajor; + + mOrientedRanges.toolMinor = mOrientedRanges.toolMajor = mOrientedRanges.touchMajor; + } + + if (orientationChanged || sizeChanged) { + // Compute oriented surface dimensions, precision, and scales. + float orientedXScale, orientedYScale; + switch (mSurfaceOrientation) { + case InputReaderPolicyInterface::ROTATION_90: + case InputReaderPolicyInterface::ROTATION_270: + mOrientedSurfaceWidth = mSurfaceHeight; + mOrientedSurfaceHeight = mSurfaceWidth; + mOrientedXPrecision = mYPrecision; + mOrientedYPrecision = mXPrecision; + orientedXScale = mYScale; + orientedYScale = mXScale; + break; + default: + mOrientedSurfaceWidth = mSurfaceWidth; + mOrientedSurfaceHeight = mSurfaceHeight; + mOrientedXPrecision = mXPrecision; + mOrientedYPrecision = mYPrecision; + orientedXScale = mXScale; + orientedYScale = mYScale; + break; + } + + // Configure position ranges. + mOrientedRanges.x.min = 0; + mOrientedRanges.x.max = mOrientedSurfaceWidth; + mOrientedRanges.x.flat = 0; + mOrientedRanges.x.fuzz = orientedXScale; - if (in->current.down) { - out->pointerCount = 1; - out->pointers[0].id = 0; - out->pointers[0].x = in->current.x; - out->pointers[0].y = in->current.y; - out->pointers[0].pressure = in->current.pressure; - out->pointers[0].size = in->current.size; - out->pointers[0].touchMajor = in->current.pressure; - out->pointers[0].touchMinor = in->current.pressure; - out->pointers[0].toolMajor = in->current.size; - out->pointers[0].toolMinor = in->current.size; - out->pointers[0].orientation = 0; - out->idToIndex[0] = 0; - out->idBits.markBit(0); + mOrientedRanges.y.min = 0; + mOrientedRanges.y.max = mOrientedSurfaceHeight; + mOrientedRanges.y.flat = 0; + mOrientedRanges.y.fuzz = orientedYScale; } - onTouchScreenChanged(when, device, true); + return true; } -void InputReader::onTouchScreenChanged(nsecs_t when, - InputDevice* device, bool havePointerIds) { - /* Apply policy */ +void TouchInputMapper::configureVirtualKeys() { + assert(mAxes.x.valid && mAxes.y.valid); + + Vector<InputReaderPolicyInterface::VirtualKeyDefinition> virtualKeyDefinitions; + getPolicy()->getVirtualKeyDefinitions(getDeviceName(), virtualKeyDefinitions); + + { // acquire virtual key lock + AutoMutex _l(mVirtualKeyLock); + + mVirtualKeys.clear(); + + if (virtualKeyDefinitions.size() == 0) { + return; + } + + mVirtualKeys.setCapacity(virtualKeyDefinitions.size()); + + int32_t touchScreenLeft = mAxes.x.minValue; + int32_t touchScreenTop = mAxes.y.minValue; + int32_t touchScreenWidth = mAxes.x.getRange(); + int32_t touchScreenHeight = mAxes.y.getRange(); - int32_t policyActions = mPolicy->interceptTouch(when); + for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) { + const InputReaderPolicyInterface::VirtualKeyDefinition& virtualKeyDefinition = + virtualKeyDefinitions[i]; + + mVirtualKeys.add(); + VirtualKey& virtualKey = mVirtualKeys.editTop(); + + virtualKey.scanCode = virtualKeyDefinition.scanCode; + int32_t keyCode; + uint32_t flags; + if (getEventHub()->scancodeToKeycode(getDeviceId(), virtualKey.scanCode, + & keyCode, & flags)) { + LOGW(" VirtualKey %d: could not obtain key code, ignoring", virtualKey.scanCode); + mVirtualKeys.pop(); // drop the key + continue; + } + + virtualKey.keyCode = keyCode; + virtualKey.flags = flags; + + // convert the key definition's display coordinates into touch coordinates for a hit box + int32_t halfWidth = virtualKeyDefinition.width / 2; + int32_t halfHeight = virtualKeyDefinition.height / 2; + + virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth) + * touchScreenWidth / mSurfaceWidth + touchScreenLeft; + virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth) + * touchScreenWidth / mSurfaceWidth + touchScreenLeft; + virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight) + * touchScreenHeight / mSurfaceHeight + touchScreenTop; + virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight) + * touchScreenHeight / mSurfaceHeight + touchScreenTop; + + LOGI(" VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d", + virtualKey.scanCode, virtualKey.keyCode, + virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom); + } + } // release virtual key lock +} + +void TouchInputMapper::reset() { + // Synthesize touch up event if touch is currently down. + // This will also take care of finishing virtual key processing if needed. + if (mLastTouch.pointerCount != 0) { + nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC); + mCurrentTouch.clear(); + syncTouch(when, true); + } + + InputMapper::reset(); + + // Reinitialize. + initialize(); +} + +void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) { + /* Refresh associated display information and update our size configuration if needed. */ + + if (! configureSurface()) { + return; + } + + /* Apply policy */ uint32_t policyFlags = 0; - if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) { - device->touchScreen.lastTouch.clear(); + int32_t policyActions = getPolicy()->interceptGeneric(when, policyFlags); + + if (! applyStandardPolicyActions(when, policyActions)) { + mLastTouch.clear(); return; // event dropped } /* Preprocess pointer data */ - if (device->touchScreen.parameters.useBadTouchFilter) { - if (device->touchScreen.applyBadTouchFilter()) { + if (mParameters.useBadTouchFilter) { + if (applyBadTouchFilter()) { havePointerIds = false; } } - if (device->touchScreen.parameters.useJumpyTouchFilter) { - if (device->touchScreen.applyJumpyTouchFilter()) { + if (mParameters.useJumpyTouchFilter) { + if (applyJumpyTouchFilter()) { havePointerIds = false; } } if (! havePointerIds) { - device->touchScreen.calculatePointerIds(); + calculatePointerIds(); } - InputDevice::TouchData temp; - InputDevice::TouchData* savedTouch; - if (device->touchScreen.parameters.useAveragingTouchFilter) { - temp.copyFrom(device->touchScreen.currentTouch); + TouchData temp; + TouchData* savedTouch; + if (mParameters.useAveragingTouchFilter) { + temp.copyFrom(mCurrentTouch); savedTouch = & temp; - device->touchScreen.applyAveragingTouchFilter(); + applyAveragingTouchFilter(); } else { - savedTouch = & device->touchScreen.currentTouch; + savedTouch = & mCurrentTouch; } - /* Process virtual keys or touches */ + /* Process touches and virtual keys */ - if (! consumeVirtualKeyTouches(when, device, policyFlags)) { - dispatchTouches(when, device, policyFlags); + TouchResult touchResult = consumeOffScreenTouches(when, policyFlags); + if (touchResult == DISPATCH_TOUCH) { + dispatchTouches(when, policyFlags); } - // Copy current touch to last touch in preparation for the next cycle. - device->touchScreen.lastTouch.copyFrom(*savedTouch); + /* Copy current touch to last touch in preparation for the next cycle. */ + + if (touchResult == DROP_STROKE) { + mLastTouch.clear(); + } else { + mLastTouch.copyFrom(*savedTouch); + } } -bool InputReader::consumeVirtualKeyTouches(nsecs_t when, - InputDevice* device, uint32_t policyFlags) { - switch (device->touchScreen.currentVirtualKey.status) { - case InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_CANCELED: - if (device->touchScreen.currentTouch.pointerCount == 0) { - // Pointer went up after virtual key canceled. - device->touchScreen.currentVirtualKey.status = - InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_UP; - } - return true; // consumed +TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches( + nsecs_t when, uint32_t policyFlags) { + int32_t keyEventAction, keyEventFlags; + int32_t keyCode, scanCode, downTime; + TouchResult touchResult; + + { // acquire virtual key lock + AutoMutex _l(mVirtualKeyLock); - case InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_DOWN: - if (device->touchScreen.currentTouch.pointerCount == 0) { - // Pointer went up while virtual key was down. - device->touchScreen.currentVirtualKey.status = - InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_UP; + if (mCurrentVirtualKey.down) { + if (mCurrentTouch.pointerCount == 0) { + // Pointer went up while virtual key was down. + mCurrentVirtualKey.down = false; #if DEBUG_VIRTUAL_KEYS - LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d", - device->touchScreen.currentVirtualKey.keyCode, - device->touchScreen.currentVirtualKey.scanCode); + LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); #endif - dispatchVirtualKey(when, device, policyFlags, AKEY_EVENT_ACTION_UP, - AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY); - return true; // consumed - } + keyEventAction = AKEY_EVENT_ACTION_UP; + keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY; + touchResult = SKIP_TOUCH; + goto DispatchVirtualKey; + } - if (device->touchScreen.currentTouch.pointerCount == 1) { - const InputDevice::VirtualKey* virtualKey = device->touchScreen.findVirtualKeyHit(); - if (virtualKey - && virtualKey->keyCode == device->touchScreen.currentVirtualKey.keyCode) { - // Pointer is still within the space of the virtual key. - return true; // consumed + if (mCurrentTouch.pointerCount == 1) { + int32_t x = mCurrentTouch.pointers[0].x; + int32_t y = mCurrentTouch.pointers[0].y; + const VirtualKey* virtualKey = findVirtualKeyHitLvk(x, y); + if (virtualKey && virtualKey->keyCode == mCurrentVirtualKey.keyCode) { + // Pointer is still within the space of the virtual key. + return SKIP_TOUCH; + } } - } - // Pointer left virtual key area or another pointer also went down. - // Send key cancellation. - device->touchScreen.currentVirtualKey.status = - InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_CANCELED; + // Pointer left virtual key area or another pointer also went down. + // Send key cancellation and drop the stroke so subsequent motions will be + // considered fresh downs. This is useful when the user swipes away from the + // virtual key area into the main display surface. + mCurrentVirtualKey.down = false; #if DEBUG_VIRTUAL_KEYS - LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", - device->touchScreen.currentVirtualKey.keyCode, - device->touchScreen.currentVirtualKey.scanCode); + LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); #endif - dispatchVirtualKey(when, device, policyFlags, AKEY_EVENT_ACTION_UP, - AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY - | AKEY_EVENT_FLAG_CANCELED); - return true; // consumed - - default: - if (device->touchScreen.currentTouch.pointerCount == 1 - && device->touchScreen.lastTouch.pointerCount == 0) { - // Pointer just went down. Check for virtual key hit. - const InputDevice::VirtualKey* virtualKey = device->touchScreen.findVirtualKeyHit(); - if (virtualKey) { - device->touchScreen.currentVirtualKey.status = - InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_DOWN; - device->touchScreen.currentVirtualKey.downTime = when; - device->touchScreen.currentVirtualKey.keyCode = virtualKey->keyCode; - device->touchScreen.currentVirtualKey.scanCode = virtualKey->scanCode; + keyEventAction = AKEY_EVENT_ACTION_UP; + keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY + | AKEY_EVENT_FLAG_CANCELED; + touchResult = DROP_STROKE; + goto DispatchVirtualKey; + } else { + if (mCurrentTouch.pointerCount >= 1 && mLastTouch.pointerCount == 0) { + // Pointer just went down. Handle off-screen touches, if needed. + int32_t x = mCurrentTouch.pointers[0].x; + int32_t y = mCurrentTouch.pointers[0].y; + if (! isPointInsideSurface(x, y)) { + // If exactly one pointer went down, check for virtual key hit. + // Otherwise we will drop the entire stroke. + if (mCurrentTouch.pointerCount == 1) { + const VirtualKey* virtualKey = findVirtualKeyHitLvk(x, y); + if (virtualKey) { + mCurrentVirtualKey.down = true; + mCurrentVirtualKey.downTime = when; + mCurrentVirtualKey.keyCode = virtualKey->keyCode; + mCurrentVirtualKey.scanCode = virtualKey->scanCode; #if DEBUG_VIRTUAL_KEYS - LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d", - device->touchScreen.currentVirtualKey.keyCode, - device->touchScreen.currentVirtualKey.scanCode); + LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d", + mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode); #endif - dispatchVirtualKey(when, device, policyFlags, AKEY_EVENT_ACTION_DOWN, - AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY); - return true; // consumed + keyEventAction = AKEY_EVENT_ACTION_DOWN; + keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM + | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY; + touchResult = SKIP_TOUCH; + goto DispatchVirtualKey; + } + } + return DROP_STROKE; + } } + return DISPATCH_TOUCH; } - return false; // not consumed - } -} -void InputReader::dispatchVirtualKey(nsecs_t when, - InputDevice* device, uint32_t policyFlags, - int32_t keyEventAction, int32_t keyEventFlags) { - updateExportedVirtualKeyState(); + DispatchVirtualKey: + // Collect remaining state needed to dispatch virtual key. + keyCode = mCurrentVirtualKey.keyCode; + scanCode = mCurrentVirtualKey.scanCode; + downTime = mCurrentVirtualKey.downTime; + } // release virtual key lock - int32_t keyCode = device->touchScreen.currentVirtualKey.keyCode; - int32_t scanCode = device->touchScreen.currentVirtualKey.scanCode; - nsecs_t downTime = device->touchScreen.currentVirtualKey.downTime; - int32_t metaState = globalMetaState(); + // Dispatch virtual key. + int32_t metaState = mContext->getGlobalMetaState(); if (keyEventAction == AKEY_EVENT_ACTION_DOWN) { - mPolicy->virtualKeyDownFeedback(); + getPolicy()->virtualKeyDownFeedback(); } - int32_t policyActions = mPolicy->interceptKey(when, device->id, + int32_t policyActions = getPolicy()->interceptKey(when, getDeviceId(), keyEventAction == AKEY_EVENT_ACTION_DOWN, keyCode, scanCode, policyFlags); - if (applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) { - mDispatcher->notifyKey(when, device->id, AINPUT_SOURCE_KEYBOARD, policyFlags, + if (applyStandardPolicyActions(when, policyActions)) { + getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags, keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime); } + return touchResult; } -void InputReader::dispatchTouches(nsecs_t when, - InputDevice* device, uint32_t policyFlags) { - uint32_t currentPointerCount = device->touchScreen.currentTouch.pointerCount; - uint32_t lastPointerCount = device->touchScreen.lastTouch.pointerCount; +void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) { + uint32_t currentPointerCount = mCurrentTouch.pointerCount; + uint32_t lastPointerCount = mLastTouch.pointerCount; if (currentPointerCount == 0 && lastPointerCount == 0) { return; // nothing to do! } - BitSet32 currentIdBits = device->touchScreen.currentTouch.idBits; - BitSet32 lastIdBits = device->touchScreen.lastTouch.idBits; + BitSet32 currentIdBits = mCurrentTouch.idBits; + BitSet32 lastIdBits = mLastTouch.idBits; if (currentIdBits == lastIdBits) { // No pointer id changes so this is a move event. // The dispatcher takes care of batching moves so we don't have to deal with that here. int32_t motionEventAction = AMOTION_EVENT_ACTION_MOVE; - dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch, + dispatchTouch(when, policyFlags, & mCurrentTouch, currentIdBits, -1, motionEventAction); } else { // There may be pointers going up and pointers going down at the same time when pointer @@ -791,7 +1536,7 @@ void InputReader::dispatchTouches(nsecs_t when, motionEventAction = AMOTION_EVENT_ACTION_POINTER_UP; } - dispatchTouch(when, device, policyFlags, & device->touchScreen.lastTouch, + dispatchTouch(when, policyFlags, & mLastTouch, oldActiveIdBits, upId, motionEventAction); } @@ -804,40 +1549,24 @@ void InputReader::dispatchTouches(nsecs_t when, int32_t motionEventAction; if (oldActiveIdBits.isEmpty()) { motionEventAction = AMOTION_EVENT_ACTION_DOWN; - device->touchScreen.downTime = when; + mDownTime = when; } else { motionEventAction = AMOTION_EVENT_ACTION_POINTER_DOWN; } - dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch, + dispatchTouch(when, policyFlags, & mCurrentTouch, activeIdBits, downId, motionEventAction); } } } -void InputReader::dispatchTouch(nsecs_t when, InputDevice* device, uint32_t policyFlags, - InputDevice::TouchData* touch, BitSet32 idBits, uint32_t changedId, +void TouchInputMapper::dispatchTouch(nsecs_t when, uint32_t policyFlags, + TouchData* touch, BitSet32 idBits, uint32_t changedId, int32_t motionEventAction) { - int32_t orientedWidth, orientedHeight; - switch (mDisplayOrientation) { - case InputReaderPolicyInterface::ROTATION_90: - case InputReaderPolicyInterface::ROTATION_270: - orientedWidth = mDisplayHeight; - orientedHeight = mDisplayWidth; - break; - default: - orientedWidth = mDisplayWidth; - orientedHeight = mDisplayHeight; - break; - } - uint32_t pointerCount = 0; int32_t pointerIds[MAX_POINTERS]; PointerCoords pointerCoords[MAX_POINTERS]; - const InputDevice::TouchScreenState::Precalculated& precalculated = - device->touchScreen.precalculated; - // Walk through the the active pointers and map touch screen coordinates (TouchData) into // display coordinates (PointerCoords) and adjust for display orientation. while (! idBits.isEmpty()) { @@ -845,55 +1574,57 @@ void InputReader::dispatchTouch(nsecs_t when, InputDevice* device, uint32_t poli idBits.clearBit(id); uint32_t index = touch->idToIndex[id]; - float x = float(touch->pointers[index].x - - precalculated.xOrigin) * precalculated.xScale; - float y = float(touch->pointers[index].y - - precalculated.yOrigin) * precalculated.yScale; - float pressure = float(touch->pointers[index].pressure - - precalculated.pressureOrigin) * precalculated.pressureScale; - float size = float(touch->pointers[index].size - - precalculated.sizeOrigin) * precalculated.sizeScale; + float x = float(touch->pointers[index].x - mXOrigin) * mXScale; + float y = float(touch->pointers[index].y - mYOrigin) * mYScale; + float pressure = float(touch->pointers[index].pressure - mPressureOrigin) * mPressureScale; + float size = float(touch->pointers[index].size - mSizeOrigin) * mSizeScale; - float orientation = float(touch->pointers[index].orientation) - * precalculated.orientationScale; + float orientation = float(touch->pointers[index].orientation) * mOrientationScale; - bool vertical = abs(orientation) <= M_PI / 8; + float touchMajor, touchMinor, toolMajor, toolMinor; + if (abs(orientation) <= M_PI_4) { + // Nominally vertical orientation: scale major axis by Y, and scale minor axis by X. + touchMajor = float(touch->pointers[index].touchMajor) * mYScale; + touchMinor = float(touch->pointers[index].touchMinor) * mXScale; + toolMajor = float(touch->pointers[index].toolMajor) * mYScale; + toolMinor = float(touch->pointers[index].toolMinor) * mXScale; + } else { + // Nominally horizontal orientation: scale major axis by X, and scale minor axis by Y. + touchMajor = float(touch->pointers[index].touchMajor) * mXScale; + touchMinor = float(touch->pointers[index].touchMinor) * mYScale; + toolMajor = float(touch->pointers[index].toolMajor) * mXScale; + toolMinor = float(touch->pointers[index].toolMinor) * mYScale; + } - switch (mDisplayOrientation) { + switch (mSurfaceOrientation) { case InputReaderPolicyInterface::ROTATION_90: { float xTemp = x; x = y; - y = mDisplayWidth - xTemp; - vertical = ! vertical; + y = mSurfaceWidth - xTemp; + orientation -= M_PI_2; + if (orientation < - M_PI_2) { + orientation += M_PI; + } break; } case InputReaderPolicyInterface::ROTATION_180: { - x = mDisplayWidth - x; - y = mDisplayHeight - y; + x = mSurfaceWidth - x; + y = mSurfaceHeight - y; + orientation = - orientation; break; } case InputReaderPolicyInterface::ROTATION_270: { float xTemp = x; - x = mDisplayHeight - y; + x = mSurfaceHeight - y; y = xTemp; - vertical = ! vertical; + orientation += M_PI_2; + if (orientation > M_PI_2) { + orientation -= M_PI; + } break; } } - float touchMajor, touchMinor, toolMajor, toolMinor; - if (vertical) { - touchMajor = float(touch->pointers[index].touchMajor) * precalculated.yScale; - touchMinor = float(touch->pointers[index].touchMinor) * precalculated.xScale; - toolMajor = float(touch->pointers[index].toolMajor) * precalculated.yScale; - toolMinor = float(touch->pointers[index].toolMinor) * precalculated.xScale; - } else { - touchMajor = float(touch->pointers[index].touchMajor) * precalculated.xScale; - touchMinor = float(touch->pointers[index].touchMinor) * precalculated.yScale; - toolMajor = float(touch->pointers[index].toolMajor) * precalculated.xScale; - toolMinor = float(touch->pointers[index].toolMinor) * precalculated.yScale; - } - pointerIds[pointerCount] = int32_t(id); pointerCoords[pointerCount].x = x; @@ -919,561 +1650,984 @@ void InputReader::dispatchTouch(nsecs_t when, InputDevice* device, uint32_t poli if (motionEventAction == AMOTION_EVENT_ACTION_DOWN) { if (pointerCoords[0].x <= 0) { motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_LEFT; - } else if (pointerCoords[0].x >= orientedWidth) { + } else if (pointerCoords[0].x >= mOrientedSurfaceWidth) { motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_RIGHT; } if (pointerCoords[0].y <= 0) { motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_TOP; - } else if (pointerCoords[0].y >= orientedHeight) { + } else if (pointerCoords[0].y >= mOrientedSurfaceHeight) { motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_BOTTOM; } } - nsecs_t downTime = device->touchScreen.downTime; - mDispatcher->notifyMotion(when, device->id, AINPUT_SOURCE_TOUCHSCREEN, policyFlags, - motionEventAction, globalMetaState(), motionEventEdgeFlags, + getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TOUCHSCREEN, policyFlags, + motionEventAction, getContext()->getGlobalMetaState(), motionEventEdgeFlags, pointerCount, pointerIds, pointerCoords, - 0, 0, downTime); + mOrientedXPrecision, mOrientedYPrecision, mDownTime); } -void InputReader::onTrackballStateChanged(nsecs_t when, - InputDevice* device) { - static const uint32_t DELTA_FIELDS = - InputDevice::TrackballState::Accumulator::FIELD_REL_X - | InputDevice::TrackballState::Accumulator::FIELD_REL_Y; +bool TouchInputMapper::isPointInsideSurface(int32_t x, int32_t y) { + if (mAxes.x.valid && mAxes.y.valid) { + return x >= mAxes.x.minValue && x <= mAxes.x.maxValue + && y >= mAxes.y.minValue && y <= mAxes.y.maxValue; + } + return true; +} - /* Refresh display properties so we can trackball moves according to display orientation */ +const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHitLvk(int32_t x, int32_t y) { + for (size_t i = 0; i < mVirtualKeys.size(); i++) { + const VirtualKey& virtualKey = mVirtualKeys[i]; - if (! refreshDisplayProperties()) { - return; - } +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, " + "left=%d, top=%d, right=%d, bottom=%d", + x, y, + virtualKey.keyCode, virtualKey.scanCode, + virtualKey.hitLeft, virtualKey.hitTop, + virtualKey.hitRight, virtualKey.hitBottom); +#endif - /* Update device state */ + if (virtualKey.isHit(x, y)) { + return & virtualKey; + } + } - uint32_t fields = device->trackball.accumulator.fields; - bool downChanged = fields & InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE; - bool deltaChanged = fields & DELTA_FIELDS; + return NULL; +} - bool down; - if (downChanged) { - if (device->trackball.accumulator.btnMouse) { - device->trackball.current.down = true; - device->trackball.current.downTime = when; - down = true; - } else { - device->trackball.current.down = false; - down = false; +void TouchInputMapper::calculatePointerIds() { + uint32_t currentPointerCount = mCurrentTouch.pointerCount; + uint32_t lastPointerCount = mLastTouch.pointerCount; + + if (currentPointerCount == 0) { + // No pointers to assign. + mCurrentTouch.idBits.clear(); + } else if (lastPointerCount == 0) { + // All pointers are new. + mCurrentTouch.idBits.clear(); + for (uint32_t i = 0; i < currentPointerCount; i++) { + mCurrentTouch.pointers[i].id = i; + mCurrentTouch.idToIndex[i] = i; + mCurrentTouch.idBits.markBit(i); } + } else if (currentPointerCount == 1 && lastPointerCount == 1) { + // Only one pointer and no change in count so it must have the same id as before. + uint32_t id = mLastTouch.pointers[0].id; + mCurrentTouch.pointers[0].id = id; + mCurrentTouch.idToIndex[id] = 0; + mCurrentTouch.idBits.value = BitSet32::valueForBit(id); } else { - down = device->trackball.current.down; - } + // General case. + // We build a heap of squared euclidean distances between current and last pointers + // associated with the current and last pointer indices. Then, we find the best + // match (by distance) for each current pointer. + PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS]; + + uint32_t heapSize = 0; + for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount; + currentPointerIndex++) { + for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount; + lastPointerIndex++) { + int64_t deltaX = mCurrentTouch.pointers[currentPointerIndex].x + - mLastTouch.pointers[lastPointerIndex].x; + int64_t deltaY = mCurrentTouch.pointers[currentPointerIndex].y + - mLastTouch.pointers[lastPointerIndex].y; + + uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); + + // Insert new element into the heap (sift up). + heap[heapSize].currentPointerIndex = currentPointerIndex; + heap[heapSize].lastPointerIndex = lastPointerIndex; + heap[heapSize].distance = distance; + heapSize += 1; + } + } - /* Apply policy */ + // Heapify + for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) { + startIndex -= 1; + for (uint32_t parentIndex = startIndex; ;) { + uint32_t childIndex = parentIndex * 2 + 1; + if (childIndex >= heapSize) { + break; + } - int32_t policyActions = mPolicy->interceptTrackball(when, downChanged, down, deltaChanged); + if (childIndex + 1 < heapSize + && heap[childIndex + 1].distance < heap[childIndex].distance) { + childIndex += 1; + } - uint32_t policyFlags = 0; - if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) { - return; // event dropped - } + if (heap[parentIndex].distance <= heap[childIndex].distance) { + break; + } - /* Enqueue motion event for dispatch */ + swap(heap[parentIndex], heap[childIndex]); + parentIndex = childIndex; + } + } - int32_t motionEventAction; - if (downChanged) { - motionEventAction = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP; - } else { - motionEventAction = AMOTION_EVENT_ACTION_MOVE; - } +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - initial distance min-heap: size=%d", heapSize); + for (size_t i = 0; i < heapSize; i++) { + LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld", + i, heap[i].currentPointerIndex, heap[i].lastPointerIndex, + heap[i].distance); + } +#endif - int32_t pointerId = 0; - PointerCoords pointerCoords; - pointerCoords.x = fields & InputDevice::TrackballState::Accumulator::FIELD_REL_X - ? device->trackball.accumulator.relX * device->trackball.precalculated.xScale : 0; - pointerCoords.y = fields & InputDevice::TrackballState::Accumulator::FIELD_REL_Y - ? device->trackball.accumulator.relY * device->trackball.precalculated.yScale : 0; - pointerCoords.pressure = 1.0f; // XXX Consider making this 1.0f if down, 0 otherwise. - pointerCoords.size = 0; - pointerCoords.touchMajor = 0; - pointerCoords.touchMinor = 0; - pointerCoords.toolMajor = 0; - pointerCoords.toolMinor = 0; - pointerCoords.orientation = 0; + // Pull matches out by increasing order of distance. + // To avoid reassigning pointers that have already been matched, the loop keeps track + // of which last and current pointers have been matched using the matchedXXXBits variables. + // It also tracks the used pointer id bits. + BitSet32 matchedLastBits(0); + BitSet32 matchedCurrentBits(0); + BitSet32 usedIdBits(0); + bool first = true; + for (uint32_t i = min(currentPointerCount, lastPointerCount); i > 0; i--) { + for (;;) { + if (first) { + // The first time through the loop, we just consume the root element of + // the heap (the one with smallest distance). + first = false; + } else { + // Previous iterations consumed the root element of the heap. + // Pop root element off of the heap (sift down). + heapSize -= 1; + assert(heapSize > 0); + + // Sift down. + heap[0] = heap[heapSize]; + for (uint32_t parentIndex = 0; ;) { + uint32_t childIndex = parentIndex * 2 + 1; + if (childIndex >= heapSize) { + break; + } + + if (childIndex + 1 < heapSize + && heap[childIndex + 1].distance < heap[childIndex].distance) { + childIndex += 1; + } + + if (heap[parentIndex].distance <= heap[childIndex].distance) { + break; + } + + swap(heap[parentIndex], heap[childIndex]); + parentIndex = childIndex; + } + +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - reduced distance min-heap: size=%d", heapSize); + for (size_t i = 0; i < heapSize; i++) { + LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld", + i, heap[i].currentPointerIndex, heap[i].lastPointerIndex, + heap[i].distance); + } +#endif + } - float temp; - switch (mDisplayOrientation) { - case InputReaderPolicyInterface::ROTATION_90: - temp = pointerCoords.x; - pointerCoords.x = pointerCoords.y; - pointerCoords.y = - temp; - break; + uint32_t currentPointerIndex = heap[0].currentPointerIndex; + if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched - case InputReaderPolicyInterface::ROTATION_180: - pointerCoords.x = - pointerCoords.x; - pointerCoords.y = - pointerCoords.y; - break; + uint32_t lastPointerIndex = heap[0].lastPointerIndex; + if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched - case InputReaderPolicyInterface::ROTATION_270: - temp = pointerCoords.x; - pointerCoords.x = - pointerCoords.y; - pointerCoords.y = temp; - break; - } + matchedCurrentBits.markBit(currentPointerIndex); + matchedLastBits.markBit(lastPointerIndex); - mDispatcher->notifyMotion(when, device->id, AINPUT_SOURCE_TRACKBALL, policyFlags, - motionEventAction, globalMetaState(), AMOTION_EVENT_EDGE_FLAG_NONE, - 1, & pointerId, & pointerCoords, - device->trackball.precalculated.xPrecision, - device->trackball.precalculated.yPrecision, - device->trackball.current.downTime); -} + uint32_t id = mLastTouch.pointers[lastPointerIndex].id; + mCurrentTouch.pointers[currentPointerIndex].id = id; + mCurrentTouch.idToIndex[id] = currentPointerIndex; + usedIdBits.markBit(id); -void InputReader::onConfigurationChanged(nsecs_t when) { - // Reset global meta state because it depends on the list of all configured devices. - resetGlobalMetaState(); +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld", + lastPointerIndex, currentPointerIndex, id, heap[0].distance); +#endif + break; + } + } - // Reset virtual keys, just in case. - updateExportedVirtualKeyState(); + // Assign fresh ids to new pointers. + if (currentPointerCount > lastPointerCount) { + for (uint32_t i = currentPointerCount - lastPointerCount; ;) { + uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit(); + uint32_t id = usedIdBits.firstUnmarkedBit(); - // Update input configuration. - updateExportedInputConfiguration(); + mCurrentTouch.pointers[currentPointerIndex].id = id; + mCurrentTouch.idToIndex[id] = currentPointerIndex; + usedIdBits.markBit(id); - // Enqueue configuration changed. - mDispatcher->notifyConfigurationChanged(when); -} +#if DEBUG_POINTER_ASSIGNMENT + LOGD("calculatePointerIds - assigned: cur=%d, id=%d", + currentPointerIndex, id); +#endif -bool InputReader::applyStandardInputDispatchPolicyActions(nsecs_t when, - int32_t policyActions, uint32_t* policyFlags) { - if (policyActions & InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING) { - mDispatcher->notifyAppSwitchComing(when); + if (--i == 0) break; // done + matchedCurrentBits.markBit(currentPointerIndex); + } + } + + // Fix id bits. + mCurrentTouch.idBits = usedIdBits; } +} - if (policyActions & InputReaderPolicyInterface::ACTION_WOKE_HERE) { - *policyFlags |= POLICY_FLAG_WOKE_HERE; +/* Special hack for devices that have bad screen data: if one of the + * points has moved more than a screen height from the last position, + * then drop it. */ +bool TouchInputMapper::applyBadTouchFilter() { + // This hack requires valid axis parameters. + if (! mAxes.y.valid) { + return false; } - if (policyActions & InputReaderPolicyInterface::ACTION_BRIGHT_HERE) { - *policyFlags |= POLICY_FLAG_BRIGHT_HERE; + uint32_t pointerCount = mCurrentTouch.pointerCount; + + // Nothing to do if there are no points. + if (pointerCount == 0) { + return false; } - return policyActions & InputReaderPolicyInterface::ACTION_DISPATCH; -} + // Don't do anything if a finger is going down or up. We run + // here before assigning pointer IDs, so there isn't a good + // way to do per-finger matching. + if (pointerCount != mLastTouch.pointerCount) { + return false; + } -void InputReader::resetDisplayProperties() { - mDisplayWidth = mDisplayHeight = -1; - mDisplayOrientation = -1; -} + // We consider a single movement across more than a 7/16 of + // the long size of the screen to be bad. This was a magic value + // determined by looking at the maximum distance it is feasible + // to actually move in one sample. + int32_t maxDeltaY = mAxes.y.getRange() * 7 / 16; + + // XXX The original code in InputDevice.java included commented out + // code for testing the X axis. Note that when we drop a point + // we don't actually restore the old X either. Strange. + // The old code also tries to track when bad points were previously + // detected but it turns out that due to the placement of a "break" + // at the end of the loop, we never set mDroppedBadPoint to true + // so it is effectively dead code. + // Need to figure out if the old code is busted or just overcomplicated + // but working as intended. + + // Look through all new points and see if any are farther than + // acceptable from all previous points. + for (uint32_t i = pointerCount; i-- > 0; ) { + int32_t y = mCurrentTouch.pointers[i].y; + int32_t closestY = INT_MAX; + int32_t closestDeltaY = 0; + +#if DEBUG_HACKS + LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y); +#endif -bool InputReader::refreshDisplayProperties() { - int32_t newWidth, newHeight, newOrientation; - if (mPolicy->getDisplayInfo(0, & newWidth, & newHeight, & newOrientation)) { - if (newWidth != mDisplayWidth || newHeight != mDisplayHeight) { - LOGD("Display size changed from %dx%d to %dx%d, updating device configuration", - mDisplayWidth, mDisplayHeight, newWidth, newHeight); + for (uint32_t j = pointerCount; j-- > 0; ) { + int32_t lastY = mLastTouch.pointers[j].y; + int32_t deltaY = abs(y - lastY); - mDisplayWidth = newWidth; - mDisplayHeight = newHeight; +#if DEBUG_HACKS + LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d", + j, lastY, deltaY); +#endif - for (size_t i = 0; i < mDevices.size(); i++) { - configureDeviceForCurrentDisplaySize(mDevices.valueAt(i)); + if (deltaY < maxDeltaY) { + goto SkipSufficientlyClosePoint; + } + if (deltaY < closestDeltaY) { + closestDeltaY = deltaY; + closestY = lastY; } } - if (newOrientation != mDisplayOrientation) { - LOGD("Display orientation changed to %d", mDisplayOrientation); + // Must not have found a close enough match. +#if DEBUG_HACKS + LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d", + i, y, closestY, closestDeltaY, maxDeltaY); +#endif - mDisplayOrientation = newOrientation; - } - return true; - } else { - resetDisplayProperties(); - return false; + mCurrentTouch.pointers[i].y = closestY; + return true; // XXX original code only corrects one point + + SkipSufficientlyClosePoint: ; } -} -InputDevice* InputReader::getDevice(int32_t deviceId) { - ssize_t index = mDevices.indexOfKey(deviceId); - return index >= 0 ? mDevices.valueAt((size_t) index) : NULL; + // No change. + return false; } -InputDevice* InputReader::getNonIgnoredDevice(int32_t deviceId) { - InputDevice* device = getDevice(deviceId); - return device && ! device->ignored ? device : NULL; -} +/* Special hack for devices that have bad screen data: drop points where + * the coordinate value for one axis has jumped to the other pointer's location. + */ +bool TouchInputMapper::applyJumpyTouchFilter() { + // This hack requires valid axis parameters. + if (! mAxes.y.valid) { + return false; + } -void InputReader::addDevice(nsecs_t when, int32_t deviceId) { - uint32_t classes = mEventHub->getDeviceClasses(deviceId); - String8 name = mEventHub->getDeviceName(deviceId); - InputDevice* device = new InputDevice(deviceId, classes, name); + uint32_t pointerCount = mCurrentTouch.pointerCount; + if (mLastTouch.pointerCount != pointerCount) { +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Different pointer count %d -> %d", + mLastTouch.pointerCount, pointerCount); + for (uint32_t i = 0; i < pointerCount; i++) { + LOGD(" Pointer %d (%d, %d)", i, + mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y); + } +#endif - if (classes != 0) { - LOGI("Device added: id=0x%x, name=%s, classes=%02x", device->id, - device->name.string(), device->classes); + if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) { + if (mLastTouch.pointerCount == 1 && pointerCount == 2) { + // Just drop the first few events going from 1 to 2 pointers. + // They're bad often enough that they're not worth considering. + mCurrentTouch.pointerCount = 1; + mJumpyTouchFilter.jumpyPointsDropped += 1; - configureDevice(device); - } else { - LOGI("Device added: id=0x%x, name=%s (ignored non-input device)", device->id, - device->name.string()); +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Pointer 2 dropped"); +#endif + return true; + } else if (mLastTouch.pointerCount == 2 && pointerCount == 1) { + // The event when we go from 2 -> 1 tends to be messed up too + mCurrentTouch.pointerCount = 2; + mCurrentTouch.pointers[0] = mLastTouch.pointers[0]; + mCurrentTouch.pointers[1] = mLastTouch.pointers[1]; + mJumpyTouchFilter.jumpyPointsDropped += 1; + +#if DEBUG_HACKS + for (int32_t i = 0; i < 2; i++) { + LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i, + mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y); + } +#endif + return true; + } + } + // Reset jumpy points dropped on other transitions or if limit exceeded. + mJumpyTouchFilter.jumpyPointsDropped = 0; - device->ignored = true; +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Transition - drop limit reset"); +#endif + return false; } - device->reset(); + // We have the same number of pointers as last time. + // A 'jumpy' point is one where the coordinate value for one axis + // has jumped to the other pointer's location. No need to do anything + // else if we only have one pointer. + if (pointerCount < 2) { + return false; + } - mDevices.add(deviceId, device); + if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) { + int jumpyEpsilon = mAxes.y.getRange() / JUMPY_EPSILON_DIVISOR; - if (! device->ignored) { - onConfigurationChanged(when); - } -} + // We only replace the single worst jumpy point as characterized by pointer distance + // in a single axis. + int32_t badPointerIndex = -1; + int32_t badPointerReplacementIndex = -1; + int32_t badPointerDistance = INT_MIN; // distance to be corrected -void InputReader::removeDevice(nsecs_t when, InputDevice* device) { - mDevices.removeItem(device->id); + for (uint32_t i = pointerCount; i-- > 0; ) { + int32_t x = mCurrentTouch.pointers[i].x; + int32_t y = mCurrentTouch.pointers[i].y; - if (! device->ignored) { - LOGI("Device removed: id=0x%x, name=%s, classes=%02x", device->id, - device->name.string(), device->classes); +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y); +#endif - onConfigurationChanged(when); - } else { - LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)", device->id, - device->name.string()); - } + // Check if a touch point is too close to another's coordinates + bool dropX = false, dropY = false; + for (uint32_t j = 0; j < pointerCount; j++) { + if (i == j) { + continue; + } - delete device; -} + if (abs(x - mCurrentTouch.pointers[j].x) <= jumpyEpsilon) { + dropX = true; + break; + } -void InputReader::configureDevice(InputDevice* device) { - if (device->isMultiTouchScreen()) { - configureAbsoluteAxisInfo(device, ABS_MT_POSITION_X, "X", - & device->touchScreen.parameters.xAxis); - configureAbsoluteAxisInfo(device, ABS_MT_POSITION_Y, "Y", - & device->touchScreen.parameters.yAxis); - configureAbsoluteAxisInfo(device, ABS_MT_TOUCH_MAJOR, "Pressure", - & device->touchScreen.parameters.pressureAxis); - configureAbsoluteAxisInfo(device, ABS_MT_WIDTH_MAJOR, "Size", - & device->touchScreen.parameters.sizeAxis); - configureAbsoluteAxisInfo(device, ABS_MT_ORIENTATION, "Orientation", - & device->touchScreen.parameters.orientationAxis); - } else if (device->isSingleTouchScreen()) { - configureAbsoluteAxisInfo(device, ABS_X, "X", - & device->touchScreen.parameters.xAxis); - configureAbsoluteAxisInfo(device, ABS_Y, "Y", - & device->touchScreen.parameters.yAxis); - configureAbsoluteAxisInfo(device, ABS_PRESSURE, "Pressure", - & device->touchScreen.parameters.pressureAxis); - configureAbsoluteAxisInfo(device, ABS_TOOL_WIDTH, "Size", - & device->touchScreen.parameters.sizeAxis); - device->touchScreen.parameters.orientationAxis.valid = false; - } - - if (device->isTouchScreen()) { - device->touchScreen.parameters.useBadTouchFilter = - mPolicy->filterTouchEvents(); - device->touchScreen.parameters.useAveragingTouchFilter = - mPolicy->filterTouchEvents(); - device->touchScreen.parameters.useJumpyTouchFilter = - mPolicy->filterJumpyTouchEvents(); - - if (device->touchScreen.parameters.pressureAxis.valid) { - device->touchScreen.precalculated.pressureOrigin = - device->touchScreen.parameters.pressureAxis.minValue; - device->touchScreen.precalculated.pressureScale = - 1.0f / device->touchScreen.parameters.pressureAxis.range; - } else { - device->touchScreen.precalculated.pressureOrigin = 0; - device->touchScreen.precalculated.pressureScale = 1.0f; - } + if (abs(y - mCurrentTouch.pointers[j].y) <= jumpyEpsilon) { + dropY = true; + break; + } + } + if (! dropX && ! dropY) { + continue; // not jumpy + } - if (device->touchScreen.parameters.sizeAxis.valid) { - device->touchScreen.precalculated.sizeOrigin = - device->touchScreen.parameters.sizeAxis.minValue; - device->touchScreen.precalculated.sizeScale = - 1.0f / device->touchScreen.parameters.sizeAxis.range; - } else { - device->touchScreen.precalculated.sizeOrigin = 0; - device->touchScreen.precalculated.sizeScale = 1.0f; - } + // Find a replacement candidate by comparing with older points on the + // complementary (non-jumpy) axis. + int32_t distance = INT_MIN; // distance to be corrected + int32_t replacementIndex = -1; + + if (dropX) { + // X looks too close. Find an older replacement point with a close Y. + int32_t smallestDeltaY = INT_MAX; + for (uint32_t j = 0; j < pointerCount; j++) { + int32_t deltaY = abs(y - mLastTouch.pointers[j].y); + if (deltaY < smallestDeltaY) { + smallestDeltaY = deltaY; + replacementIndex = j; + } + } + distance = abs(x - mLastTouch.pointers[replacementIndex].x); + } else { + // Y looks too close. Find an older replacement point with a close X. + int32_t smallestDeltaX = INT_MAX; + for (uint32_t j = 0; j < pointerCount; j++) { + int32_t deltaX = abs(x - mLastTouch.pointers[j].x); + if (deltaX < smallestDeltaX) { + smallestDeltaX = deltaX; + replacementIndex = j; + } + } + distance = abs(y - mLastTouch.pointers[replacementIndex].y); + } - if (device->touchScreen.parameters.orientationAxis.valid - && device->touchScreen.parameters.orientationAxis.maxValue > 0) { - device->touchScreen.precalculated.orientationScale = - M_PI_4 / device->touchScreen.parameters.orientationAxis.maxValue; - } else { - device->touchScreen.precalculated.orientationScale = 0.0f; + // If replacing this pointer would correct a worse error than the previous ones + // considered, then use this replacement instead. + if (distance > badPointerDistance) { + badPointerIndex = i; + badPointerReplacementIndex = replacementIndex; + badPointerDistance = distance; + } } - } - if (device->isTrackball()) { - device->trackball.precalculated.xPrecision = TRACKBALL_MOVEMENT_THRESHOLD; - device->trackball.precalculated.yPrecision = TRACKBALL_MOVEMENT_THRESHOLD; - device->trackball.precalculated.xScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; - device->trackball.precalculated.yScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + // Correct the jumpy pointer if one was found. + if (badPointerIndex >= 0) { +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)", + badPointerIndex, + mLastTouch.pointers[badPointerReplacementIndex].x, + mLastTouch.pointers[badPointerReplacementIndex].y); +#endif + + mCurrentTouch.pointers[badPointerIndex].x = + mLastTouch.pointers[badPointerReplacementIndex].x; + mCurrentTouch.pointers[badPointerIndex].y = + mLastTouch.pointers[badPointerReplacementIndex].y; + mJumpyTouchFilter.jumpyPointsDropped += 1; + return true; + } } - configureDeviceForCurrentDisplaySize(device); + mJumpyTouchFilter.jumpyPointsDropped = 0; + return false; } -void InputReader::configureDeviceForCurrentDisplaySize(InputDevice* device) { - if (device->isTouchScreen()) { - if (device->touchScreen.parameters.xAxis.valid - && device->touchScreen.parameters.yAxis.valid) { - device->touchScreen.precalculated.xOrigin = - device->touchScreen.parameters.xAxis.minValue; - device->touchScreen.precalculated.yOrigin = - device->touchScreen.parameters.yAxis.minValue; +/* Special hack for devices that have bad screen data: aggregate and + * compute averages of the coordinate data, to reduce the amount of + * jitter seen by applications. */ +void TouchInputMapper::applyAveragingTouchFilter() { + for (uint32_t currentIndex = 0; currentIndex < mCurrentTouch.pointerCount; currentIndex++) { + uint32_t id = mCurrentTouch.pointers[currentIndex].id; + int32_t x = mCurrentTouch.pointers[currentIndex].x; + int32_t y = mCurrentTouch.pointers[currentIndex].y; + int32_t pressure = mCurrentTouch.pointers[currentIndex].pressure; + + if (mLastTouch.idBits.hasBit(id)) { + // Pointer was down before and is still down now. + // Compute average over history trace. + uint32_t start = mAveragingTouchFilter.historyStart[id]; + uint32_t end = mAveragingTouchFilter.historyEnd[id]; + + int64_t deltaX = x - mAveragingTouchFilter.historyData[end].pointers[id].x; + int64_t deltaY = y - mAveragingTouchFilter.historyData[end].pointers[id].y; + uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); + +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld", + id, distance); +#endif - if (mDisplayWidth < 0) { - LOGD("Skipping part of touch screen configuration since display size is unknown."); + if (distance < AVERAGING_DISTANCE_LIMIT) { + // Increment end index in preparation for recording new historical data. + end += 1; + if (end > AVERAGING_HISTORY_SIZE) { + end = 0; + } - device->touchScreen.precalculated.xScale = 1.0f; - device->touchScreen.precalculated.yScale = 1.0f; - } else { - LOGI("Device configured: id=0x%x, name=%s (display size was changed)", device->id, - device->name.string()); + // If the end index has looped back to the start index then we have filled + // the historical trace up to the desired size so we drop the historical + // data at the start of the trace. + if (end == start) { + start += 1; + if (start > AVERAGING_HISTORY_SIZE) { + start = 0; + } + } - device->touchScreen.precalculated.xScale = - float(mDisplayWidth) / device->touchScreen.parameters.xAxis.range; - device->touchScreen.precalculated.yScale = - float(mDisplayHeight) / device->touchScreen.parameters.yAxis.range; + // Add the raw data to the historical trace. + mAveragingTouchFilter.historyStart[id] = start; + mAveragingTouchFilter.historyEnd[id] = end; + mAveragingTouchFilter.historyData[end].pointers[id].x = x; + mAveragingTouchFilter.historyData[end].pointers[id].y = y; + mAveragingTouchFilter.historyData[end].pointers[id].pressure = pressure; + + // Average over all historical positions in the trace by total pressure. + int32_t averagedX = 0; + int32_t averagedY = 0; + int32_t totalPressure = 0; + for (;;) { + int32_t historicalX = mAveragingTouchFilter.historyData[start].pointers[id].x; + int32_t historicalY = mAveragingTouchFilter.historyData[start].pointers[id].y; + int32_t historicalPressure = mAveragingTouchFilter.historyData[start] + .pointers[id].pressure; + + averagedX += historicalX * historicalPressure; + averagedY += historicalY * historicalPressure; + totalPressure += historicalPressure; + + if (start == end) { + break; + } + + start += 1; + if (start > AVERAGING_HISTORY_SIZE) { + start = 0; + } + } + + averagedX /= totalPressure; + averagedY /= totalPressure; + +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - " + "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure, + averagedX, averagedY); +#endif - configureVirtualKeys(device); + mCurrentTouch.pointers[currentIndex].x = averagedX; + mCurrentTouch.pointers[currentIndex].y = averagedY; + } else { +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id); +#endif } } else { - device->touchScreen.precalculated.xOrigin = 0; - device->touchScreen.precalculated.xScale = 1.0f; - device->touchScreen.precalculated.yOrigin = 0; - device->touchScreen.precalculated.yScale = 1.0f; +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id); +#endif } + + // Reset pointer history. + mAveragingTouchFilter.historyStart[id] = 0; + mAveragingTouchFilter.historyEnd[id] = 0; + mAveragingTouchFilter.historyData[0].pointers[id].x = x; + mAveragingTouchFilter.historyData[0].pointers[id].y = y; + mAveragingTouchFilter.historyData[0].pointers[id].pressure = pressure; } } -void InputReader::configureVirtualKeys(InputDevice* device) { - assert(device->touchScreen.parameters.xAxis.valid - && device->touchScreen.parameters.yAxis.valid); - - device->touchScreen.virtualKeys.clear(); - - Vector<InputReaderPolicyInterface::VirtualKeyDefinition> virtualKeyDefinitions; - mPolicy->getVirtualKeyDefinitions(device->name, virtualKeyDefinitions); - if (virtualKeyDefinitions.size() == 0) { - return; - } +int32_t TouchInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { + { // acquire virtual key lock + AutoMutex _l(mVirtualKeyLock); - device->touchScreen.virtualKeys.setCapacity(virtualKeyDefinitions.size()); + if (mCurrentVirtualKey.down && mCurrentVirtualKey.keyCode == keyCode) { + return AKEY_STATE_VIRTUAL; + } - int32_t touchScreenLeft = device->touchScreen.parameters.xAxis.minValue; - int32_t touchScreenTop = device->touchScreen.parameters.yAxis.minValue; - int32_t touchScreenWidth = device->touchScreen.parameters.xAxis.range; - int32_t touchScreenHeight = device->touchScreen.parameters.yAxis.range; + for (size_t i = 0; i < mVirtualKeys.size(); i++) { + const VirtualKey& virtualKey = mVirtualKeys[i]; + if (virtualKey.keyCode == keyCode) { + return AKEY_STATE_UP; + } + } + } // release virtual key lock - for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) { - const InputReaderPolicyInterface::VirtualKeyDefinition& virtualKeyDefinition = - virtualKeyDefinitions[i]; + return AKEY_STATE_UNKNOWN; +} - device->touchScreen.virtualKeys.add(); - InputDevice::VirtualKey& virtualKey = - device->touchScreen.virtualKeys.editTop(); +int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { + { // acquire virtual key lock + AutoMutex _l(mVirtualKeyLock); - virtualKey.scanCode = virtualKeyDefinition.scanCode; - int32_t keyCode; - uint32_t flags; - if (mEventHub->scancodeToKeycode(device->id, virtualKey.scanCode, - & keyCode, & flags)) { - LOGW(" VirtualKey %d: could not obtain key code, ignoring", virtualKey.scanCode); - device->touchScreen.virtualKeys.pop(); // drop the key - continue; + if (mCurrentVirtualKey.down && mCurrentVirtualKey.scanCode == scanCode) { + return AKEY_STATE_VIRTUAL; } - virtualKey.keyCode = keyCode; - virtualKey.flags = flags; + for (size_t i = 0; i < mVirtualKeys.size(); i++) { + const VirtualKey& virtualKey = mVirtualKeys[i]; + if (virtualKey.scanCode == scanCode) { + return AKEY_STATE_UP; + } + } + } // release virtual key lock - // convert the key definition's display coordinates into touch coordinates for a hit box - int32_t halfWidth = virtualKeyDefinition.width / 2; - int32_t halfHeight = virtualKeyDefinition.height / 2; + return AKEY_STATE_UNKNOWN; +} - virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth) - * touchScreenWidth / mDisplayWidth + touchScreenLeft; - virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth) - * touchScreenWidth / mDisplayWidth + touchScreenLeft; - virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight) - * touchScreenHeight / mDisplayHeight + touchScreenTop; - virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight) - * touchScreenHeight / mDisplayHeight + touchScreenTop; +bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, + const int32_t* keyCodes, uint8_t* outFlags) { + { // acquire virtual key lock + AutoMutex _l(mVirtualKeyLock); - LOGI(" VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d", - virtualKey.scanCode, virtualKey.keyCode, - virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom); - } -} + for (size_t i = 0; i < mVirtualKeys.size(); i++) { + const VirtualKey& virtualKey = mVirtualKeys[i]; -void InputReader::configureAbsoluteAxisInfo(InputDevice* device, - int axis, const char* name, InputDevice::AbsoluteAxisInfo* out) { - if (! mEventHub->getAbsoluteInfo(device->id, axis, - & out->minValue, & out->maxValue, & out->flat, &out->fuzz)) { - out->range = out->maxValue - out->minValue; - if (out->range != 0) { - LOGI(" %s: min=%d max=%d flat=%d fuzz=%d", - name, out->minValue, out->maxValue, out->flat, out->fuzz); - out->valid = true; - return; + for (size_t i = 0; i < numCodes; i++) { + if (virtualKey.keyCode == keyCodes[i]) { + outFlags[i] = 1; + } + } } - } + } // release virtual key lock - out->valid = false; - out->minValue = 0; - out->maxValue = 0; - out->flat = 0; - out->fuzz = 0; - out->range = 0; - LOGI(" %s: unknown axis values, marking as invalid", name); + return true; } -void InputReader::configureExcludedDevices() { - Vector<String8> excludedDeviceNames; - mPolicy->getExcludedDeviceNames(excludedDeviceNames); - for (size_t i = 0; i < excludedDeviceNames.size(); i++) { - mEventHub->addExcludedDevice(excludedDeviceNames[i]); - } +// --- SingleTouchInputMapper --- + +SingleTouchInputMapper::SingleTouchInputMapper(InputDevice* device, int32_t associatedDisplayId) : + TouchInputMapper(device, associatedDisplayId) { + initialize(); } -void InputReader::resetGlobalMetaState() { - mGlobalMetaState = -1; +SingleTouchInputMapper::~SingleTouchInputMapper() { } -int32_t InputReader::globalMetaState() { - if (mGlobalMetaState == -1) { - mGlobalMetaState = 0; - for (size_t i = 0; i < mDevices.size(); i++) { - InputDevice* device = mDevices.valueAt(i); - if (device->isKeyboard()) { - mGlobalMetaState |= device->keyboard.current.metaState; +void SingleTouchInputMapper::initialize() { + mAccumulator.clear(); + + mDown = false; + mX = 0; + mY = 0; + mPressure = 0; + mSize = 0; +} + +void SingleTouchInputMapper::reset() { + TouchInputMapper::reset(); + + // Reinitialize. + initialize(); + } + +void SingleTouchInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_KEY: + switch (rawEvent->scanCode) { + case BTN_TOUCH: + mAccumulator.fields |= Accumulator::FIELD_BTN_TOUCH; + mAccumulator.btnTouch = rawEvent->value != 0; + + sync(rawEvent->when); + mAccumulator.clear(); + break; + } + break; + + case EV_ABS: + switch (rawEvent->scanCode) { + case ABS_X: + mAccumulator.fields |= Accumulator::FIELD_ABS_X; + mAccumulator.absX = rawEvent->value; + break; + case ABS_Y: + mAccumulator.fields |= Accumulator::FIELD_ABS_Y; + mAccumulator.absY = rawEvent->value; + break; + case ABS_PRESSURE: + mAccumulator.fields |= Accumulator::FIELD_ABS_PRESSURE; + mAccumulator.absPressure = rawEvent->value; + break; + case ABS_TOOL_WIDTH: + mAccumulator.fields |= Accumulator::FIELD_ABS_TOOL_WIDTH; + mAccumulator.absToolWidth = rawEvent->value; + break; + } + break; + + case EV_SYN: + switch (rawEvent->scanCode) { + case SYN_REPORT: + if (mAccumulator.isDirty()) { + sync(rawEvent->when); + mAccumulator.clear(); } + break; } + break; } - return mGlobalMetaState; } -void InputReader::updateExportedVirtualKeyState() { - int32_t keyCode = -1, scanCode = -1; +void SingleTouchInputMapper::sync(nsecs_t when) { + /* Update device state */ - for (size_t i = 0; i < mDevices.size(); i++) { - InputDevice* device = mDevices.valueAt(i); - if (device->isTouchScreen()) { - if (device->touchScreen.currentVirtualKey.status - == InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_DOWN) { - keyCode = device->touchScreen.currentVirtualKey.keyCode; - scanCode = device->touchScreen.currentVirtualKey.scanCode; - } - } + uint32_t fields = mAccumulator.fields; + + if (fields & Accumulator::FIELD_BTN_TOUCH) { + mDown = mAccumulator.btnTouch; + } + + if (fields & Accumulator::FIELD_ABS_X) { + mX = mAccumulator.absX; } - { // acquire exported state lock - AutoMutex _l(mExportedStateLock); + if (fields & Accumulator::FIELD_ABS_Y) { + mY = mAccumulator.absY; + } - mExportedVirtualKeyCode = keyCode; - mExportedVirtualScanCode = scanCode; - } // release exported state lock + if (fields & Accumulator::FIELD_ABS_PRESSURE) { + mPressure = mAccumulator.absPressure; + } + + if (fields & Accumulator::FIELD_ABS_TOOL_WIDTH) { + mSize = mAccumulator.absToolWidth; + } + + mCurrentTouch.clear(); + + if (mDown) { + mCurrentTouch.pointerCount = 1; + mCurrentTouch.pointers[0].id = 0; + mCurrentTouch.pointers[0].x = mX; + mCurrentTouch.pointers[0].y = mY; + mCurrentTouch.pointers[0].pressure = mPressure; + mCurrentTouch.pointers[0].size = mSize; + mCurrentTouch.pointers[0].touchMajor = mPressure; + mCurrentTouch.pointers[0].touchMinor = mPressure; + mCurrentTouch.pointers[0].toolMajor = mSize; + mCurrentTouch.pointers[0].toolMinor = mSize; + mCurrentTouch.pointers[0].orientation = 0; + mCurrentTouch.idToIndex[0] = 0; + mCurrentTouch.idBits.markBit(0); + } + + syncTouch(when, true); } -bool InputReader::getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const { - { // acquire exported state lock - AutoMutex _l(mExportedStateLock); +void SingleTouchInputMapper::configureAxes() { + TouchInputMapper::configureAxes(); - *outKeyCode = mExportedVirtualKeyCode; - *outScanCode = mExportedVirtualScanCode; - return mExportedVirtualKeyCode != -1; - } // release exported state lock + // The axes are aliased to take into account the manner in which they are presented + // as part of the TouchData during the sync. + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_X, & mAxes.x); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_Y, & mAxes.y); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_PRESSURE, & mAxes.pressure); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_TOOL_WIDTH, & mAxes.size); + + mAxes.touchMajor = mAxes.pressure; + mAxes.touchMinor = mAxes.pressure; + mAxes.toolMajor = mAxes.size; + mAxes.toolMinor = mAxes.size; } -void InputReader::updateExportedInputConfiguration() { - int32_t touchScreenConfig = InputConfiguration::TOUCHSCREEN_NOTOUCH; - int32_t keyboardConfig = InputConfiguration::KEYBOARD_NOKEYS; - int32_t navigationConfig = InputConfiguration::NAVIGATION_NONAV; - for (size_t i = 0; i < mDevices.size(); i++) { - InputDevice* device = mDevices.valueAt(i); - int32_t deviceClasses = device->classes; +// --- MultiTouchInputMapper --- + +MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device, int32_t associatedDisplayId) : + TouchInputMapper(device, associatedDisplayId) { + initialize(); +} + +MultiTouchInputMapper::~MultiTouchInputMapper() { +} + +void MultiTouchInputMapper::initialize() { + mAccumulator.clear(); +} + +void MultiTouchInputMapper::reset() { + TouchInputMapper::reset(); - if (deviceClasses & INPUT_DEVICE_CLASS_TOUCHSCREEN) { - touchScreenConfig = InputConfiguration::TOUCHSCREEN_FINGER; + // Reinitialize. + initialize(); +} + +void MultiTouchInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_ABS: { + uint32_t pointerIndex = mAccumulator.pointerCount; + Accumulator::Pointer* pointer = & mAccumulator.pointers[pointerIndex]; + + switch (rawEvent->scanCode) { + case ABS_MT_POSITION_X: + pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_X; + pointer->absMTPositionX = rawEvent->value; + break; + case ABS_MT_POSITION_Y: + pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_Y; + pointer->absMTPositionY = rawEvent->value; + break; + case ABS_MT_TOUCH_MAJOR: + pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MAJOR; + pointer->absMTTouchMajor = rawEvent->value; + break; + case ABS_MT_TOUCH_MINOR: + pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MINOR; + pointer->absMTTouchMinor = rawEvent->value; + break; + case ABS_MT_WIDTH_MAJOR: + pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MAJOR; + pointer->absMTWidthMajor = rawEvent->value; + break; + case ABS_MT_WIDTH_MINOR: + pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MINOR; + pointer->absMTWidthMinor = rawEvent->value; + break; + case ABS_MT_ORIENTATION: + pointer->fields |= Accumulator::FIELD_ABS_MT_ORIENTATION; + pointer->absMTOrientation = rawEvent->value; + break; + case ABS_MT_TRACKING_ID: + pointer->fields |= Accumulator::FIELD_ABS_MT_TRACKING_ID; + pointer->absMTTrackingId = rawEvent->value; + break; } - if (deviceClasses & INPUT_DEVICE_CLASS_ALPHAKEY) { - keyboardConfig = InputConfiguration::KEYBOARD_QWERTY; + break; + } + + case EV_SYN: + switch (rawEvent->scanCode) { + case SYN_MT_REPORT: { + // MultiTouch Sync: The driver has returned all data for *one* of the pointers. + uint32_t pointerIndex = mAccumulator.pointerCount; + + if (mAccumulator.pointers[pointerIndex].fields) { + if (pointerIndex == MAX_POINTERS) { + LOGW("MultiTouch device driver returned more than maximum of %d pointers.", + MAX_POINTERS); + } else { + pointerIndex += 1; + mAccumulator.pointerCount = pointerIndex; + } + } + + mAccumulator.pointers[pointerIndex].clear(); + break; } - if (deviceClasses & INPUT_DEVICE_CLASS_TRACKBALL) { - navigationConfig = InputConfiguration::NAVIGATION_TRACKBALL; - } else if (deviceClasses & INPUT_DEVICE_CLASS_DPAD) { - navigationConfig = InputConfiguration::NAVIGATION_DPAD; + + case SYN_REPORT: + if (mAccumulator.isDirty()) { + sync(rawEvent->when); + mAccumulator.clear(); + } + break; } + break; } +} - { // acquire exported state lock - AutoMutex _l(mExportedStateLock); +void MultiTouchInputMapper::sync(nsecs_t when) { + static const uint32_t REQUIRED_FIELDS = + Accumulator::FIELD_ABS_MT_POSITION_X + | Accumulator::FIELD_ABS_MT_POSITION_Y + | Accumulator::FIELD_ABS_MT_TOUCH_MAJOR + | Accumulator::FIELD_ABS_MT_WIDTH_MAJOR; - mExportedInputConfiguration.touchScreen = touchScreenConfig; - mExportedInputConfiguration.keyboard = keyboardConfig; - mExportedInputConfiguration.navigation = navigationConfig; - } // release exported state lock -} + /* Update device state */ + + uint32_t inCount = mAccumulator.pointerCount; + uint32_t outCount = 0; + bool havePointerIds = true; -void InputReader::getCurrentInputConfiguration(InputConfiguration* outConfiguration) const { - { // acquire exported state lock - AutoMutex _l(mExportedStateLock); + mCurrentTouch.clear(); - *outConfiguration = mExportedInputConfiguration; - } // release exported state lock -} + for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) { + uint32_t fields = mAccumulator.pointers[inIndex].fields; -int32_t InputReader::getCurrentScanCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t scanCode) const { - { // acquire exported state lock - AutoMutex _l(mExportedStateLock); + if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) { +#if DEBUG_POINTERS + LOGD("Pointers: Missing required multitouch pointer fields: index=%d, fields=%d", + inIndex, fields); + continue; +#endif + } - if (mExportedVirtualScanCode == scanCode) { - return AKEY_STATE_VIRTUAL; + if (mAccumulator.pointers[inIndex].absMTTouchMajor <= 0) { + // Pointer is not down. Drop it. + continue; } - } // release exported state lock - return mEventHub->getScanCodeState(deviceId, deviceClasses, scanCode); -} + mCurrentTouch.pointers[outCount].x = mAccumulator.pointers[inIndex].absMTPositionX; + mCurrentTouch.pointers[outCount].y = mAccumulator.pointers[inIndex].absMTPositionY; -int32_t InputReader::getCurrentKeyCodeState(int32_t deviceId, int32_t deviceClasses, - int32_t keyCode) const { - { // acquire exported state lock - AutoMutex _l(mExportedStateLock); + mCurrentTouch.pointers[outCount].touchMajor = + mAccumulator.pointers[inIndex].absMTTouchMajor; + mCurrentTouch.pointers[outCount].touchMinor = + (fields & Accumulator::FIELD_ABS_MT_TOUCH_MINOR) != 0 + ? mAccumulator.pointers[inIndex].absMTTouchMinor + : mAccumulator.pointers[inIndex].absMTTouchMajor; - if (mExportedVirtualKeyCode == keyCode) { - return AKEY_STATE_VIRTUAL; + mCurrentTouch.pointers[outCount].toolMajor = + mAccumulator.pointers[inIndex].absMTWidthMajor; + mCurrentTouch.pointers[outCount].toolMinor = + (fields & Accumulator::FIELD_ABS_MT_WIDTH_MINOR) != 0 + ? mAccumulator.pointers[inIndex].absMTWidthMinor + : mAccumulator.pointers[inIndex].absMTWidthMajor; + + mCurrentTouch.pointers[outCount].orientation = + (fields & Accumulator::FIELD_ABS_MT_ORIENTATION) != 0 + ? mAccumulator.pointers[inIndex].absMTOrientation : 0; + + // Derive an approximation of pressure and size. + // FIXME assignment of pressure may be incorrect, probably better to let + // pressure = touch / width. Later on we pass width to MotionEvent as a size, which + // isn't quite right either. Should be using touch for that. + mCurrentTouch.pointers[outCount].pressure = mAccumulator.pointers[inIndex].absMTTouchMajor; + mCurrentTouch.pointers[outCount].size = mAccumulator.pointers[inIndex].absMTWidthMajor; + + if (havePointerIds) { + if (fields & Accumulator:: + FIELD_ABS_MT_TRACKING_ID) { + uint32_t id = uint32_t(mAccumulator.pointers[inIndex].absMTTrackingId); + + if (id > MAX_POINTER_ID) { +#if DEBUG_POINTERS + LOGD("Pointers: Ignoring driver provided pointer id %d because " + "it is larger than max supported id %d for optimizations", + id, MAX_POINTER_ID); +#endif + havePointerIds = false; + } + else { + mCurrentTouch.pointers[outCount].id = id; + mCurrentTouch.idToIndex[id] = outCount; + mCurrentTouch.idBits.markBit(id); + } + } else { + havePointerIds = false; + } } - } // release exported state lock - return mEventHub->getKeyCodeState(deviceId, deviceClasses, keyCode); -} + outCount += 1; + } -int32_t InputReader::getCurrentSwitchState(int32_t deviceId, int32_t deviceClasses, - int32_t sw) const { - return mEventHub->getSwitchState(deviceId, deviceClasses, sw); -} + mCurrentTouch.pointerCount = outCount; -bool InputReader::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { - return mEventHub->hasKeys(numCodes, keyCodes, outFlags); + syncTouch(when, havePointerIds); } +void MultiTouchInputMapper::configureAxes() { + TouchInputMapper::configureAxes(); -// --- InputReaderThread --- + // The axes are aliased to take into account the manner in which they are presented + // as part of the TouchData during the sync. + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_X, & mAxes.x); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_Y, & mAxes.y); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MAJOR, & mAxes.touchMajor); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MINOR, & mAxes.touchMinor); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MAJOR, & mAxes.toolMajor); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MINOR, & mAxes.toolMinor); + getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_ORIENTATION, & mAxes.orientation); -InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) : - Thread(/*canCallJava*/ true), mReader(reader) { -} + if (! mAxes.touchMinor.valid) { + mAxes.touchMinor = mAxes.touchMajor; + } -InputReaderThread::~InputReaderThread() { -} + if (! mAxes.toolMinor.valid) { + mAxes.toolMinor = mAxes.toolMajor; + } -bool InputReaderThread::threadLoop() { - mReader->loopOnce(); - return true; + mAxes.pressure = mAxes.touchMajor; + mAxes.size = mAxes.toolMajor; } + } // namespace android diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index 8bd582353d01..2e20268b3ef4 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -33,6 +33,7 @@ commonSources:= \ SharedBuffer.cpp \ Static.cpp \ StopWatch.cpp \ + StreamingZipInflater.cpp \ String8.cpp \ String16.cpp \ StringArray.cpp \ @@ -131,4 +132,4 @@ endif # team really wants is to build the stuff defined by this makefile. ifeq (,$(ONE_SHOT_MAKEFILE)) include $(call first-makefiles-under,$(LOCAL_PATH)) -endif
\ No newline at end of file +endif diff --git a/libs/utils/Asset.cpp b/libs/utils/Asset.cpp index 42951237d627..cef7db492e55 100644 --- a/libs/utils/Asset.cpp +++ b/libs/utils/Asset.cpp @@ -24,6 +24,7 @@ #include <utils/Asset.h> #include <utils/Atomic.h> #include <utils/FileMap.h> +#include <utils/StreamingZipInflater.h> #include <utils/ZipUtils.h> #include <utils/ZipFileRO.h> #include <utils/Log.h> @@ -659,7 +660,7 @@ const void* _FileAsset::ensureAlignment(FileMap* map) */ _CompressedAsset::_CompressedAsset(void) : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0), - mMap(NULL), mFd(-1), mBuf(NULL) + mMap(NULL), mFd(-1), mZipInflater(NULL), mBuf(NULL) { } @@ -698,6 +699,10 @@ status_t _CompressedAsset::openChunk(int fd, off_t offset, mFd = fd; assert(mBuf == NULL); + if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) { + mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen); + } + return NO_ERROR; } @@ -724,6 +729,9 @@ status_t _CompressedAsset::openChunk(FileMap* dataMap, int compressionMethod, mUncompressedLen = uncompressedLen; assert(mOffset == 0); + if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) { + mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen); + } return NO_ERROR; } @@ -739,26 +747,29 @@ ssize_t _CompressedAsset::read(void* buf, size_t count) assert(mOffset >= 0 && mOffset <= mUncompressedLen); - // TODO: if mAccessMode == ACCESS_STREAMING, use zlib more cleverly + /* If we're relying on a streaming inflater, go through that */ + if (mZipInflater) { + actual = mZipInflater->read(buf, count); + } else { + if (mBuf == NULL) { + if (getBuffer(false) == NULL) + return -1; + } + assert(mBuf != NULL); - if (mBuf == NULL) { - if (getBuffer(false) == NULL) - return -1; - } - assert(mBuf != NULL); + /* adjust count if we're near EOF */ + maxLen = mUncompressedLen - mOffset; + if (count > maxLen) + count = maxLen; - /* adjust count if we're near EOF */ - maxLen = mUncompressedLen - mOffset; - if (count > maxLen) - count = maxLen; + if (!count) + return 0; - if (!count) - return 0; - - /* copy from buffer */ - //printf("comp buf read\n"); - memcpy(buf, (char*)mBuf + mOffset, count); - actual = count; + /* copy from buffer */ + //printf("comp buf read\n"); + memcpy(buf, (char*)mBuf + mOffset, count); + actual = count; + } mOffset += actual; return actual; @@ -780,6 +791,9 @@ off_t _CompressedAsset::seek(off_t offset, int whence) if (newPosn == (off_t) -1) return newPosn; + if (mZipInflater) { + mZipInflater->seekAbsolute(newPosn); + } mOffset = newPosn; return mOffset; } @@ -793,10 +807,12 @@ void _CompressedAsset::close(void) mMap->release(); mMap = NULL; } - if (mBuf != NULL) { - delete[] mBuf; - mBuf = NULL; - } + + delete[] mBuf; + mBuf = NULL; + + delete mZipInflater; + mZipInflater = NULL; if (mFd > 0) { ::close(mFd); @@ -817,12 +833,6 @@ const void* _CompressedAsset::getBuffer(bool wordAligned) if (mBuf != NULL) return mBuf; - if (mUncompressedLen > UNCOMPRESS_DATA_MAX) { - LOGD("Data exceeds UNCOMPRESS_DATA_MAX (%ld vs %d)\n", - (long) mUncompressedLen, UNCOMPRESS_DATA_MAX); - goto bail; - } - /* * Allocate a buffer and read the file into it. */ @@ -853,7 +863,13 @@ const void* _CompressedAsset::getBuffer(bool wordAligned) goto bail; } - /* success! */ + /* + * Success - now that we have the full asset in RAM we + * no longer need the streaming inflater + */ + delete mZipInflater; + mZipInflater = NULL; + mBuf = buf; buf = NULL; diff --git a/libs/utils/StreamingZipInflater.cpp b/libs/utils/StreamingZipInflater.cpp new file mode 100644 index 000000000000..7ebde78cd85b --- /dev/null +++ b/libs/utils/StreamingZipInflater.cpp @@ -0,0 +1,225 @@ +/* + * 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 "szipinf" +#include <utils/Log.h> + +#include <utils/FileMap.h> +#include <utils/StreamingZipInflater.h> +#include <string.h> +#include <stddef.h> +#include <assert.h> + +static inline size_t min_of(size_t a, size_t b) { return (a < b) ? a : b; } + +using namespace android; + +/* + * Streaming access to compressed asset data in an open fd + */ +StreamingZipInflater::StreamingZipInflater(int fd, off_t compDataStart, + size_t uncompSize, size_t compSize) { + mFd = fd; + mDataMap = NULL; + mInFileStart = compDataStart; + mOutTotalSize = uncompSize; + mInTotalSize = compSize; + + mInBufSize = StreamingZipInflater::INPUT_CHUNK_SIZE; + mInBuf = new uint8_t[mInBufSize]; + + mOutBufSize = StreamingZipInflater::OUTPUT_CHUNK_SIZE; + mOutBuf = new uint8_t[mOutBufSize]; + + initInflateState(); +} + +/* + * Streaming access to compressed data held in an mmapped region of memory + */ +StreamingZipInflater::StreamingZipInflater(FileMap* dataMap, size_t uncompSize) { + mFd = -1; + mDataMap = dataMap; + mOutTotalSize = uncompSize; + mInTotalSize = dataMap->getDataLength(); + + mInBuf = (uint8_t*) dataMap->getDataPtr(); + mInBufSize = mInTotalSize; + + mOutBufSize = StreamingZipInflater::OUTPUT_CHUNK_SIZE; + mOutBuf = new uint8_t[mOutBufSize]; + + initInflateState(); +} + +StreamingZipInflater::~StreamingZipInflater() { + // tear down the in-flight zip state just in case + ::inflateEnd(&mInflateState); + + if (mDataMap == NULL) { + delete [] mInBuf; + } + delete [] mOutBuf; +} + +void StreamingZipInflater::initInflateState() { + LOGD("Initializing inflate state"); + + memset(&mInflateState, 0, sizeof(mInflateState)); + mInflateState.zalloc = Z_NULL; + mInflateState.zfree = Z_NULL; + mInflateState.opaque = Z_NULL; + mInflateState.next_in = (Bytef*)mInBuf; + mInflateState.next_out = (Bytef*) mOutBuf; + mInflateState.avail_out = mOutBufSize; + mInflateState.data_type = Z_UNKNOWN; + + mOutLastDecoded = mOutDeliverable = mOutCurPosition = 0; + mInNextChunkOffset = 0; + mStreamNeedsInit = true; + + if (mDataMap == NULL) { + ::lseek(mFd, mInFileStart, SEEK_SET); + mInflateState.avail_in = 0; // set when a chunk is read in + } else { + mInflateState.avail_in = mInBufSize; + } +} + +/* + * Basic approach: + * + * 1. If we have undelivered uncompressed data, send it. At this point + * either we've satisfied the request, or we've exhausted the available + * output data in mOutBuf. + * + * 2. While we haven't sent enough data to satisfy the request: + * 0. if the request is for more data than exists, bail. + * a. if there is no input data to decode, read some into the input buffer + * and readjust the z_stream input pointers + * b. point the output to the start of the output buffer and decode what we can + * c. deliver whatever output data we can + */ +ssize_t StreamingZipInflater::read(void* outBuf, size_t count) { + uint8_t* dest = (uint8_t*) outBuf; + size_t bytesRead = 0; + size_t toRead = min_of(count, size_t(mOutTotalSize - mOutCurPosition)); + while (toRead > 0) { + // First, write from whatever we already have decoded and ready to go + size_t deliverable = min_of(toRead, mOutLastDecoded - mOutDeliverable); + if (deliverable > 0) { + if (outBuf != NULL) memcpy(dest, mOutBuf + mOutDeliverable, deliverable); + mOutDeliverable += deliverable; + mOutCurPosition += deliverable; + dest += deliverable; + bytesRead += deliverable; + toRead -= deliverable; + } + + // need more data? time to decode some. + if (toRead > 0) { + // if we don't have any data to decode, read some in. If we're working + // from mmapped data this won't happen, because the clipping to total size + // will prevent reading off the end of the mapped input chunk. + if (mInflateState.avail_in == 0) { + int err = readNextChunk(); + if (err < 0) { + LOGE("Unable to access asset data: %d", err); + if (!mStreamNeedsInit) { + ::inflateEnd(&mInflateState); + initInflateState(); + } + return -1; + } + } + // we know we've drained whatever is in the out buffer now, so just + // start from scratch there, reading all the input we have at present. + mInflateState.next_out = (Bytef*) mOutBuf; + mInflateState.avail_out = mOutBufSize; + + /* + LOGD("Inflating to outbuf: avail_in=%u avail_out=%u next_in=%p next_out=%p", + mInflateState.avail_in, mInflateState.avail_out, + mInflateState.next_in, mInflateState.next_out); + */ + int result = Z_OK; + if (mStreamNeedsInit) { + LOGI("Initializing zlib to inflate"); + result = inflateInit2(&mInflateState, -MAX_WBITS); + mStreamNeedsInit = false; + } + if (result == Z_OK) result = ::inflate(&mInflateState, Z_SYNC_FLUSH); + if (result < 0) { + // Whoops, inflation failed + LOGE("Error inflating asset: %d", result); + ::inflateEnd(&mInflateState); + initInflateState(); + return -1; + } else { + if (result == Z_STREAM_END) { + // we know we have to have reached the target size here and will + // not try to read any further, so just wind things up. + ::inflateEnd(&mInflateState); + } + + // Note how much data we got, and off we go + mOutDeliverable = 0; + mOutLastDecoded = mOutBufSize - mInflateState.avail_out; + } + } + } + return bytesRead; +} + +int StreamingZipInflater::readNextChunk() { + assert(mDataMap == NULL); + + if (mInNextChunkOffset < mInTotalSize) { + size_t toRead = min_of(mInBufSize, mInTotalSize - mInNextChunkOffset); + if (toRead > 0) { + ssize_t didRead = ::read(mFd, mInBuf, toRead); + //LOGD("Reading input chunk, size %08x didread %08x", toRead, didRead); + if (didRead < 0) { + // TODO: error + LOGE("Error reading asset data"); + return didRead; + } else { + mInNextChunkOffset += didRead; + mInflateState.next_in = (Bytef*) mInBuf; + mInflateState.avail_in = didRead; + } + } + } + return 0; +} + +// seeking backwards requires uncompressing fom the beginning, so is very +// expensive. seeking forwards only requires uncompressing from the current +// position to the destination. +off_t StreamingZipInflater::seekAbsolute(off_t absoluteInputPosition) { + if (absoluteInputPosition < mOutCurPosition) { + // rewind and reprocess the data from the beginning + if (!mStreamNeedsInit) { + ::inflateEnd(&mInflateState); + } + initInflateState(); + read(NULL, absoluteInputPosition); + } else if (absoluteInputPosition > mOutCurPosition) { + read(NULL, absoluteInputPosition - mOutCurPosition); + } + // else if the target position *is* our current position, do nothing + return absoluteInputPosition; +} diff --git a/media/java/android/media/BassBoost.java b/media/java/android/media/BassBoost.java index ef4ce059d1c7..75c2c88d21e9 100644 --- a/media/java/android/media/BassBoost.java +++ b/media/java/android/media/BassBoost.java @@ -19,17 +19,19 @@ package android.media; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.media.AudioEffect; import android.os.Bundle; import android.util.Log; + import java.nio.ByteOrder; import java.nio.ByteBuffer; import java.nio.CharBuffer; +import java.util.StringTokenizer; -import android.media.AudioEffect; /** * Bass boost is an audio effect to boost or amplify low frequencies of the sound. It is comparable - * to an simple equalizer but limited to one band amplification in the low frequency range. + * to a simple equalizer but limited to one band amplification in the low frequency range. * <p>An application creates a BassBoost object to instantiate and control a bass boost engine * in the audio framework. * <p>The methods, parameter types and units exposed by the BassBoost implementation are directly @@ -210,4 +212,82 @@ public class BassBoost extends AudioEffect { } } } + + /** + * The Settings class regroups all bass boost parameters. It is used in + * conjuntion with getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public short strength; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + int tokens = st.countTokens(); + if (st.countTokens() != 3) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("BassBoost")) { + throw new IllegalArgumentException( + "invalid settings for BassBoost: " + key); + } + try { + key = st.nextToken(); + if (!key.equals("strength")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + strength = Short.parseShort(st.nextToken()); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + String str = new String ( + "BassBoost"+ + ";strength="+Short.toString(strength) + ); + return str; + } + }; + + + /** + * Gets the bass boost properties. This method is useful when a snapshot of current + * bass boost settings must be saved by the application. + * @return a BassBoost.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public BassBoost.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + Settings settings = new Settings(); + short[] value = new short[1]; + checkStatus(getParameter(PARAM_STRENGTH, value)); + settings.strength = value[0]; + return settings; + } + + /** + * Sets the bass boost properties. This method is useful when bass boost settings have to + * be applied from a previous backup. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(BassBoost.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_STRENGTH, settings.strength)); + } } diff --git a/media/java/android/media/EnvironmentalReverb.java b/media/java/android/media/EnvironmentalReverb.java index 88230fc51f5d..3cc84527c15a 100644 --- a/media/java/android/media/EnvironmentalReverb.java +++ b/media/java/android/media/EnvironmentalReverb.java @@ -19,12 +19,13 @@ package android.media; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.media.AudioEffect; import android.os.Bundle; import android.util.Log; + import java.nio.ByteOrder; import java.nio.ByteBuffer; - -import android.media.AudioEffect; +import java.util.StringTokenizer; /** * A sound generated within a room travels in many directions. The listener first hears the @@ -107,6 +108,9 @@ public class EnvironmentalReverb extends AudioEffect { */ public static final int PARAM_DENSITY = 9; + // used by setProperties()/getProperties + private static final int PARAM_PROPERTIES = 10; + /** * Registered listener for parameter changes */ @@ -142,7 +146,6 @@ public class EnvironmentalReverb extends AudioEffect { public EnvironmentalReverb(int priority, int audioSession) throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { super(EFFECT_TYPE_ENV_REVERB, EFFECT_TYPE_NULL, priority, audioSession); - Log.e(TAG, "contructor"); } /** @@ -501,4 +504,169 @@ public class EnvironmentalReverb extends AudioEffect { } } } + + /** + * The Settings class regroups all environmental reverb parameters. It is used in + * conjuntion with getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public short roomLevel; + public short roomHFLevel; + public int decayTime; + public short decayHFRatio; + public short reflectionsLevel; + public int reflectionsDelay; + public short reverbLevel; + public int reverbDelay; + public short diffusion; + public short density; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + int tokens = st.countTokens(); + if (st.countTokens() != 21) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("EnvironmentalReverb")) { + throw new IllegalArgumentException( + "invalid settings for EnvironmentalReverb: " + key); + } + + try { + key = st.nextToken(); + if (!key.equals("roomLevel")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + roomLevel = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("roomHFLevel")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + roomHFLevel = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("decayTime")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + decayTime = Integer.parseInt(st.nextToken()); + key = st.nextToken(); + if (!key.equals("decayHFRatio")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + decayHFRatio = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("reflectionsLevel")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + reflectionsLevel = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("reflectionsDelay")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + reflectionsDelay = Integer.parseInt(st.nextToken()); + key = st.nextToken(); + if (!key.equals("reverbLevel")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + reverbLevel = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("reverbDelay")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + reverbDelay = Integer.parseInt(st.nextToken()); + key = st.nextToken(); + if (!key.equals("diffusion")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + diffusion = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("density")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + density = Short.parseShort(st.nextToken()); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + return new String ( + "EnvironmentalReverb"+ + ";roomLevel="+Short.toString(roomLevel)+ + ";roomHFLevel="+Short.toString(roomHFLevel)+ + ";decayTime="+Integer.toString(decayTime)+ + ";decayHFRatio="+Short.toString(decayHFRatio)+ + ";reflectionsLevel="+Short.toString(reflectionsLevel)+ + ";reflectionsDelay="+Integer.toString(reflectionsDelay)+ + ";reverbLevel="+Short.toString(reverbLevel)+ + ";reverbDelay="+Integer.toString(reverbDelay)+ + ";diffusion="+Short.toString(diffusion)+ + ";density="+Short.toString(density) + ); + } + }; + + // Keep this in sync with sizeof(s_reverb_settings) defined in + // frameworks/base/include/media/EffectEnvironmentalReverbApi.h + static private int PROPERTY_SIZE = 26; + + /** + * Gets the environmental reverb properties. This method is useful when a snapshot of current + * reverb settings must be saved by the application. + * @return an EnvironmentalReverb.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public EnvironmentalReverb.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[PROPERTY_SIZE]; + checkStatus(getParameter(PARAM_PROPERTIES, param)); + Settings settings = new Settings(); + settings.roomLevel = byteArrayToShort(param, 0); + settings.roomHFLevel = byteArrayToShort(param, 2); + settings.decayTime = byteArrayToInt(param, 4); + settings.decayHFRatio = byteArrayToShort(param, 8); + settings.reflectionsLevel = byteArrayToShort(param, 10); + settings.reflectionsDelay = byteArrayToInt(param, 12); + settings.reverbLevel = byteArrayToShort(param, 16); + settings.reverbDelay = byteArrayToInt(param, 18); + settings.diffusion = byteArrayToShort(param, 22); + settings.density = byteArrayToShort(param, 24); + return settings; + } + + /** + * Sets the environmental reverb properties. This method is useful when reverb settings have to + * be applied from a previous backup. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(EnvironmentalReverb.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + + byte[] param = concatArrays(shortToByteArray(settings.roomLevel), + shortToByteArray(settings.roomHFLevel), + intToByteArray(settings.decayTime), + shortToByteArray(settings.decayHFRatio), + shortToByteArray(settings.reflectionsLevel), + intToByteArray(settings.reflectionsDelay), + shortToByteArray(settings.reverbLevel), + intToByteArray(settings.reverbDelay), + shortToByteArray(settings.diffusion), + shortToByteArray(settings.density)); + + checkStatus(setParameter(PARAM_PROPERTIES, param)); + } } diff --git a/media/java/android/media/Equalizer.java b/media/java/android/media/Equalizer.java index 082f69416fb4..21c37bb3dec8 100644 --- a/media/java/android/media/Equalizer.java +++ b/media/java/android/media/Equalizer.java @@ -19,13 +19,15 @@ package android.media; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.media.AudioEffect; import android.os.Bundle; import android.util.Log; + import java.nio.ByteOrder; import java.nio.ByteBuffer; import java.nio.CharBuffer; +import java.util.StringTokenizer; -import android.media.AudioEffect; /** * An Equalizer is used to alter the frequency response of a particular music source or of the main @@ -87,12 +89,19 @@ public class Equalizer extends AudioEffect { * Request preset name. Parameter ID for OnParameterChangeListener */ public static final int PARAM_GET_PRESET_NAME = 8; + // used by setProperties()/getProperties + private static final int PARAM_PROPERTIES = 9; /** * maximum size for perset name */ public static final int PARAM_STRING_SIZE_MAX = 32; /** + * Number of bands implemented by Equalizer engine + */ + private short mNumBands = 0; + + /** * Number of presets implemented by Equalizer engine */ private int mNumPresets; @@ -136,6 +145,8 @@ public class Equalizer extends AudioEffect { UnsupportedOperationException, RuntimeException { super(EFFECT_TYPE_EQUALIZER, EFFECT_TYPE_NULL, priority, audioSession); + getNumberOfBands(); + mNumPresets = (int)getNumberOfPresets(); if (mNumPresets != 0) { @@ -150,7 +161,6 @@ public class Equalizer extends AudioEffect { while (value[length] != 0) length++; try { mPresetNames[i] = new String(value, 0, length, "ISO-8859-1"); - Log.e(TAG, "preset #: "+i+" name: "+mPresetNames[i]+" length: "+length); } catch (java.io.UnsupportedEncodingException e) { Log.e(TAG, "preset name decode error"); } @@ -167,11 +177,15 @@ public class Equalizer extends AudioEffect { */ public short getNumberOfBands() throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + if (mNumBands != 0) { + return mNumBands; + } int[] param = new int[1]; param[0] = PARAM_NUM_BANDS; short[] value = new short[1]; checkStatus(getParameter(param, value)); - return value[0]; + mNumBands = value[0]; + return mNumBands; } /** @@ -440,4 +454,120 @@ public class Equalizer extends AudioEffect { } } + /** + * The Settings class regroups all equalizer parameters. It is used in + * conjuntion with getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public short curPreset; + public short numBands = 0; + public short[] bandLevels = null; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + int tokens = st.countTokens(); + if (st.countTokens() < 5) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("Equalizer")) { + throw new IllegalArgumentException( + "invalid settings for Equalizer: " + key); + } + try { + key = st.nextToken(); + if (!key.equals("curPreset")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + curPreset = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("numBands")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + numBands = Short.parseShort(st.nextToken()); + if (st.countTokens() != numBands*2) { + throw new IllegalArgumentException("settings: " + settings); + } + bandLevels = new short[numBands]; + for (int i = 0; i < numBands; i++) { + key = st.nextToken(); + if (!key.equals("band"+(i+1)+"Level")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + bandLevels[i] = Short.parseShort(st.nextToken()); + } + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + + String str = new String ( + "Equalizer"+ + ";curPreset="+Short.toString(curPreset)+ + ";numBands="+Short.toString(numBands) + ); + for (int i = 0; i < numBands; i++) { + str = str.concat(";band"+(i+1)+"Level="+Short.toString(bandLevels[i])); + } + return str; + } + }; + + + /** + * Gets the equalizer properties. This method is useful when a snapshot of current + * equalizer settings must be saved by the application. + * @return an Equalizer.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public Equalizer.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[4 + mNumBands * 2]; + checkStatus(getParameter(PARAM_PROPERTIES, param)); + Settings settings = new Settings(); + settings.curPreset = byteArrayToShort(param, 0); + settings.numBands = byteArrayToShort(param, 2); + settings.bandLevels = new short[mNumBands]; + for (int i = 0; i < mNumBands; i++) { + settings.bandLevels[i] = byteArrayToShort(param, 4 + 2*i); + } + return settings; + } + + /** + * Sets the equalizer properties. This method is useful when equalizer settings have to + * be applied from a previous backup. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(Equalizer.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + if (settings.numBands != settings.bandLevels.length || + settings.numBands != mNumBands) { + throw new IllegalArgumentException("settings invalid band count: " +settings.numBands); + } + + byte[] param = concatArrays(shortToByteArray(settings.curPreset), + shortToByteArray(mNumBands)); + for (int i = 0; i < mNumBands; i++) { + param = concatArrays(param, + shortToByteArray(settings.bandLevels[i])); + } + checkStatus(setParameter(PARAM_PROPERTIES, param)); + } } diff --git a/media/java/android/media/PresetReverb.java b/media/java/android/media/PresetReverb.java index 83a01a48e46e..c7d7037c81c3 100644 --- a/media/java/android/media/PresetReverb.java +++ b/media/java/android/media/PresetReverb.java @@ -19,12 +19,14 @@ package android.media; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.media.AudioEffect; import android.os.Bundle; import android.util.Log; + import java.nio.ByteOrder; import java.nio.ByteBuffer; +import java.util.StringTokenizer; -import android.media.AudioEffect; /** * A sound generated within a room travels in many directions. The listener first hears the @@ -116,7 +118,6 @@ public class PresetReverb extends AudioEffect { public PresetReverb(int priority, int audioSession) throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { super(EFFECT_TYPE_PRESET_REVERB, EFFECT_TYPE_NULL, priority, audioSession); - Log.e(TAG, "contructor"); } /** @@ -144,10 +145,8 @@ public class PresetReverb extends AudioEffect { */ public short getPreset() throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { - int[] param = new int[1]; - param[0] = PARAM_PRESET; short[] value = new short[1]; - checkStatus(getParameter(param, value)); + checkStatus(getParameter(PARAM_PRESET, value)); return value[0]; } @@ -216,4 +215,82 @@ public class PresetReverb extends AudioEffect { } } } + + /** + * The Settings class regroups all preset reverb parameters. It is used in + * conjuntion with getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public short preset; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + int tokens = st.countTokens(); + if (st.countTokens() != 3) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("PresetReverb")) { + throw new IllegalArgumentException( + "invalid settings for PresetReverb: " + key); + } + try { + key = st.nextToken(); + if (!key.equals("preset")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + preset = Short.parseShort(st.nextToken()); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + String str = new String ( + "PresetReverb"+ + ";preset="+Short.toString(preset) + ); + return str; + } + }; + + + /** + * Gets the preset reverb properties. This method is useful when a snapshot of current + * preset reverb settings must be saved by the application. + * @return a PresetReverb.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public PresetReverb.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + Settings settings = new Settings(); + short[] value = new short[1]; + checkStatus(getParameter(PARAM_PRESET, value)); + settings.preset = value[0]; + return settings; + } + + /** + * Sets the preset reverb properties. This method is useful when preset reverb settings have to + * be applied from a previous backup. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(PresetReverb.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_PRESET, settings.preset)); + } } diff --git a/media/java/android/media/Virtualizer.java b/media/java/android/media/Virtualizer.java index 9f7129772fae..2c8909e8dc8e 100644 --- a/media/java/android/media/Virtualizer.java +++ b/media/java/android/media/Virtualizer.java @@ -19,13 +19,15 @@ package android.media; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.media.AudioEffect; import android.os.Bundle; import android.util.Log; + import java.nio.ByteOrder; import java.nio.ByteBuffer; import java.nio.CharBuffer; +import java.util.StringTokenizer; -import android.media.AudioEffect; /** * An audio virtualizer is a general name for an effect to spatialize audio channels. The exact @@ -211,4 +213,82 @@ public class Virtualizer extends AudioEffect { } } } + + /** + * The Settings class regroups all virtualizer parameters. It is used in + * conjuntion with getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public short strength; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + int tokens = st.countTokens(); + if (st.countTokens() != 3) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("Virtualizer")) { + throw new IllegalArgumentException( + "invalid settings for Virtualizer: " + key); + } + try { + key = st.nextToken(); + if (!key.equals("strength")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + strength = Short.parseShort(st.nextToken()); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + String str = new String ( + "Virtualizer"+ + ";strength="+Short.toString(strength) + ); + return str; + } + }; + + + /** + * Gets the virtualizer properties. This method is useful when a snapshot of current + * virtualizer settings must be saved by the application. + * @return a Virtualizer.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public Virtualizer.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + Settings settings = new Settings(); + short[] value = new short[1]; + checkStatus(getParameter(PARAM_STRENGTH, value)); + settings.strength = value[0]; + return settings; + } + + /** + * Sets the virtualizer properties. This method is useful when virtualizer settings have to + * be applied from a previous backup. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(Virtualizer.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_STRENGTH, settings.strength)); + } } diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index beb3dfc5f3bc..8d9f4fee4107 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -549,7 +549,7 @@ static jint android_media_AudioEffect_native_setParameter(JNIEnv *env, p = (effect_param_t *) malloc(sizeof(effect_param_t) + voffset + vsize); memcpy(p->data, lpParam, psize); p->psize = psize; - memcpy(p->data + voffset, lpValue, psize); + memcpy(p->data + voffset, lpValue, vsize); p->vsize = vsize; lStatus = lpAudioEffect->setParameter(p); @@ -698,8 +698,11 @@ static jint android_media_AudioEffect_native_command(JNIEnv *env, jobject thiz, } } - lStatus = translateError(lpAudioEffect->command(cmdCode, cmdSize, pCmdData, - pReplySize, pReplyData)); + lStatus = translateError(lpAudioEffect->command((uint32_t)cmdCode, + (uint32_t)cmdSize, + pCmdData, + (uint32_t *)pReplySize, + pReplyData)); command_Exit: diff --git a/media/libeffects/factory/EffectsFactory.c b/media/libeffects/factory/EffectsFactory.c index 0be280c2324e..c19a505d670b 100644 --- a/media/libeffects/factory/EffectsFactory.c +++ b/media/libeffects/factory/EffectsFactory.c @@ -73,7 +73,12 @@ int Effect_Process(effect_interface_t self, audio_buffer_t *inBuffer, audio_buff return ret; } -int Effect_Command(effect_interface_t self, int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData) +int Effect_Command(effect_interface_t self, + uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData) { int ret = init(); if (ret < 0) { diff --git a/media/libeffects/lvm/lib/Bass/src/LVDBE_Control.c b/media/libeffects/lvm/lib/Bass/src/LVDBE_Control.c index e66513f03ac9..8cf84b7e3c81 100755 --- a/media/libeffects/lvm/lib/Bass/src/LVDBE_Control.c +++ b/media/libeffects/lvm/lib/Bass/src/LVDBE_Control.c @@ -18,8 +18,8 @@ /**************************************************************************************** $Author: nxp007753 $ - $Revision: 1223 $ - $Date: 2010-07-15 14:27:01 +0200 (Thu, 15 Jul 2010) $ + $Revision: 1315 $ + $Date: 2010-07-23 11:52:08 +0200 (Fri, 23 Jul 2010) $ *****************************************************************************************/ @@ -124,10 +124,11 @@ void LVDBE_SetFilters(LVDBE_Instance_t *pInstance, /* * Setup the high pass filter */ - LoadConst_16(0, /* Clear the history, value 0 */ - (LVM_INT16 *)&pInstance->pData->HPFTaps, /* Destination */ - sizeof(pInstance->pData->HPFTaps)/sizeof(LVM_INT16)); /* Number of words */ - BQ_2I_D32F32Cll_TRC_WRA_01_Init(&pInstance->pCoef->HPFInstance, /* Initialise the filter */ + LoadConst_16(0, /* Clear the history, value 0 */ + (void *)&pInstance->pData->HPFTaps, /* Destination Cast to void: \ + no dereferencing in function*/ + sizeof(pInstance->pData->HPFTaps)/sizeof(LVM_INT16)); /* Number of words */ + BQ_2I_D32F32Cll_TRC_WRA_01_Init(&pInstance->pCoef->HPFInstance, /* Initialise the filter */ &pInstance->pData->HPFTaps, (BQ_C32_Coefs_t *)&LVDBE_HPF_Table[Offset]); @@ -135,10 +136,11 @@ void LVDBE_SetFilters(LVDBE_Instance_t *pInstance, /* * Setup the band pass filter */ - LoadConst_16(0, /* Clear the history, value 0 */ - (LVM_INT16 *)&pInstance->pData->BPFTaps, /* Destination */ - sizeof(pInstance->pData->BPFTaps)/sizeof(LVM_INT16)); /* Number of words */ - BP_1I_D32F32Cll_TRC_WRA_02_Init(&pInstance->pCoef->BPFInstance, /* Initialise the filter */ + LoadConst_16(0, /* Clear the history, value 0 */ + (void *)&pInstance->pData->BPFTaps, /* Destination Cast to void:\ + no dereferencing in function*/ + sizeof(pInstance->pData->BPFTaps)/sizeof(LVM_INT16)); /* Number of words */ + BP_1I_D32F32Cll_TRC_WRA_02_Init(&pInstance->pCoef->BPFInstance, /* Initialise the filter */ &pInstance->pData->BPFTaps, (BP_C32_Coefs_t *)&LVDBE_BPF_Table[Offset]); diff --git a/media/libeffects/lvm/lib/Bundle/src/LVM_Buffers.c b/media/libeffects/lvm/lib/Bundle/src/LVM_Buffers.c index 41785a32d35c..727340013d89 100755 --- a/media/libeffects/lvm/lib/Bundle/src/LVM_Buffers.c +++ b/media/libeffects/lvm/lib/Bundle/src/LVM_Buffers.c @@ -19,8 +19,8 @@ /**************************************************************************************** $Author: nxp007753 $ - $Revision: 1082 $ - $Date: 2010-07-05 12:44:39 +0200 (Mon, 05 Jul 2010) $ + $Revision: 1316 $ + $Date: 2010-07-23 11:53:24 +0200 (Fri, 23 Jul 2010) $ *****************************************************************************************/ diff --git a/media/libeffects/lvm/lib/Bundle/src/LVM_Control.c b/media/libeffects/lvm/lib/Bundle/src/LVM_Control.c index 4667feb63ab7..922f77d2bae1 100755 --- a/media/libeffects/lvm/lib/Bundle/src/LVM_Control.c +++ b/media/libeffects/lvm/lib/Bundle/src/LVM_Control.c @@ -18,8 +18,8 @@ /**************************************************************************************** $Author: nxp007753 $ - $Revision: 1255 $ - $Date: 2010-07-16 17:07:29 +0200 (Fri, 16 Jul 2010) $ + $Revision: 1316 $ + $Date: 2010-07-23 11:53:24 +0200 (Fri, 23 Jul 2010) $ *****************************************************************************************/ @@ -314,8 +314,9 @@ void LVM_SetTrebleBoost(LVM_Instance_t *pInstance, /* * Clear the taps */ - LoadConst_16((LVM_INT16)0, /* Value */ - (LVM_INT16 *)&pInstance->pTE_Taps->TrebleBoost_Taps, /* Destination */ + LoadConst_16((LVM_INT16)0, /* Value */ + (void *)&pInstance->pTE_Taps->TrebleBoost_Taps, /* Destination.\ + Cast to void: no dereferencing in function */ (LVM_UINT16)(sizeof(pInstance->pTE_Taps->TrebleBoost_Taps)/sizeof(LVM_INT16))); /* Number of words */ } } diff --git a/media/libeffects/lvm/lib/Bundle/src/LVM_Init.c b/media/libeffects/lvm/lib/Bundle/src/LVM_Init.c index 7ac5685ea574..323a2a3a6dce 100755 --- a/media/libeffects/lvm/lib/Bundle/src/LVM_Init.c +++ b/media/libeffects/lvm/lib/Bundle/src/LVM_Init.c @@ -18,8 +18,8 @@ /************************************************************************************ $Author: nxp007753 $ - $Revision: 1255 $ - $Date: 2010-07-16 17:07:29 +0200 (Fri, 16 Jul 2010) $ + $Revision: 1316 $ + $Date: 2010-07-23 11:53:24 +0200 (Fri, 23 Jul 2010) $ *************************************************************************************/ diff --git a/media/libeffects/lvm/lib/Common/src/MixSoft_2St_D32C31_SAT.c b/media/libeffects/lvm/lib/Common/src/MixSoft_2St_D32C31_SAT.c index b03f0ba0ff4d..83f95ac6cd14 100755 --- a/media/libeffects/lvm/lib/Common/src/MixSoft_2St_D32C31_SAT.c +++ b/media/libeffects/lvm/lib/Common/src/MixSoft_2St_D32C31_SAT.c @@ -18,9 +18,9 @@ /************************************************************************/ /* */ /* Project:: */ -/* $Author: beq07716 $*/ -/* $Revision: 1000 $*/ -/* $Date: 2010-06-28 13:08:20 +0200 (Mon, 28 Jun 2010) $*/ +/* $Author: nxp007753 $*/ +/* $Revision: 1316 $*/ +/* $Date: 2010-07-23 11:53:24 +0200 (Fri, 23 Jul 2010) $*/ /* */ /************************************************************************/ @@ -51,7 +51,8 @@ void MixSoft_2St_D32C31_SAT( Mix_2St_Cll_t *pInstance, if ((pInstance->Current1 != pInstance->Target1) || (pInstance->Current2 != pInstance->Target2)) { MixSoft_1St_D32C31_WRA( (Mix_1St_Cll_t*) pInstance, src1, dst, n); - MixInSoft_D32C31_SAT( (Mix_1St_Cll_t*) &pInstance->Alpha2, src2, dst, n); + MixInSoft_D32C31_SAT( (void *) &pInstance->Alpha2, /* Cast to void: no dereferencing in function*/ + src2, dst, n); } /****************************************************************************** @@ -61,7 +62,8 @@ void MixSoft_2St_D32C31_SAT( Mix_2St_Cll_t *pInstance, else { if (pInstance->Current1 == 0) - MixSoft_1St_D32C31_WRA( (Mix_1St_Cll_t*) &pInstance->Alpha2, src2, dst, n); + MixSoft_1St_D32C31_WRA( (void *) &pInstance->Alpha2, /* Cast to void: no dereferencing in function*/ + src2, dst, n); else if (pInstance->Current2 == 0) MixSoft_1St_D32C31_WRA( (Mix_1St_Cll_t*) pInstance, src1, dst, n); else diff --git a/media/libeffects/lvm/lib/Eq/src/LVEQNB_Control.c b/media/libeffects/lvm/lib/Eq/src/LVEQNB_Control.c index 88f6fb052f52..dac244911d47 100755 --- a/media/libeffects/lvm/lib/Eq/src/LVEQNB_Control.c +++ b/media/libeffects/lvm/lib/Eq/src/LVEQNB_Control.c @@ -18,8 +18,8 @@ /********************************************************************************** $Author: nxp007753 $ - $Revision: 1223 $ - $Date: 2010-07-15 14:27:01 +0200 (Thu, 15 Jul 2010) $ + $Revision: 1316 $ + $Date: 2010-07-23 11:53:24 +0200 (Fri, 23 Jul 2010) $ ***********************************************************************************/ diff --git a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_BypassMix.c b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_BypassMix.c index 2a83e89fa105..b1d9408c1a05 100755 --- a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_BypassMix.c +++ b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_BypassMix.c @@ -17,9 +17,9 @@ /************************************************************************************ - $Author: nxp007753 $ - $Revision: 1246 $ - $Date: 2010-07-16 11:07:10 +0200 (Fri, 16 Jul 2010) $ + $Author: beq06068 $ + $Revision: 1307 $ + $Date: 2010-07-22 17:41:25 +0200 (Thu, 22 Jul 2010) $ *************************************************************************************/ @@ -90,7 +90,7 @@ LVCS_ReturnStatus_en LVCS_BypassMixInit(LVCS_Handle_t hInstance, */ if ((pParams->OperatingMode == LVCS_ON) && (pInstance->bTimerDone == LVM_TRUE) - && (LVC_Mixer_GetTarget(&pInstance->MSBypassMixer.MixerStream[1]) != 0x7FFF) /* this indicates an off->on transtion */ + && (pInstance->MSTarget1 != 0x7FFF) /* this indicates an off->on transtion */ ) { pInstance->TransitionGain = pParams->EffectLevel; @@ -260,17 +260,15 @@ LVM_INT32 LVCS_MixerCallback(LVCS_Handle_t hInstance, LVM_INT16 CallbackParam) { LVCS_Instance_t *pInstance = (LVCS_Instance_t *)hInstance; - LVM_INT32 Target1; - Target1 = LVC_Mixer_GetTarget(&pInstance->MSBypassMixer.MixerStream[0]); - (void)pGeneralPurpose; + (void)pGeneralPurpose; /* * Off transition has completed in Headphone mode */ if ((pInstance->OutputDevice == LVCS_HEADPHONE) && (pInstance->bInOperatingModeTransition) && - (Target1 == 0x0000)&& /* this indicates an on->off transition */ + (pInstance->MSTarget0 == 0x0000)&& /* this indicates an on->off transition */ (CallbackParam == 0)) { /* Set operating mode to OFF */ @@ -289,7 +287,7 @@ LVM_INT32 LVCS_MixerCallback(LVCS_Handle_t hInstance, if ((pInstance->OutputDevice == LVCS_HEADPHONE) && - (Target1 == 1) && + (pInstance->MSTarget0 == 1) && (pInstance->bTimerDone == LVM_TRUE)){ /* Exit transition state */ diff --git a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Control.c b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Control.c index 5dfca2568bd4..fea44bfca4fb 100755 --- a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Control.c +++ b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Control.c @@ -17,9 +17,9 @@ /************************************************************************************ - $Author: nxp007753 $ - $Revision: 1246 $ - $Date: 2010-07-16 11:07:10 +0200 (Fri, 16 Jul 2010) $ + $Author: beq06068 $ + $Revision: 1307 $ + $Date: 2010-07-22 17:41:25 +0200 (Thu, 22 Jul 2010) $ *************************************************************************************/ @@ -94,9 +94,6 @@ LVCS_ReturnStatus_en LVCS_Control(LVCS_Handle_t hInstance, if (pParams->SampleRate != pInstance->Params.SampleRate) { - LVC_Mixer_VarSlope_SetTimeConstant(&pInstance->MSBypassMixer.MixerStream[0],LVCS_BYPASS_MIXER_TC,pParams->SampleRate,2); - - LVC_Mixer_VarSlope_SetTimeConstant(&pInstance->MSBypassMixer.MixerStream[1],LVCS_BYPASS_MIXER_TC,pParams->SampleRate,2); pInstance->TimerParams.SamplingRate = LVCS_SampleRateTable[pParams->SampleRate]; } @@ -130,6 +127,29 @@ LVCS_ReturnStatus_en LVCS_Control(LVCS_Handle_t hInstance, pInstance->VolCorrect = pLVCS_VolCorrectTable[Offset]; + LVC_Mixer_Init(&pInstance->BypassMix.Mixer_Instance.MixerStream[0],0,0); + + + { + LVM_UINT32 Gain; + const Gain_t *pOutputGainTable = (Gain_t*)&LVCS_OutputGainTable[0]; + Gain = (LVM_UINT32)(pOutputGainTable[Offset].Loss * LVM_MAXINT_16); + Gain = (LVM_UINT32)pOutputGainTable[Offset].UnprocLoss * (Gain >> 15); + Gain=Gain>>15; + /* + * Apply the gain correction and shift, note the result is in Q3.13 format + */ + Gain = (Gain * pInstance->VolCorrect.GainMin) >>12; + + LVC_Mixer_Init(&pInstance->BypassMix.Mixer_Instance.MixerStream[1],0,Gain); + LVC_Mixer_VarSlope_SetTimeConstant(&pInstance->BypassMix.Mixer_Instance.MixerStream[0], + LVCS_BYPASS_MIXER_TC,pParams->SampleRate,2); + LVC_Mixer_VarSlope_SetTimeConstant(&pInstance->BypassMix.Mixer_Instance.MixerStream[1], + LVCS_BYPASS_MIXER_TC,pParams->SampleRate,2); + + } + + err=LVCS_SEnhancerInit(hInstance, pParams); @@ -199,44 +219,15 @@ LVCS_ReturnStatus_en LVCS_Control(LVCS_Handle_t hInstance, /* Change transition bypass mixer settings if needed depending on transition type */ if(pParams->OperatingMode != LVCS_OFF){ - LVM_INT32 Current1; - LVM_INT32 Current2; - - Current1 = LVC_Mixer_GetCurrent(&pInstance->MSBypassMixer.MixerStream[0]); - Current2 = LVC_Mixer_GetCurrent(&pInstance->MSBypassMixer.MixerStream[1]); - - if(pInstance->bInOperatingModeTransition != LVM_TRUE) - { - Current1 = 0x00000000; - Current2 = LVM_MAXINT_16; - } - pInstance->MSBypassMixer.MixerStream[0].CallbackSet = 1; - pInstance->MSBypassMixer.MixerStream[1].CallbackSet = 1; - - LVC_Mixer_Init(&pInstance->MSBypassMixer.MixerStream[0],LVM_MAXINT_16,Current1); - LVC_Mixer_Init(&pInstance->MSBypassMixer.MixerStream[1],0,Current2); + pInstance->MSTarget0=LVM_MAXINT_16; + pInstance->MSTarget1=0; } else { - LVM_INT32 Current1; - LVM_INT32 Current2; - - Current1 = LVC_Mixer_GetCurrent(&pInstance->MSBypassMixer.MixerStream[0]); - Current2 = LVC_Mixer_GetCurrent(&pInstance->MSBypassMixer.MixerStream[1]); - - if(pInstance->bInOperatingModeTransition != LVM_TRUE) - { - Current1 = LVM_MAXINT_16; - Current2 = 0x00000000; - } - pInstance->MSBypassMixer.MixerStream[0].CallbackSet = 1; - pInstance->MSBypassMixer.MixerStream[1].CallbackSet = 1; pInstance->Params.OperatingMode = OperatingModeSave; - LVC_Mixer_Init(&pInstance->MSBypassMixer.MixerStream[0],0x00000000,Current1); - LVC_Mixer_Init(&pInstance->MSBypassMixer.MixerStream[1],LVM_MAXINT_16,Current2); + pInstance->MSTarget1=LVM_MAXINT_16; + pInstance->MSTarget0=0; } - LVC_Mixer_SetTimeConstant(&pInstance->MSBypassMixer.MixerStream[0],LVCS_BYPASS_MIXER_TC,pParams->SampleRate,2); - LVC_Mixer_SetTimeConstant(&pInstance->MSBypassMixer.MixerStream[1],LVCS_BYPASS_MIXER_TC,pParams->SampleRate,2); /* Set transition flag */ diff --git a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Equaliser.c b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Equaliser.c index ca615b07c8a4..7ab6571774a1 100755 --- a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Equaliser.c +++ b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Equaliser.c @@ -17,9 +17,9 @@ /************************************************************************************ - $Author: beq07716 $ - $Revision: 1001 $ - $Date: 2010-06-28 13:23:02 +0200 (Mon, 28 Jun 2010) $ + $Author: nxp007753 $ + $Revision: 1315 $ + $Date: 2010-07-23 11:52:08 +0200 (Fri, 23 Jul 2010) $ *************************************************************************************/ @@ -94,8 +94,9 @@ LVCS_ReturnStatus_en LVCS_EqualiserInit(LVCS_Handle_t hInstance, Coeffs.B1 = (LVM_INT16)-pEqualiserCoefTable[Offset].B1; Coeffs.B2 = (LVM_INT16)-pEqualiserCoefTable[Offset].B2; - LoadConst_16((LVM_INT16)0, /* Value */ - (LVM_INT16 *)&pData->EqualiserBiquadTaps, /* Destination */ + LoadConst_16((LVM_INT16)0, /* Value */ + (void *)&pData->EqualiserBiquadTaps, /* Destination Cast to void:\ + no dereferencing in function*/ (LVM_UINT16)(sizeof(pData->EqualiserBiquadTaps)/sizeof(LVM_INT16))); /* Number of words */ BQ_2I_D16F32Css_TRC_WRA_01_Init(&pCoefficients->EqualiserBiquadInstance, diff --git a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Init.c b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Init.c index 4aa95d3cb83b..f5f7cd0d5741 100755 --- a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Init.c +++ b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Init.c @@ -17,9 +17,9 @@ /************************************************************************************ - $Author: beq07716 $ - $Revision: 1001 $ - $Date: 2010-06-28 13:23:02 +0200 (Mon, 28 Jun 2010) $ + $Author: beq06068 $ + $Revision: 1307 $ + $Date: 2010-07-22 17:41:25 +0200 (Thu, 22 Jul 2010) $ *************************************************************************************/ @@ -159,7 +159,6 @@ LVCS_ReturnStatus_en LVCS_Init(LVCS_Handle_t *phInstance, LVCS_Capabilities_t *pCapabilities) { - LVM_INT16 Offset; LVCS_Instance_t *pInstance; LVCS_VolCorrect_t *pLVCS_VolCorrectTable; @@ -197,30 +196,18 @@ LVCS_ReturnStatus_en LVCS_Init(LVCS_Handle_t *phInstance, pInstance->Params.EffectLevel = 0; pInstance->Params.ReverbLevel = (LVM_UINT16)0x8000; pLVCS_VolCorrectTable = (LVCS_VolCorrect_t*)&LVCS_VolCorrectTable[0]; - Offset = (LVM_INT16)(pInstance->Params.SpeakerType + (pInstance->Params.SourceFormat*(1+LVCS_EX_HEADPHONES))); - pInstance->VolCorrect = pLVCS_VolCorrectTable[Offset]; + pInstance->VolCorrect = pLVCS_VolCorrectTable[0]; pInstance->TransitionGain = 0; + /* These current and target values are intialized again in LVCS_Control.c */ LVC_Mixer_Init(&pInstance->BypassMix.Mixer_Instance.MixerStream[0],0,0); + /* These current and target values are intialized again in LVCS_Control.c */ LVC_Mixer_Init(&pInstance->BypassMix.Mixer_Instance.MixerStream[1],0,0); /* * Initialise the bypass variables */ - pInstance->MSBypassMixer.MixerStream[0].CallbackParam = 0; - pInstance->MSBypassMixer.MixerStream[0].pCallbackHandle = LVM_NULL; - pInstance->MSBypassMixer.MixerStream[0].pCallBack = LVM_NULL; - pInstance->MSBypassMixer.MixerStream[0].CallbackSet = 0; - LVC_Mixer_Init(&pInstance->MSBypassMixer.MixerStream[0],0,0); - LVC_Mixer_SetTimeConstant(&pInstance->MSBypassMixer.MixerStream[0],0,LVM_FS_44100,2); - - - pInstance->MSBypassMixer.MixerStream[1].CallbackParam = 0; - pInstance->MSBypassMixer.MixerStream[1].pCallbackHandle = LVM_NULL; - pInstance->MSBypassMixer.MixerStream[1].pCallBack = LVM_NULL; - pInstance->MSBypassMixer.MixerStream[1].CallbackSet = 0; - LVC_Mixer_Init(&pInstance->MSBypassMixer.MixerStream[1],0,0); - LVC_Mixer_SetTimeConstant(&pInstance->MSBypassMixer.MixerStream[1],0,LVM_FS_44100,2); - + pInstance->MSTarget0=0; + pInstance->MSTarget1=0; pInstance->bInOperatingModeTransition = LVM_FALSE; pInstance->bTimerDone = LVM_FALSE; pInstance->TimerParams.CallBackParam = 0; diff --git a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Private.h b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Private.h index be30829d8331..a97769032580 100755 --- a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Private.h +++ b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_Private.h @@ -17,9 +17,9 @@ /************************************************************************************ - $Author: beq07716 $ - $Revision: 1001 $ - $Date: 2010-06-28 13:23:02 +0200 (Mon, 28 Jun 2010) $ + $Author: beq06068 $ + $Revision: 1307 $ + $Date: 2010-07-22 17:41:25 +0200 (Thu, 22 Jul 2010) $ *************************************************************************************/ @@ -128,7 +128,8 @@ typedef struct LVCS_BypassMix_t BypassMix; /* Bypass mixer configuration */ /* Bypass variable */ - LVMixer3_2St_st MSBypassMixer; /* Bypass mixer used in transitions in MS mode */ + LVM_INT16 MSTarget0; /* Mixer state control variable for smooth transtion */ + LVM_INT16 MSTarget1; /* Mixer state control variable for smooth transtion */ LVM_INT16 bInOperatingModeTransition; /* Operating mode transition flag */ LVM_INT16 bTimerDone; /* Timer completion flag */ LVM_Timer_Params_t TimerParams; /* Timer parameters */ diff --git a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_ReverbGenerator.c b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_ReverbGenerator.c index 2efef84b5ebb..861bde69b8f9 100755 --- a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_ReverbGenerator.c +++ b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_ReverbGenerator.c @@ -17,9 +17,9 @@ /************************************************************************************ - $Author: beq07716 $ - $Revision: 1001 $ - $Date: 2010-06-28 13:23:02 +0200 (Mon, 28 Jun 2010) $ + $Author: nxp007753 $ + $Revision: 1315 $ + $Date: 2010-07-23 11:52:08 +0200 (Fri, 23 Jul 2010) $ *************************************************************************************/ @@ -113,7 +113,7 @@ LVCS_ReturnStatus_en LVCS_ReverbGeneratorInit(LVCS_Handle_t hInstance, Coeffs.B2 = (LVM_INT16)-pReverbCoefTable[Offset].B2; LoadConst_16(0, /* Value */ - (LVM_INT16 *)&pData->ReverbBiquadTaps, /* Destination */ + (void *)&pData->ReverbBiquadTaps, /* Destination Cast to void: no dereferencing in function*/ (LVM_UINT16)(sizeof(pData->ReverbBiquadTaps)/sizeof(LVM_INT16))); /* Number of words */ BQ_2I_D16F16Css_TRC_WRA_01_Init(&pCoefficients->ReverbBiquadInstance, diff --git a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_StereoEnhancer.c b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_StereoEnhancer.c index 4ff471658d1e..b67d824f2a55 100755 --- a/media/libeffects/lvm/lib/StereoWidening/src/LVCS_StereoEnhancer.c +++ b/media/libeffects/lvm/lib/StereoWidening/src/LVCS_StereoEnhancer.c @@ -17,9 +17,9 @@ /************************************************************************************ - $Author: beq07716 $ - $Revision: 1001 $ - $Date: 2010-06-28 13:23:02 +0200 (Mon, 28 Jun 2010) $ + $Author: nxp007753 $ + $Revision: 1315 $ + $Date: 2010-07-23 11:52:08 +0200 (Fri, 23 Jul 2010) $ *************************************************************************************/ @@ -90,7 +90,8 @@ LVCS_ReturnStatus_en LVCS_SEnhancerInit(LVCS_Handle_t hInstance, /* Clear the taps */ LoadConst_16(0, /* Value */ - (LVM_INT16 *)&pData->SEBiquadTapsMid, /* Destination */ + (void *)&pData->SEBiquadTapsMid, /* Destination Cast to void:\ + no dereferencing in function*/ (LVM_UINT16)(sizeof(pData->SEBiquadTapsMid)/sizeof(LVM_UINT16))); /* Number of words */ FO_1I_D16F16Css_TRC_WRA_01_Init(&pCoefficient->SEBiquadInstanceMid, @@ -116,7 +117,8 @@ LVCS_ReturnStatus_en LVCS_SEnhancerInit(LVCS_Handle_t hInstance, /* Clear the taps */ LoadConst_16(0, /* Value */ - (LVM_INT16 *)&pData->SEBiquadTapsSide, /* Destination */ + (void *)&pData->SEBiquadTapsSide, /* Destination Cast to void:\ + no dereferencing in function*/ (LVM_UINT16)(sizeof(pData->SEBiquadTapsSide)/sizeof(LVM_UINT16))); /* Number of words */ diff --git a/media/libeffects/lvm/wrapper/Android.mk b/media/libeffects/lvm/wrapper/Android.mk index 4ebc44399a02..7855dcd4fceb 100644 --- a/media/libeffects/lvm/wrapper/Android.mk +++ b/media/libeffects/lvm/wrapper/Android.mk @@ -1,30 +1,36 @@ -LOCAL_PATH:= $(call my-dir)
-
-# music bundle wrapper
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_ARM_MODE := arm
-
-LOCAL_SRC_FILES:= \
- Bundle/EffectBundle.cpp
-
-LOCAL_MODULE:= libbundlewrapper
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/soundfx
-
-LOCAL_PRELINK_MODULE := false
-
-LOCAL_STATIC_LIBRARIES += libmusicbundle
-
-LOCAL_SHARED_LIBRARIES := \
- libcutils \
- libdl
-
-LOCAL_C_INCLUDES += \
- $(LOCAL_PATH)/Bundle \
- $(LOCAL_PATH)/../lib/Common/lib/ \
- $(LOCAL_PATH)/../lib/Bundle/lib/
-
-
-include $(BUILD_SHARED_LIBRARY)
+LOCAL_PATH:= $(call my-dir) + +# music bundle wrapper +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_ARM_MODE := arm + +LOCAL_SRC_FILES:= \ + Bundle/EffectBundle.cpp + +LOCAL_MODULE:= libbundlewrapper + +LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/soundfx + +LOCAL_PRELINK_MODULE := false + +LOCAL_STATIC_LIBRARIES += libmusicbundle + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + +ifeq ($(TARGET_SIMULATOR),true) +LOCAL_LDLIBS += -ldl +else +LOCAL_SHARED_LIBRARIES += libdl +endif + + +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/Bundle \ + $(LOCAL_PATH)/../lib/Common/lib/ \ + $(LOCAL_PATH)/../lib/Bundle/lib/ + + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp index 6043dd5ae7f5..4c3ebcae1a3b 100644 --- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp +++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp @@ -1625,7 +1625,7 @@ int BassBoost_getParameter(EffectContext *pContext, //LOGV("\tBassBoost_getParameter start"); switch (param){ - case BASSBOOST_PARAM_STRENGTH_SUP: + case BASSBOOST_PARAM_STRENGTH_SUPPORTED: case BASSBOOST_PARAM_STRENGTH: if (*pValueSize != sizeof(int16_t)){ LOGV("\tLVM_ERROR : BassBoost_getParameter() invalid pValueSize2 %d", *pValueSize); @@ -1640,10 +1640,10 @@ int BassBoost_getParameter(EffectContext *pContext, } switch (param){ - case BASSBOOST_PARAM_STRENGTH_SUP: + case BASSBOOST_PARAM_STRENGTH_SUPPORTED: *(uint32_t *)pValue = 1; - //LOGV("\tBassBoost_getParameter() BASSBOOST_PARAM_STRENGTH_SUP Value is %d", + //LOGV("\tBassBoost_getParameter() BASSBOOST_PARAM_STRENGTH_SUPPORTED Value is %d", // *(uint32_t *)pValue); break; @@ -1735,7 +1735,7 @@ int Virtualizer_getParameter(EffectContext *pContext, //LOGV("\tVirtualizer_getParameter start"); switch (param){ - case VIRTUALIZER_PARAM_STRENGTH_SUP: + case VIRTUALIZER_PARAM_STRENGTH_SUPPORTED: case VIRTUALIZER_PARAM_STRENGTH: if (*pValueSize != sizeof(int16_t)){ LOGV("\tLVM_ERROR : Virtualizer_getParameter() invalid pValueSize2 %d",*pValueSize); @@ -1750,10 +1750,10 @@ int Virtualizer_getParameter(EffectContext *pContext, } switch (param){ - case VIRTUALIZER_PARAM_STRENGTH_SUP: + case VIRTUALIZER_PARAM_STRENGTH_SUPPORTED: *(uint32_t *)pValue = 1; - //LOGV("\tVirtualizer_getParameter() VIRTUALIZER_PARAM_STRENGTH_SUP Value is %d", + //LOGV("\tVirtualizer_getParameter() VIRTUALIZER_PARAM_STRENGTH_SUPPORTED Value is %d", // *(uint32_t *)pValue); break; @@ -1876,6 +1876,14 @@ int Equalizer_getParameter(EffectContext *pContext, case EQ_PARAM_GET_PRESET_NAME: break; + case EQ_PARAM_PROPERTIES: + if (*pValueSize < (2 + FIVEBAND_NUMBANDS) * sizeof(uint16_t)) { + LOGV("\tLVM_ERROR : Equalizer_getParameter() invalid pValueSize 1 %d", *pValueSize); + return -EINVAL; + } + *pValueSize = (2 + FIVEBAND_NUMBANDS) * sizeof(uint16_t); + break; + default: LOGV("\tLVM_ERROR : Equalizer_getParameter unknown param %d", param); return -EINVAL; @@ -1959,6 +1967,16 @@ int Equalizer_getParameter(EffectContext *pContext, // param2, gEqualizerPresets[param2].name, *pValueSize); break; + case EQ_PARAM_PROPERTIES: { + uint16_t *p = (uint16_t *)pValue; + LOGV("\tEqualizer_getParameter() EQ_PARAM_PROPERTIES"); + p[0] = EqualizerGetPreset(pContext); + p[1] = FIVEBAND_NUMBANDS; + for (int i = 0; i < FIVEBAND_NUMBANDS; i++) { + p[2 + i] = EqualizerGetBandLevel(pContext, i); + } + } break; + default: LOGV("\tLVM_ERROR : Equalizer_getParameter() invalid param %d", param); status = -EINVAL; @@ -2330,14 +2348,13 @@ extern "C" int Effect_process(effect_interface_t self, /* Effect Control Interface Implementation: Command */ extern "C" int Effect_command(effect_interface_t self, - int cmdCode, - int cmdSize, + uint32_t cmdCode, + uint32_t cmdSize, void *pCmdData, - int *replySize, + uint32_t *replySize, void *pReplyData){ EffectContext * pContext = (EffectContext *) self; int retsize; - int status = 0; //LOGV("\t\nEffect_command start"); @@ -2371,54 +2388,29 @@ extern "C" int Effect_command(effect_interface_t self, switch (cmdCode){ case EFFECT_CMD_INIT: + if (pReplyData == NULL || *replySize != sizeof(int)){ + LOGV("\tLVM_ERROR, EFFECT_CMD_INIT: ERROR for effect type %d", + pContext->EffectType); + return -EINVAL; + } + *(int *) pReplyData = 0; //LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_INIT start"); if(pContext->EffectType == LVM_BASS_BOOST){ //LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_INIT for LVM_BASS_BOOST"); - - if (pReplyData == NULL || *replySize != sizeof(int)){ - LOGV("\tLVM_ERROR : BassBoost_command cmdCode Case: " - "EFFECT_CMD_INIT: ERROR"); - return -EINVAL; - } - android::BassSetStrength(pContext, 0); } if(pContext->EffectType == LVM_VIRTUALIZER){ //LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_INIT for LVM_VIRTUALIZER"); - - if (pReplyData == NULL || *replySize != sizeof(int)){ - LOGV("\tLVM_ERROR : Virtualizer_command cmdCode Case: " - "EFFECT_CMD_INIT: ERROR"); - return -EINVAL; - } - android::VirtualizerSetStrength(pContext, 0); } if(pContext->EffectType == LVM_EQUALIZER){ //LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_INIT for LVM_EQUALIZER"); - - if (pReplyData == NULL || *replySize != sizeof(int)){ - LOGV("\tLVM_ERROR : Equalizer_command cmdCode Case: " - "EFFECT_CMD_INIT: ERROR"); - return -EINVAL; - } - android::EqualizerSetPreset(pContext, 0); } if(pContext->EffectType == LVM_VOLUME){ //LOGV("\tEffect_command cmdCode Case: " // "EFFECT_CMD_INIT start"); - - if (pReplyData == NULL || *replySize != sizeof(int)){ - LOGV("\tLVM_ERROR : Volume_command cmdCode Case: " - "EFFECT_CMD_INIT: ERROR"); - return -EINVAL; - } - - status = android::VolumeSetVolumeLevel(pContext, 0); - if(status == -EINVAL){ - return -EINVAL; - } + *(int *) pReplyData = android::VolumeSetVolumeLevel(pContext, 0); } break; diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h index 029f843ad757..d009bf926737 100644 --- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h +++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.h @@ -17,7 +17,9 @@ #ifndef ANDROID_EFFECTBUNDLE_H_ #define ANDROID_EFFECTBUNDLE_H_ -#include <media/EffectApi.h> +#include <media/EffectEqualizerApi.h> +#include <media/EffectBassBoostApi.h> +#include <media/EffectVirtualizerApi.h> #include <LVM.h> #if __cplusplus @@ -29,22 +31,11 @@ extern "C" { #define MAX_CALL_SIZE 256 //#define LVM_PCM -//TODO: this should be included from each effect API include -static const effect_uuid_t SL_IID_BASSBOOST_ = { 0x0634f220, 0xddd4, 0x11db, 0xa0fc, - { 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b } }; -const effect_uuid_t * const SL_IID_BASSBOOST = &SL_IID_BASSBOOST_; - -static const effect_uuid_t SL_IID_EQUALIZER_ = { 0x0bed4300, 0xddd6, 0x11db, 0x8f34, - { 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b } }; -const effect_uuid_t * const SL_IID_EQUALIZER = &SL_IID_EQUALIZER_; - -static const effect_uuid_t SL_IID_VIRTUALIZER_ = { 0x37cc2c00, 0xdddd, 0x11db, 0x8577, - { 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b } }; -const effect_uuid_t * const SL_IID_VIRTUALIZER = &SL_IID_VIRTUALIZER_; - +#ifndef OPENSL_ES_H_ static const effect_uuid_t SL_IID_VOLUME_ = { 0x09e8ede0, 0xddde, 0x11db, 0xb4f6, { 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b } }; const effect_uuid_t * const SL_IID_VOLUME = &SL_IID_VOLUME_; +#endif //OPENSL_ES_H_ typedef enum { @@ -112,34 +103,6 @@ struct EffectContext{ BundledEffectContext *pBundledContext; }; -//TODO: this should be included from each effect API include -/* enumerated parameter settings for BassBoost effect */ -typedef enum -{ - BASSBOOST_PARAM_STRENGTH_SUP, // type SLboolean = typedef SLuint32 - BASSBOOST_PARAM_STRENGTH // type SLpermille = typedef SLuint16 -} t_bassboost_params; - -/* enumerated parameter settings for Virtualizer effect */ -typedef enum -{ - VIRTUALIZER_PARAM_STRENGTH_SUP, // type SLboolean = typedef SLuint32 - VIRTUALIZER_PARAM_STRENGTH // type SLpermille = typedef SLuint16 -} t_virtualizer_params; - -/* enumerated parameter settings for Equalizer effect */ -typedef enum -{ - EQ_PARAM_NUM_BANDS, // Gets the number of frequency bands that the equalizer supports. - EQ_PARAM_LEVEL_RANGE, // Returns the minimum and maximum band levels supported. - EQ_PARAM_BAND_LEVEL, // Gets/Sets the gain set for the given equalizer band. - EQ_PARAM_CENTER_FREQ, // Gets the center frequency of the given band. - EQ_PARAM_BAND_FREQ_RANGE, // Gets the frequency range of the given frequency band. - EQ_PARAM_GET_BAND, // Gets the band that has the most effect on the given frequency. - EQ_PARAM_CUR_PRESET, // Gets/Sets the current preset. - EQ_PARAM_GET_NUM_OF_PRESETS, // Gets the total number of presets the equalizer supports. - EQ_PARAM_GET_PRESET_NAME // Gets the preset name based on the index. -} t_equalizer_params; /* enumerated parameter settings for Volume effect */ typedef enum diff --git a/media/libeffects/testlibs/EffectEqualizer.cpp b/media/libeffects/testlibs/EffectEqualizer.cpp index 0eb2bdf4ea75..a71f236aafd8 100644 --- a/media/libeffects/testlibs/EffectEqualizer.cpp +++ b/media/libeffects/testlibs/EffectEqualizer.cpp @@ -551,8 +551,8 @@ extern "C" int Equalizer_process(effect_interface_t self, audio_buffer_t *inBuff return 0; } // end Equalizer_process -extern "C" int Equalizer_command(effect_interface_t self, int cmdCode, int cmdSize, - void *pCmdData, int *replySize, void *pReplyData) { +extern "C" int Equalizer_command(effect_interface_t self, uint32_t cmdCode, uint32_t cmdSize, + void *pCmdData, uint32_t *replySize, void *pReplyData) { android::EqualizerContext * pContext = (android::EqualizerContext *) self; int retsize; diff --git a/media/libeffects/testlibs/EffectReverb.c b/media/libeffects/testlibs/EffectReverb.c index 2ce755897fee..3eb8b2cf8634 100644 --- a/media/libeffects/testlibs/EffectReverb.c +++ b/media/libeffects/testlibs/EffectReverb.c @@ -270,8 +270,8 @@ static int Reverb_Process(effect_interface_t self, audio_buffer_t *inBuffer, aud } -static int Reverb_Command(effect_interface_t self, int cmdCode, int cmdSize, - void *pCmdData, int *replySize, void *pReplyData) { +static int Reverb_Command(effect_interface_t self, uint32_t cmdCode, uint32_t cmdSize, + void *pCmdData, uint32_t *replySize, void *pReplyData) { reverb_module_t *pRvbModule = (reverb_module_t *) self; reverb_object_t *pReverb; int retsize; @@ -688,7 +688,7 @@ int Reverb_getParameter(reverb_object_t *pReverb, int32_t param, size_t *pSize, void *pValue) { int32_t *pValue32; int16_t *pValue16; - t_reverb_properties *pProperties; + t_reverb_settings *pProperties; int32_t i; int32_t temp; int32_t temp2; @@ -727,7 +727,7 @@ int Reverb_getParameter(reverb_object_t *pReverb, int32_t param, size_t *pSize, break; case REVERB_PARAM_PROPERTIES: - size = sizeof(t_reverb_properties); + size = sizeof(t_reverb_settings); break; default: @@ -740,7 +740,7 @@ int Reverb_getParameter(reverb_object_t *pReverb, int32_t param, size_t *pSize, pValue32 = (int32_t *) pValue; pValue16 = (int16_t *) pValue; - pProperties = (t_reverb_properties *) pValue; + pProperties = (t_reverb_settings *) pValue; switch (param) { case REVERB_PARAM_BYPASS: @@ -971,7 +971,7 @@ int Reverb_setParameter(reverb_object_t *pReverb, int32_t param, size_t size, void *pValue) { int32_t value32; int16_t value16; - t_reverb_properties *pProperties; + t_reverb_settings *pProperties; int32_t i; int32_t temp; int32_t temp2; @@ -1019,7 +1019,7 @@ int Reverb_setParameter(reverb_object_t *pReverb, int32_t param, size_t size, break; case REVERB_PARAM_PROPERTIES: - paramSize = sizeof(t_reverb_properties); + paramSize = sizeof(t_reverb_settings); break; default: @@ -1035,7 +1035,7 @@ int Reverb_setParameter(reverb_object_t *pReverb, int32_t param, size_t size, } else if (paramSize == sizeof(int32_t)) { value32 = *(int32_t *) pValue; } else { - pProperties = (t_reverb_properties *) pValue; + pProperties = (t_reverb_settings *) pValue; } pPreset = &pReverb->m_sPreset.m_sPreset[pReverb->m_nNextRoom]; diff --git a/media/libeffects/testlibs/EffectReverb.h b/media/libeffects/testlibs/EffectReverb.h index ee8e3905ef0c..dbcd1922e9f3 100644 --- a/media/libeffects/testlibs/EffectReverb.h +++ b/media/libeffects/testlibs/EffectReverb.h @@ -301,12 +301,23 @@ typedef struct reverb_module_s { *------------------------------------ */ int EffectQueryNumberEffects(uint32_t *pNumEffects); -int EffectQueryEffect(uint32_t index, effect_descriptor_t *pDescriptor); -int EffectCreate(effect_uuid_t *effectUID, int32_t sessionId, int32_t ioId, effect_interface_t *pInterface); +int EffectQueryEffect(uint32_t index, + effect_descriptor_t *pDescriptor); +int EffectCreate(effect_uuid_t *effectUID, + int32_t sessionId, + int32_t ioId, + effect_interface_t *pInterface); int EffectRelease(effect_interface_t interface); -static int Reverb_Process(effect_interface_t self, audio_buffer_t *inBuffer, audio_buffer_t *outBuffer); -static int Reverb_Command(effect_interface_t self, int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData); +static int Reverb_Process(effect_interface_t self, + audio_buffer_t *inBuffer, + audio_buffer_t *outBuffer); +static int Reverb_Command(effect_interface_t self, + uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData); /*------------------------------------ diff --git a/media/libeffects/visualizer/Android.mk b/media/libeffects/visualizer/Android.mk index 82cd92550b95..48b45ff52dde 100644 --- a/media/libeffects/visualizer/Android.mk +++ b/media/libeffects/visualizer/Android.mk @@ -15,7 +15,7 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/soundfx LOCAL_MODULE:= libvisualizer ifeq ($(TARGET_OS)-$(TARGET_SIMULATOR),linux-true) -LOCAL_LDLIBS += -ldlS +LOCAL_LDLIBS += -ldl endif ifneq ($(TARGET_SIMULATOR),true) diff --git a/media/libeffects/visualizer/EffectVisualizer.cpp b/media/libeffects/visualizer/EffectVisualizer.cpp index ec135576b236..8ab57c932345 100644 --- a/media/libeffects/visualizer/EffectVisualizer.cpp +++ b/media/libeffects/visualizer/EffectVisualizer.cpp @@ -272,8 +272,8 @@ extern "C" int Visualizer_process( return 0; } // end Visualizer_process -extern "C" int Visualizer_command(effect_interface_t self, int cmdCode, int cmdSize, - void *pCmdData, int *replySize, void *pReplyData) { +extern "C" int Visualizer_command(effect_interface_t self, uint32_t cmdCode, uint32_t cmdSize, + void *pCmdData, uint32_t *replySize, void *pReplyData) { android::VisualizerContext * pContext = (android::VisualizerContext *)self; int retsize; diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp index df0f73bc87b2..3cdf48a4d51b 100644 --- a/media/libmedia/AudioEffect.cpp +++ b/media/libmedia/AudioEffect.cpp @@ -221,7 +221,11 @@ status_t AudioEffect::setEnabled(bool enabled) return INVALID_OPERATION; } -status_t AudioEffect::command(int32_t cmdCode, int32_t cmdSize, void *cmdData, int32_t *replySize, void *replyData) +status_t AudioEffect::command(uint32_t cmdCode, + uint32_t cmdSize, + void *cmdData, + uint32_t *replySize, + void *replyData) { if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) { return INVALID_OPERATION; @@ -241,8 +245,8 @@ status_t AudioEffect::setParameter(effect_param_t *param) return BAD_VALUE; } - int size = sizeof(int); - int psize = ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + param->vsize; + uint32_t size = sizeof(int); + uint32_t psize = ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + param->vsize; LOGV("setParameter: param: %d, param2: %d", *(int *)param->data, (param->psize == 8) ? *((int *)param->data + 1): -1); @@ -285,7 +289,7 @@ status_t AudioEffect::setParameterCommit() if (mCblk->clientIndex == 0) { return INVALID_OPERATION; } - int size = 0; + uint32_t size = 0; return mIEffect->command(EFFECT_CMD_SET_PARAM_COMMIT, 0, NULL, &size, NULL); } @@ -301,7 +305,7 @@ status_t AudioEffect::getParameter(effect_param_t *param) LOGV("getParameter: param: %d, param2: %d", *(int *)param->data, (param->psize == 8) ? *((int *)param->data + 1): -1); - int psize = sizeof(effect_param_t) + ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + param->vsize; + uint32_t psize = sizeof(effect_param_t) + ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + param->vsize; return mIEffect->command(EFFECT_CMD_GET_PARAM, sizeof(effect_param_t) + param->psize, param, &psize, param); } @@ -350,7 +354,11 @@ void AudioEffect::enableStatusChanged(bool enabled) } } -void AudioEffect::commandExecuted(int cmdCode, int cmdSize, void *cmdData, int replySize, void *replyData) +void AudioEffect::commandExecuted(uint32_t cmdCode, + uint32_t cmdSize, + void *cmdData, + uint32_t replySize, + void *replyData) { if (cmdData == NULL || replyData == NULL) { return; diff --git a/media/libmedia/IEffect.cpp b/media/libmedia/IEffect.cpp index 8e3ac7110d83..a945b978cf85 100644 --- a/media/libmedia/IEffect.cpp +++ b/media/libmedia/IEffect.cpp @@ -59,7 +59,11 @@ public: return reply.readInt32(); } - status_t command(int cmdCode, int cmdSize, void *pCmdData, int *pReplySize, void *pReplyData) + status_t command(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *pReplySize, + void *pReplyData) { LOGV("command"); Parcel data, reply; @@ -136,15 +140,15 @@ status_t BnEffect::onTransact( case COMMAND: { LOGV("COMMAND"); CHECK_INTERFACE(IEffect, data, reply); - int cmdCode = data.readInt32(); - int cmdSize = data.readInt32(); + uint32_t cmdCode = data.readInt32(); + uint32_t cmdSize = data.readInt32(); char *cmd = NULL; if (cmdSize) { cmd = (char *)malloc(cmdSize); data.read(cmd, cmdSize); } - int replySize = data.readInt32(); - int replySz = replySize; + uint32_t replySize = data.readInt32(); + uint32_t replySz = replySize; char *resp = NULL; if (replySize) { resp = (char *)malloc(replySize); diff --git a/media/libmedia/IEffectClient.cpp b/media/libmedia/IEffectClient.cpp index e7659ae2b099..1fa9cbea7eba 100644 --- a/media/libmedia/IEffectClient.cpp +++ b/media/libmedia/IEffectClient.cpp @@ -56,7 +56,11 @@ public: remote()->transact(ENABLE_STATUS_CHANGED, data, &reply, IBinder::FLAG_ONEWAY); } - void commandExecuted(int cmdCode, int cmdSize, void *pCmdData, int replySize, void *pReplyData) + void commandExecuted(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t replySize, + void *pReplyData) { LOGV("commandExecuted"); Parcel data, reply; @@ -108,14 +112,14 @@ status_t BnEffectClient::onTransact( case COMMAND_EXECUTED: { LOGV("COMMAND_EXECUTED"); CHECK_INTERFACE(IEffectClient, data, reply); - int cmdCode = data.readInt32(); - int cmdSize = data.readInt32(); + uint32_t cmdCode = data.readInt32(); + uint32_t cmdSize = data.readInt32(); char *cmd = NULL; if (cmdSize) { cmd = (char *)malloc(cmdSize); data.read(cmd, cmdSize); } - int replySize = data.readInt32(); + uint32_t replySize = data.readInt32(); char *resp = NULL; if (replySize) { resp = (char *)malloc(replySize); diff --git a/media/libmedia/Visualizer.cpp b/media/libmedia/Visualizer.cpp index 47e96e523372..32cdb491e387 100644 --- a/media/libmedia/Visualizer.cpp +++ b/media/libmedia/Visualizer.cpp @@ -184,7 +184,7 @@ status_t Visualizer::getWaveForm(uint8_t *waveform) status_t status = NO_ERROR; if (mEnabled) { - int32_t replySize = mCaptureSize; + uint32_t replySize = mCaptureSize; status_t status = command(VISU_CMD_CAPTURE, 0, NULL, &replySize, waveform); if (replySize == 0) { status = NOT_ENOUGH_DATA; diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp index b56f99734708..f26676da110e 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.cpp +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -981,8 +981,10 @@ status_t StagefrightRecorder::startMPEG4Recording() { totalBitRate += mVideoBitRate; } - reinterpret_cast<MPEG4Writer *>(writer.get())-> - setInterleaveDuration(mInterleaveDurationUs); + if (mInterleaveDurationUs > 0) { + reinterpret_cast<MPEG4Writer *>(writer.get())-> + setInterleaveDuration(mInterleaveDurationUs); + } if (mMaxFileDurationUs != 0) { writer->setMaxFileDuration(mMaxFileDurationUs); diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index b7bde6bdb097..c27cfc81739d 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -23,6 +23,7 @@ #include <media/stagefright/AudioPlayer.h> #include <media/stagefright/MediaDebug.h> #include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> #include <media/stagefright/MediaSource.h> #include <media/stagefright/MetaData.h> diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp index c4a25bc5c3db..baf9f4f11864 100644 --- a/media/libstagefright/MPEG4Writer.cpp +++ b/media/libstagefright/MPEG4Writer.cpp @@ -66,11 +66,11 @@ private: pthread_t mThread; - struct SampleInfo { - size_t size; - int64_t timestampUs; - }; - List<SampleInfo> mSampleInfos; + // mNumSamples is used to track how many samples in mSampleSizes List. + // This is to reduce the cost associated with mSampleSizes.size() call, + // since it is O(n). Ideally, the fix should be in List class. + size_t mNumSamples; + List<size_t> mSampleSizes; bool mSamplesHaveSameSize; List<MediaBuffer *> mChunkSamples; @@ -145,7 +145,7 @@ MPEG4Writer::MPEG4Writer(const char *filename) mOffset(0), mMdatOffset(0), mEstimatedMoovBoxSize(0), - mInterleaveDurationUs(500000) { + mInterleaveDurationUs(1000000) { CHECK(mFile != NULL); } @@ -157,7 +157,7 @@ MPEG4Writer::MPEG4Writer(int fd) mOffset(0), mMdatOffset(0), mEstimatedMoovBoxSize(0), - mInterleaveDurationUs(500000) { + mInterleaveDurationUs(1000000) { CHECK(mFile != NULL); } @@ -916,6 +916,15 @@ status_t MPEG4Writer::Track::makeAVCCodecSpecificData( return OK; } +static bool collectStatisticalData() { + char value[PROPERTY_VALUE_MAX]; + if (property_get("media.stagefright.record-stats", value, NULL) + && (!strcmp(value, "1") || !strcasecmp(value, "true"))) { + return true; + } + return false; +} + void MPEG4Writer::Track::threadEntry() { sp<MetaData> meta = mSource->getFormat(); const char *mime; @@ -935,7 +944,9 @@ void MPEG4Writer::Track::threadEntry() { uint32_t previousSampleSize = 0; // Size of the previous sample int64_t previousPausedDurationUs = 0; sp<MetaData> meta_data; + bool collectStats = collectStatisticalData(); + mNumSamples = 0; status_t err = OK; MediaBuffer *buffer; while (!mDone && (err = mSource->read(&buffer)) == OK) { @@ -1081,8 +1092,8 @@ void MPEG4Writer::Track::threadEntry() { if (is_avc) StripStartcode(copy); - SampleInfo info; - info.size = is_avc + size_t sampleSize; + sampleSize = is_avc #if USE_NALLEN_FOUR ? copy->range_length() + 4 #else @@ -1091,7 +1102,7 @@ void MPEG4Writer::Track::threadEntry() { : copy->range_length(); // Max file size or duration handling - mEstimatedTrackSizeBytes += info.size; + mEstimatedTrackSizeBytes += sampleSize; if (mOwner->exceedsFileSizeLimit()) { mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0); break; @@ -1109,7 +1120,7 @@ void MPEG4Writer::Track::threadEntry() { CHECK(meta_data->findInt64(kKeyTime, ×tampUs)); //////////////////////////////////////////////////////////////////////////////// - if (mSampleInfos.empty()) { + if (mSampleSizes.empty()) { mStartTimestampUs = timestampUs; mOwner->setStartTimestampUs(mStartTimestampUs); } @@ -1126,10 +1137,10 @@ void MPEG4Writer::Track::threadEntry() { mMaxTimeStampUs = timestampUs; } - info.timestampUs = timestampUs; - mSampleInfos.push_back(info); - if (mSampleInfos.size() > 2) { - if (lastDurationUs != info.timestampUs - lastTimestampUs) { + mSampleSizes.push_back(sampleSize); + ++mNumSamples; + if (mNumSamples > 2) { + if (lastDurationUs != timestampUs - lastTimestampUs) { SttsTableEntry sttsEntry(sampleCount, lastDurationUs); mSttsTableEntries.push_back(sttsEntry); sampleCount = 1; @@ -1138,16 +1149,16 @@ void MPEG4Writer::Track::threadEntry() { } } if (mSamplesHaveSameSize) { - if (mSampleInfos.size() >= 2 && previousSampleSize != info.size) { + if (mNumSamples >= 2 && previousSampleSize != sampleSize) { mSamplesHaveSameSize = false; } - previousSampleSize = info.size; + previousSampleSize = sampleSize; } - lastDurationUs = info.timestampUs - lastTimestampUs; - lastTimestampUs = info.timestampUs; + lastDurationUs = timestampUs - lastTimestampUs; + lastTimestampUs = timestampUs; if (isSync != 0) { - mStssTableEntries.push_back(mSampleInfos.size()); + mStssTableEntries.push_back(mNumSamples); } if (mTrackingProgressStatus) { @@ -1178,7 +1189,9 @@ void MPEG4Writer::Track::threadEntry() { } else { if (timestampUs - chunkTimestampUs > interleaveDurationUs) { ++nChunks; - mChunkDurations.push_back(timestampUs - chunkTimestampUs); + if (collectStats) { + mChunkDurations.push_back(timestampUs - chunkTimestampUs); + } if (nChunks == 1 || // First chunk (--(mStscTableEntries.end()))->samplesPerChunk != mChunkSamples.size()) { @@ -1194,14 +1207,14 @@ void MPEG4Writer::Track::threadEntry() { } - if (mSampleInfos.empty()) { + if (mSampleSizes.empty()) { err = UNKNOWN_ERROR; } mOwner->trackProgressStatus(this, -1, err); // Last chunk if (mOwner->numTracks() == 1) { - StscTableEntry stscEntry(1, mSampleInfos.size(), 1); + StscTableEntry stscEntry(1, mNumSamples, 1); mStscTableEntries.push_back(stscEntry); } else if (!mChunkSamples.empty()) { ++nChunks; @@ -1213,7 +1226,7 @@ void MPEG4Writer::Track::threadEntry() { // We don't really know how long the last frame lasts, since // there is no frame time after it, just repeat the previous // frame's duration. - if (mSampleInfos.size() == 1) { + if (mNumSamples == 1) { lastDurationUs = 0; // A single sample's duration } else { ++sampleCount; // Count for the last sample @@ -1222,7 +1235,7 @@ void MPEG4Writer::Track::threadEntry() { mSttsTableEntries.push_back(sttsEntry); mReachedEOS = true; LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames - %s", - count, nZeroLengthFrames, mSampleInfos.size(), is_audio? "audio": "video"); + count, nZeroLengthFrames, mNumSamples, is_audio? "audio": "video"); logStatisticalData(is_audio); } @@ -1284,8 +1297,8 @@ void MPEG4Writer::trackProgressStatus( void MPEG4Writer::Track::findMinAvgMaxSampleDurationMs( int32_t *min, int32_t *avg, int32_t *max) { - CHECK(!mSampleInfos.empty()); - int32_t avgSampleDurationMs = mMaxTimeStampUs / 1000 / mSampleInfos.size(); + CHECK(!mSampleSizes.empty()); + int32_t avgSampleDurationMs = mMaxTimeStampUs / 1000 / mNumSamples; int32_t minSampleDurationMs = 0x7FFFFFFF; int32_t maxSampleDurationMs = 0; for (List<SttsTableEntry>::iterator it = mSttsTableEntries.begin(); @@ -1327,22 +1340,17 @@ void MPEG4Writer::Track::findMinMaxChunkDurations(int64_t *min, int64_t *max) { } void MPEG4Writer::Track::logStatisticalData(bool isAudio) { - if (mMaxTimeStampUs <= 0 || mSampleInfos.empty()) { + if (mMaxTimeStampUs <= 0 || mSampleSizes.empty()) { LOGI("nothing is recorded"); return; } - bool collectStats = false; - char value[PROPERTY_VALUE_MAX]; - if (property_get("media.stagefright.record-stats", value, NULL) - && (!strcmp(value, "1") || !strcasecmp(value, "true"))) { - collectStats = true; - } + bool collectStats = collectStatisticalData(); if (collectStats) { LOGI("%s track - duration %lld us, total %d frames", isAudio? "audio": "video", mMaxTimeStampUs, - mSampleInfos.size()); + mNumSamples); int32_t min, avg, max; findMinAvgMaxSampleDurationMs(&min, &avg, &max); LOGI("min/avg/max sample duration (ms): %d/%d/%d", min, avg, max); @@ -1355,9 +1363,9 @@ void MPEG4Writer::Track::logStatisticalData(bool isAudio) { } int64_t totalBytes = 0; - for (List<SampleInfo>::iterator it = mSampleInfos.begin(); - it != mSampleInfos.end(); ++it) { - totalBytes += it->size; + for (List<size_t>::iterator it = mSampleSizes.begin(); + it != mSampleSizes.end(); ++it) { + totalBytes += (*it); } float bitRate = (totalBytes * 8000000.0) / mMaxTimeStampUs; LOGI("avg bit rate (bps): %.2f", bitRate); @@ -1731,16 +1739,16 @@ void MPEG4Writer::Track::writeTrackHeader( mOwner->beginBox("stsz"); mOwner->writeInt32(0); // version=0, flags=0 if (mSamplesHaveSameSize) { - List<SampleInfo>::iterator it = mSampleInfos.begin(); - mOwner->writeInt32(it->size); // default sample size + List<size_t>::iterator it = mSampleSizes.begin(); + mOwner->writeInt32(*it); // default sample size } else { mOwner->writeInt32(0); } - mOwner->writeInt32(mSampleInfos.size()); + mOwner->writeInt32(mNumSamples); if (!mSamplesHaveSameSize) { - for (List<SampleInfo>::iterator it = mSampleInfos.begin(); - it != mSampleInfos.end(); ++it) { - mOwner->writeInt32((*it).size); + for (List<size_t>::iterator it = mSampleSizes.begin(); + it != mSampleSizes.end(); ++it) { + mOwner->writeInt32(*it); } } mOwner->endBox(); // stsz diff --git a/native/android/input.cpp b/native/android/input.cpp index 59bf71178f7c..a82282d35a03 100644 --- a/native/android/input.cpp +++ b/native/android/input.cpp @@ -21,14 +21,21 @@ #include <ui/Input.h> #include <ui/InputTransport.h> #include <utils/PollLoop.h> +#include <utils/RefBase.h> +#include <utils/Vector.h> #include <android_runtime/android_app_NativeActivity.h> #include <poll.h> +#include <errno.h> using android::InputEvent; using android::KeyEvent; using android::MotionEvent; +using android::InputDeviceInfo; +using android::InputDeviceProxy; +using android::sp; +using android::Vector; int32_t AInputEvent_getType(const AInputEvent* event) { return static_cast<const InputEvent*>(event)->getType(); @@ -263,3 +270,74 @@ int32_t AInputQueue_preDispatchEvent(AInputQueue* queue, AInputEvent* event) { void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled) { queue->finishEvent(event, handled != 0); } + + +int32_t AInputDevice_getDeviceIds(int32_t* idBuf, size_t nMax, size_t* nActual) { + Vector<int32_t> ids; + InputDeviceProxy::getDeviceIds(ids); + + if (nActual) { + *nActual = ids.size(); + } + + if (idBuf && ids.size() < nMax) { + memcpy(idBuf, ids.array(), ids.size() * sizeof(int32_t)); + return 0; + } + + return -ENOMEM; +} + +AInputDevice* AInputDevice_acquire(int32_t deviceId) { + sp<InputDeviceProxy> proxy(InputDeviceProxy::getDevice(deviceId)); + if (proxy == NULL) { + return NULL; + } + proxy->incStrong((void*)AInputDevice_acquire); + return static_cast<AInputDevice*>(proxy.get()); +} + +void AInputDevice_release(AInputDevice* device) { + if (device) { + InputDeviceProxy* proxy = static_cast<InputDeviceProxy*>(device); + proxy->decStrong((void*)AInputDevice_acquire); + } +} + +const char* AInputDevice_getName(AInputDevice* device) { + InputDeviceProxy* proxy = static_cast<InputDeviceProxy*>(device); + return proxy->getInfo()->getName().string(); +} + +uint32_t AInputDevice_getSources(AInputDevice* device) { + InputDeviceProxy* proxy = static_cast<InputDeviceProxy*>(device); + return proxy->getInfo()->getSources(); +} + +int32_t AInputDevice_getKeyboardType(AInputDevice* device) { + InputDeviceProxy* proxy = static_cast<InputDeviceProxy*>(device); + return proxy->getInfo()->getKeyboardType(); +} + +int32_t AInputDevice_getMotionRange(AInputDevice* device, int32_t rangeType, + float* outMin, float* outMax, float* outFlat, float* outFuzz) { + InputDeviceProxy* proxy = static_cast<InputDeviceProxy*>(device); + const InputDeviceInfo::MotionRange* range = proxy->getInfo()->getMotionRange(rangeType); + if (range) { + if (outMin) { + *outMin = range->min; + } + if (outMax) { + *outMax = range->max; + } + if (outFlat) { + *outFlat = range->flat; + } + if (outFuzz) { + *outFuzz = range->fuzz; + } + return 0; + } else { + return -ENOTSUP; + } +} diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp index e1fc4e7bc9ac..db534e09dab8 100644 --- a/native/android/sensor.cpp +++ b/native/android/sensor.cpp @@ -149,3 +149,7 @@ float ASensor_getResolution(ASensor const* sensor) return static_cast<Sensor const*>(sensor)->getResolution(); } +int ASensor_getMinDelay(ASensor const* sensor) +{ + return static_cast<Sensor const*>(sensor)->getMinDelay(); +} diff --git a/native/include/android/input.h b/native/include/android/input.h index 0b8c7e49d246..243c33c3a714 100644 --- a/native/include/android/input.h +++ b/native/include/android/input.h @@ -40,6 +40,7 @@ * NOTE: These functions MUST be implemented by /system/lib/libui.so */ +#include <stdint.h> #include <sys/types.h> #include <android/keycodes.h> #include <android/looper.h> @@ -268,7 +269,6 @@ enum { /* * Input sources. * - * The appropriate interpretation for an input event depends on its source. * Refer to the documentation on android.view.InputDevice for more details about input sources * and their correct interpretation. */ @@ -297,6 +297,37 @@ enum { }; /* + * Keyboard types. + * + * Refer to the documentation on android.view.InputDevice for more details. + */ +enum { + AINPUT_KEYBOARD_TYPE_NONE = 0, + AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC = 1, + AINPUT_KEYBOARD_TYPE_ALPHABETIC = 2, +}; + +/* + * Constants used to retrieve information about the range of motion for a particular + * coordinate of a motion event. + * + * Refer to the documentation on android.view.InputDevice for more details about input sources + * and their correct interpretation. + */ +enum { + AINPUT_MOTION_RANGE_X = 0, + AINPUT_MOTION_RANGE_Y = 1, + AINPUT_MOTION_RANGE_PRESSURE = 2, + AINPUT_MOTION_RANGE_SIZE = 3, + AINPUT_MOTION_RANGE_TOUCH_MAJOR = 4, + AINPUT_MOTION_RANGE_TOUCH_MINOR = 5, + AINPUT_MOTION_RANGE_TOOL_MAJOR = 6, + AINPUT_MOTION_RANGE_TOOL_MINOR = 7, + AINPUT_MOTION_RANGE_ORIENTATION = 8, +}; + + +/* * Input event accessors. * * Note that most functions can only be used on input events that are of a given type. @@ -475,7 +506,7 @@ float AMotionEvent_getToolMinor(const AInputEvent* motion_event, size_t pointer_ * upwards, is perfectly circular or is of unknown orientation. A positive angle * indicates that the major axis of contact is oriented to the right. A negative angle * indicates that the major axis of contact is oriented to the left. - * The full range is from -PI/4 radians (finger pointing fully left) to PI/4 radians + * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians * (finger pointing fully right). */ float AMotionEvent_getOrientation(const AInputEvent* motion_event, size_t pointer_index); @@ -575,7 +606,7 @@ float AMotionEvent_getHistoricalToolMinor(const AInputEvent* motion_event, size_ * upwards, is perfectly circular or is of unknown orientation. A positive angle * indicates that the major axis of contact is oriented to the right. A negative angle * indicates that the major axis of contact is oriented to the left. - * The full range is from -PI/4 radians (finger pointing fully left) to PI/4 radians + * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians * (finger pointing fully right). */ float AMotionEvent_getHistoricalOrientation(const AInputEvent* motion_event, size_t pointer_index, size_t history_index); @@ -631,6 +662,64 @@ int32_t AInputQueue_preDispatchEvent(AInputQueue* queue, AInputEvent* event); */ void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled); +/* + * Input devices. + * + * These functions provide a mechanism for querying the set of available input devices + * and their characteristics and capabilities. + */ +struct AInputDevice; +typedef struct AInputDevice AInputDevice; + +/* + * Populates the supplied array with the ids of all input devices in the system. + * Sets nActual to the actual number of devices. + * Returns zero if enumeration was successful. + * Returns non-zero if the actual number of devices is greater than nMax, in which case the + * client should call the method again with a larger id buffer. + */ +int32_t AInputDevice_getDeviceIds(int32_t* idBuf, size_t nMax, size_t* nActual); + +/* + * Acquires a device by id. + * Returns NULL if the device was not found. + * + * Note: The returned object must be freed using AInputDevice_release when no longer needed. + */ +AInputDevice* AInputDevice_acquire(int32_t deviceId); + +/* + * Releases a device previously acquired by AInputDevice_acquire. + * If device is NULL, this function does nothing. + */ +void AInputDevice_release(AInputDevice* device); + +/* + * Gets the name of an input device. + * + * Note: The caller should copy the name into a private buffer since the returned pointer + * will become invalid when the device object is released. + */ +const char* AInputDevice_getName(AInputDevice* device); + +/* + * Gets the combination of input sources provided by the input device. + */ +uint32_t AInputDevice_getSources(AInputDevice* device); + +/* + * Gets the keyboard type. + */ +int32_t AInputDevice_getKeyboardType(AInputDevice* device); + +/* Gets the minimum value, maximum value, flat position and error tolerance for a + * particular motion coodinate. + * Returns zero if the device supports the specified motion range. */ +int32_t AInputDevice_getMotionRange(AInputDevice* device, int32_t rangeType, + float* outMin, float* outMax, float* outFlat, float* outFuzz); + +//TODO hasKey, keymap stuff, etc... + #ifdef __cplusplus } #endif diff --git a/native/include/android/sensor.h b/native/include/android/sensor.h index 00d95d8e64fc..b4ce0242682d 100644 --- a/native/include/android/sensor.h +++ b/native/include/android/sensor.h @@ -121,6 +121,7 @@ typedef struct ASensorEvent { float temperature; float distance; float light; + float pressure; }; int32_t reserved1[4]; } ASensorEvent; @@ -188,7 +189,8 @@ int ASensorEventQueue_disableSensor(ASensorEventQueue* queue, ASensor const* sen /* * Sets the delivery rate of events in microseconds for the given sensor. * Note that this is a hint only, generally event will arrive at a higher - * rate. + * rate. It is an error to set a rate inferior to the value returned by + * ASensor_getMinDelay(). * Returns a negative error code on failure. */ int ASensorEventQueue_setEventRate(ASensorEventQueue* queue, ASensor const* sensor, int32_t usec); @@ -239,6 +241,13 @@ int ASensor_getType(ASensor const* sensor); */ float ASensor_getResolution(ASensor const* sensor); +/* + * Returns the minimum delay allowed between events in microseconds. + * A value of zero means that this sensor doesn't report events at a + * constant rate, but rather only when a new data is available. + */ +int ASensor_getMinDelay(ASensor const* sensor); + #ifdef __cplusplus }; diff --git a/packages/DefaultContainerService/res/values/strings.xml b/packages/DefaultContainerService/res/values/strings.xml index 2897f34d5845..37f5b61fd59a 100644 --- a/packages/DefaultContainerService/res/values/strings.xml +++ b/packages/DefaultContainerService/res/values/strings.xml @@ -19,5 +19,5 @@ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- service name --> - <string name="service_name">Media Container Service</string> + <string name="service_name">Package Access Helper</string> </resources> diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index b88e69d88580..252b42ab097f 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -307,6 +307,7 @@ sp<IAudioTrack> AudioFlinger::createTrack( { Mutex::Autolock _l(mLock); PlaybackThread *thread = checkPlaybackThread_l(output); + PlaybackThread *effectThread = NULL; if (thread == NULL) { LOGE("unknown output thread"); lStatus = BAD_VALUE; @@ -324,12 +325,19 @@ sp<IAudioTrack> AudioFlinger::createTrack( LOGV("createTrack() sessionId: %d", (sessionId == NULL) ? -2 : *sessionId); if (sessionId != NULL && *sessionId != AudioSystem::SESSION_OUTPUT_MIX) { - // prevent same audio session on different output threads for (size_t i = 0; i < mPlaybackThreads.size(); i++) { - if (mPlaybackThreads.keyAt(i) != output && - mPlaybackThreads.valueAt(i)->hasAudioSession(*sessionId)) { - lStatus = BAD_VALUE; - goto Exit; + sp<PlaybackThread> t = mPlaybackThreads.valueAt(i); + if (mPlaybackThreads.keyAt(i) != output) { + // prevent same audio session on different output threads + uint32_t sessions = t->hasAudioSession(*sessionId); + if (sessions & PlaybackThread::TRACK_SESSION) { + lStatus = BAD_VALUE; + goto Exit; + } + // check if an effect with same session ID is waiting for a track to be created + if (sessions & PlaybackThread::EFFECT_SESSION) { + effectThread = t.get(); + } } } lSessionId = *sessionId; @@ -344,6 +352,14 @@ sp<IAudioTrack> AudioFlinger::createTrack( track = thread->createTrack_l(client, streamType, sampleRate, format, channelCount, frameCount, sharedBuffer, lSessionId, &lStatus); + + // move effect chain to this output thread if an effect on same session was waiting + // for a track to be created + if (lStatus == NO_ERROR && effectThread != NULL) { + Mutex::Autolock _dl(thread->mLock); + Mutex::Autolock _sl(effectThread->mLock); + moveEffectChain_l(lSessionId, effectThread, thread, true); + } } if (lStatus == NO_ERROR) { trackHandle = new TrackHandle(track); @@ -1377,7 +1393,7 @@ void AudioFlinger::PlaybackThread::readOutputParameters() // create a copy of mEffectChains as calling moveEffectChain_l() can reorder some effect chains Vector< sp<EffectChain> > effectChains = mEffectChains; for (size_t i = 0; i < effectChains.size(); i ++) { - mAudioFlinger->moveEffectChain_l(effectChains[i]->sessionId(), this, this); + mAudioFlinger->moveEffectChain_l(effectChains[i]->sessionId(), this, this, false); } } @@ -1394,22 +1410,24 @@ status_t AudioFlinger::PlaybackThread::getRenderPosition(uint32_t *halFrames, ui return mOutput->getRenderPosition(dspFrames); } -bool AudioFlinger::PlaybackThread::hasAudioSession(int sessionId) +uint32_t AudioFlinger::PlaybackThread::hasAudioSession(int sessionId) { Mutex::Autolock _l(mLock); + uint32_t result = 0; if (getEffectChain_l(sessionId) != 0) { - return true; + result = EFFECT_SESSION; } for (size_t i = 0; i < mTracks.size(); ++i) { sp<Track> track = mTracks[i]; if (sessionId == track->sessionId() && !(track->mCblk->flags & CBLK_INVALID_MSK)) { - return true; + result |= TRACK_SESSION; + break; } } - return false; + return result; } uint32_t AudioFlinger::PlaybackThread::getStrategyForSession_l(int sessionId) @@ -1997,7 +2015,7 @@ uint32_t AudioFlinger::MixerThread::activeSleepTimeUs() uint32_t AudioFlinger::MixerThread::idleSleepTimeUs() { - return (uint32_t)((mFrameCount * 1000) / mSampleRate) * 1000; + return (uint32_t)(((mFrameCount * 1000) / mSampleRate) * 1000) / 2; } // ---------------------------------------------------------------------------- @@ -2458,7 +2476,7 @@ uint32_t AudioFlinger::DirectOutputThread::idleSleepTimeUs() { uint32_t time; if (AudioSystem::isLinearPCM(mFormat)) { - time = (uint32_t)((mFrameCount * 1000) / mSampleRate) * 1000; + time = (uint32_t)(((mFrameCount * 1000) / mSampleRate) * 1000) / 2; } else { time = 10000; } @@ -4704,11 +4722,17 @@ sp<IEffect> AudioFlinger::createEffect(pid_t pid, } else { // look for the thread where the specified audio session is present for (size_t i = 0; i < mPlaybackThreads.size(); i++) { - if (mPlaybackThreads.valueAt(i)->hasAudioSession(sessionId)) { + if (mPlaybackThreads.valueAt(i)->hasAudioSession(sessionId) != 0) { output = mPlaybackThreads.keyAt(i); break; } } + // If no output thread contains the requested session ID, default to + // first output. The effect chain will be moved to the correct output + // thread when a track with the same session ID is created + if (output == 0 && mPlaybackThreads.size()) { + output = mPlaybackThreads.keyAt(0); + } } } PlaybackThread *thread = checkPlaybackThread_l(output); @@ -4764,7 +4788,7 @@ status_t AudioFlinger::moveEffects(int session, int srcOutput, int dstOutput) Mutex::Autolock _dl(dstThread->mLock); Mutex::Autolock _sl(srcThread->mLock); - moveEffectChain_l(session, srcThread, dstThread); + moveEffectChain_l(session, srcThread, dstThread, false); return NO_ERROR; } @@ -4772,7 +4796,8 @@ status_t AudioFlinger::moveEffects(int session, int srcOutput, int dstOutput) // moveEffectChain_l mustbe called with both srcThread and dstThread mLocks held status_t AudioFlinger::moveEffectChain_l(int session, AudioFlinger::PlaybackThread *srcThread, - AudioFlinger::PlaybackThread *dstThread) + AudioFlinger::PlaybackThread *dstThread, + bool reRegister) { LOGV("moveEffectChain_l() session %d from thread %p to thread %p", session, srcThread, dstThread); @@ -4784,7 +4809,7 @@ status_t AudioFlinger::moveEffectChain_l(int session, return INVALID_OPERATION; } - // remove chain first. This is usefull only if reconfiguring effect chain on same output thread, + // remove chain first. This is useful only if reconfiguring effect chain on same output thread, // so that a new chain is created with correct parameters when first effect is added. This is // otherwise unecessary as removeEffect_l() will remove the chain when last effect is // removed. @@ -4792,10 +4817,32 @@ status_t AudioFlinger::moveEffectChain_l(int session, // transfer all effects one by one so that new effect chain is created on new thread with // correct buffer sizes and audio parameters and effect engines reconfigured accordingly + int dstOutput = dstThread->id(); + sp<EffectChain> dstChain; + uint32_t strategy; sp<EffectModule> effect = chain->getEffectFromId_l(0); while (effect != 0) { srcThread->removeEffect_l(effect); dstThread->addEffect_l(effect); + // if the move request is not received from audio policy manager, the effect must be + // re-registered with the new strategy and output + if (dstChain == 0) { + dstChain = effect->chain().promote(); + if (dstChain == 0) { + LOGW("moveEffectChain_l() cannot get chain from effect %p", effect.get()); + srcThread->addEffect_l(effect); + return NO_INIT; + } + strategy = dstChain->strategy(); + } + if (reRegister) { + AudioSystem::unregisterEffect(effect->id()); + AudioSystem::registerEffect(&effect->desc(), + dstOutput, + strategy, + session, + effect->id()); + } effect = chain->getEffectFromId_l(0); } @@ -5408,8 +5455,13 @@ status_t AudioFlinger::EffectModule::configure() this, thread.get(), mConfig.inputCfg.buffer.raw, mConfig.inputCfg.buffer.frameCount); status_t cmdStatus; - int size = sizeof(int); - status_t status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_CONFIGURE, sizeof(effect_config_t), &mConfig, &size, &cmdStatus); + uint32_t size = sizeof(int); + status_t status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_CONFIGURE, + sizeof(effect_config_t), + &mConfig, + &size, + &cmdStatus); if (status == 0) { status = cmdStatus; } @@ -5427,8 +5479,13 @@ status_t AudioFlinger::EffectModule::init() return NO_INIT; } status_t cmdStatus; - int size = sizeof(status_t); - status_t status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_INIT, 0, NULL, &size, &cmdStatus); + uint32_t size = sizeof(status_t); + status_t status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_INIT, + 0, + NULL, + &size, + &cmdStatus); if (status == 0) { status = cmdStatus; } @@ -5441,8 +5498,13 @@ status_t AudioFlinger::EffectModule::start_l() return NO_INIT; } status_t cmdStatus; - int size = sizeof(status_t); - status_t status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_ENABLE, 0, NULL, &size, &cmdStatus); + uint32_t size = sizeof(status_t); + status_t status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_ENABLE, + 0, + NULL, + &size, + &cmdStatus); if (status == 0) { status = cmdStatus; } @@ -5455,15 +5517,24 @@ status_t AudioFlinger::EffectModule::stop_l() return NO_INIT; } status_t cmdStatus; - int size = sizeof(status_t); - status_t status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_DISABLE, 0, NULL, &size, &cmdStatus); + uint32_t size = sizeof(status_t); + status_t status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_DISABLE, + 0, + NULL, + &size, + &cmdStatus); if (status == 0) { status = cmdStatus; } return status; } -status_t AudioFlinger::EffectModule::command(int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData) +status_t AudioFlinger::EffectModule::command(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData) { Mutex::Autolock _l(mLock); // LOGV("command(), cmdCode: %d, mEffectInterface: %p", cmdCode, mEffectInterface); @@ -5471,9 +5542,14 @@ status_t AudioFlinger::EffectModule::command(int cmdCode, int cmdSize, void *pCm if (mEffectInterface == NULL) { return NO_INIT; } - status_t status = (*mEffectInterface)->command(mEffectInterface, cmdCode, cmdSize, pCmdData, replySize, pReplyData); + status_t status = (*mEffectInterface)->command(mEffectInterface, + cmdCode, + cmdSize, + pCmdData, + replySize, + pReplyData); if (cmdCode != EFFECT_CMD_GET_PARAM && status == NO_ERROR) { - int size = (replySize == NULL) ? 0 : *replySize; + uint32_t size = (replySize == NULL) ? 0 : *replySize; for (size_t i = 1; i < mHandles.size(); i++) { sp<EffectHandle> h = mHandles[i].promote(); if (h != 0) { @@ -5549,13 +5625,18 @@ status_t AudioFlinger::EffectModule::setVolume(uint32_t *left, uint32_t *right, status_t cmdStatus; uint32_t volume[2]; uint32_t *pVolume = NULL; - int size = sizeof(volume); + uint32_t size = sizeof(volume); volume[0] = *left; volume[1] = *right; if (controller) { pVolume = volume; } - status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_SET_VOLUME, size, volume, &size, pVolume); + status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_SET_VOLUME, + size, + volume, + &size, + pVolume); if (controller && status == NO_ERROR && size == sizeof(volume)) { *left = volume[0]; *right = volume[1]; @@ -5575,8 +5656,13 @@ status_t AudioFlinger::EffectModule::setDevice(uint32_t device) return BAD_VALUE; } status_t cmdStatus; - int size = sizeof(status_t); - status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_SET_DEVICE, sizeof(uint32_t), &device, &size, &cmdStatus); + uint32_t size = sizeof(status_t); + status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_SET_DEVICE, + sizeof(uint32_t), + &device, + &size, + &cmdStatus); if (status == NO_ERROR) { status = cmdStatus; } @@ -5595,8 +5681,13 @@ status_t AudioFlinger::EffectModule::setMode(uint32_t mode) return BAD_VALUE; } status_t cmdStatus; - int size = sizeof(status_t); - status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_SET_AUDIO_MODE, sizeof(int), &effectMode, &size, &cmdStatus); + uint32_t size = sizeof(status_t); + status = (*mEffectInterface)->command(mEffectInterface, + EFFECT_CMD_SET_AUDIO_MODE, + sizeof(int), + &effectMode, + &size, + &cmdStatus); if (status == NO_ERROR) { status = cmdStatus; } @@ -5805,9 +5896,14 @@ void AudioFlinger::EffectHandle::disconnect() } } -status_t AudioFlinger::EffectHandle::command(int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData) +status_t AudioFlinger::EffectHandle::command(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData) { -// LOGV("command(), cmdCode: %d, mHasControl: %d, mEffect: %p", cmdCode, mHasControl, (mEffect == 0) ? 0 : mEffect.get()); +// LOGV("command(), cmdCode: %d, mHasControl: %d, mEffect: %p", +// cmdCode, mHasControl, (mEffect == 0) ? 0 : mEffect.get()); // only get parameter command is permitted for applications not controlling the effect if (!mHasControl && cmdCode != EFFECT_CMD_GET_PARAM) { @@ -5829,7 +5925,7 @@ status_t AudioFlinger::EffectHandle::command(int cmdCode, int cmdSize, void *pCm status_t status = NO_ERROR; while (mCblk->serverIndex < mCblk->clientIndex) { int reply; - int rsize = sizeof(int); + uint32_t rsize = sizeof(int); int *p = (int *)(mBuffer + mCblk->serverIndex); int size = *p++; if (((uint8_t *)p + size) > mBuffer + mCblk->clientIndex) { @@ -5842,8 +5938,14 @@ status_t AudioFlinger::EffectHandle::command(int cmdCode, int cmdSize, void *pCm mCblk->serverIndex += size; continue; } - int psize = sizeof(effect_param_t) + ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + param->vsize; - status_t ret = mEffect->command(EFFECT_CMD_SET_PARAM, psize, p, &rsize, &reply); + uint32_t psize = sizeof(effect_param_t) + + ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + + param->vsize; + status_t ret = mEffect->command(EFFECT_CMD_SET_PARAM, + psize, + p, + &rsize, + &reply); if (ret == NO_ERROR) { if (reply != NO_ERROR) { status = reply; @@ -5879,7 +5981,11 @@ void AudioFlinger::EffectHandle::setControl(bool hasControl, bool signal) } } -void AudioFlinger::EffectHandle::commandExecuted(int cmdCode, int cmdSize, void *pCmdData, int replySize, void *pReplyData) +void AudioFlinger::EffectHandle::commandExecuted(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t replySize, + void *pReplyData) { if (mEffectClient != 0) { mEffectClient->commandExecuted(cmdCode, cmdSize, pCmdData, replySize, pReplyData); diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index a8c9a92da919..5520551992f4 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -613,7 +613,15 @@ private: void disconnectEffect(const sp< EffectModule>& effect, const wp<EffectHandle>& handle); - bool hasAudioSession(int sessionId); + // return values for hasAudioSession (bit field) + enum effect_state { + EFFECT_SESSION = 0x1, // the audio session corresponds to at least one + // effect + TRACK_SESSION = 0x2 // the audio session corresponds to at least one + // track + }; + + uint32_t hasAudioSession(int sessionId); sp<EffectChain> getEffectChain(int sessionId); sp<EffectChain> getEffectChain_l(int sessionId); status_t addEffectChain_l(const sp<EffectChain>& chain); @@ -776,7 +784,8 @@ private: int nextUniqueId(); status_t moveEffectChain_l(int session, AudioFlinger::PlaybackThread *srcThread, - AudioFlinger::PlaybackThread *dstThread); + AudioFlinger::PlaybackThread *dstThread, + bool reRegister); friend class AudioBuffer; @@ -933,7 +942,11 @@ private: int id() { return mId; } void process(); void updateState(); - status_t command(int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData); + status_t command(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData); void reset_l(); status_t configure(); @@ -1023,7 +1036,11 @@ private: // IEffect virtual status_t enable(); virtual status_t disable(); - virtual status_t command(int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData); + virtual status_t command(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData); virtual void disconnect(); virtual sp<IMemory> getCblk() const; virtual status_t onTransact(uint32_t code, const Parcel& data, @@ -1032,7 +1049,11 @@ private: // Give or take control of effect module void setControl(bool hasControl, bool signal); - void commandExecuted(int cmdCode, int cmdSize, void *pCmdData, int replySize, void *pReplyData); + void commandExecuted(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t replySize, + void *pReplyData); void setEnabled(bool enabled); // Getters diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java index 0de11c65c856..14b7d3ec3891 100644 --- a/services/java/com/android/server/DropBoxManagerService.java +++ b/services/java/com/android/server/DropBoxManagerService.java @@ -690,8 +690,6 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { // was lost. Tombstones are expunged by age (see above). if (mAllFiles.blocks > mCachedQuotaBlocks) { - Slog.i(TAG, "Usage (" + mAllFiles.blocks + ") > Quota (" + mCachedQuotaBlocks + ")"); - // Find a fair share amount of space to limit each tag int unsqueezed = mAllFiles.blocks, squeezed = 0; TreeSet<FileList> tags = new TreeSet<FileList>(mFilesByTag.values()); diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java index b4f46ab55a97..91951233fca1 100644 --- a/services/java/com/android/server/InputManager.java +++ b/services/java/com/android/server/InputManager.java @@ -29,6 +29,7 @@ import android.os.PowerManager; import android.util.Slog; import android.util.Xml; import android.view.InputChannel; +import android.view.InputEvent; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; @@ -66,39 +67,50 @@ public class InputManager { private static native void nativeSetDisplaySize(int displayId, int width, int height); private static native void nativeSetDisplayOrientation(int displayId, int rotation); - private static native int nativeGetScanCodeState(int deviceId, int deviceClasses, + private static native int nativeGetScanCodeState(int deviceId, int sourceMask, int scanCode); - private static native int nativeGetKeyCodeState(int deviceId, int deviceClasses, + private static native int nativeGetKeyCodeState(int deviceId, int sourceMask, int keyCode); - private static native int nativeGetSwitchState(int deviceId, int deviceClasses, + private static native int nativeGetSwitchState(int deviceId, int sourceMask, int sw); - private static native boolean nativeHasKeys(int[] keyCodes, boolean[] keyExists); + private static native boolean nativeHasKeys(int deviceId, int sourceMask, + int[] keyCodes, boolean[] keyExists); private static native void nativeRegisterInputChannel(InputChannel inputChannel); private static native void nativeUnregisterInputChannel(InputChannel inputChannel); - private static native int nativeInjectKeyEvent(KeyEvent event, - int injectorPid, int injectorUid, boolean sync, int timeoutMillis); - private static native int nativeInjectMotionEvent(MotionEvent event, - int injectorPid, int injectorUid, boolean sync, int timeoutMillis); + private static native int nativeInjectInputEvent(InputEvent event, + int injectorPid, int injectorUid, int syncMode, int timeoutMillis); private static native void nativeSetInputWindows(InputWindow[] windows); private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen); private static native void nativeSetFocusedApplication(InputApplication application); private static native void nativePreemptInputDispatch(); private static native String nativeDump(); - // Device class as defined by EventHub. - private static final int CLASS_KEYBOARD = 0x00000001; - private static final int CLASS_ALPHAKEY = 0x00000002; - private static final int CLASS_TOUCHSCREEN = 0x00000004; - private static final int CLASS_TRACKBALL = 0x00000008; - private static final int CLASS_TOUCHSCREEN_MT = 0x00000010; - private static final int CLASS_DPAD = 0x00000020; - // Input event injection constants defined in InputDispatcher.h. static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0; static final int INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1; static final int INPUT_EVENT_INJECTION_FAILED = 2; static final int INPUT_EVENT_INJECTION_TIMED_OUT = 3; + // Input event injection synchronization modes defined in InputDispatcher.h + static final int INPUT_EVENT_INJECTION_SYNC_NONE = 0; + static final int INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1; + static final int INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH = 2; + + // Key states (may be returned by queries about the current state of a + // particular key code, scan code or switch). + + /** The key state is unknown or the requested key itself is not supported. */ + public static final int KEY_STATE_UNKNOWN = -1; + + /** The key is up. /*/ + public static final int KEY_STATE_UP = 0; + + /** The key is down. */ + public static final int KEY_STATE_DOWN = 1; + + /** The key is down but is a virtual key press that is being emulated by the system. */ + public static final int KEY_STATE_VIRTUAL = 2; + public InputManager(Context context, WindowManagerService windowManagerService) { this.mContext = context; this.mWindowManagerService = windowManagerService; @@ -150,55 +162,67 @@ public class InputManager { config.navigation = mNavigationConfig; } - public int getScancodeState(int code) { - return nativeGetScanCodeState(0, -1, code); - } - - public int getScancodeState(int deviceId, int code) { - return nativeGetScanCodeState(deviceId, -1, code); - } - - public int getTrackballScancodeState(int code) { - return nativeGetScanCodeState(-1, CLASS_TRACKBALL, code); - } - - public int getDPadScancodeState(int code) { - return nativeGetScanCodeState(-1, CLASS_DPAD, code); - } - - public int getKeycodeState(int code) { - return nativeGetKeyCodeState(0, -1, code); - } - - public int getKeycodeState(int deviceId, int code) { - return nativeGetKeyCodeState(deviceId, -1, code); - } - - public int getTrackballKeycodeState(int code) { - return nativeGetKeyCodeState(-1, CLASS_TRACKBALL, code); + /** + * Gets the current state of a key or button by key code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param keyCode The key code to check. + * @return The key state. + */ + public int getKeyCodeState(int deviceId, int sourceMask, int keyCode) { + return nativeGetKeyCodeState(deviceId, sourceMask, keyCode); } - public int getDPadKeycodeState(int code) { - return nativeGetKeyCodeState(-1, CLASS_DPAD, code); - } - - public int getSwitchState(int sw) { - return nativeGetSwitchState(-1, -1, sw); + /** + * Gets the current state of a key or button by scan code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param scanCode The scan code to check. + * @return The key state. + */ + public int getScanCodeState(int deviceId, int sourceMask, int scanCode) { + return nativeGetScanCodeState(deviceId, sourceMask, scanCode); } - public int getSwitchState(int deviceId, int sw) { - return nativeGetSwitchState(deviceId, -1, sw); + /** + * Gets the current state of a switch by switch code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param switchCode The switch code to check. + * @return The switch state. + */ + public int getSwitchState(int deviceId, int sourceMask, int switchCode) { + return nativeGetSwitchState(deviceId, sourceMask, switchCode); } - public boolean hasKeys(int[] keyCodes, boolean[] keyExists) { + /** + * Determines whether the specified key codes are supported by a particular device. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param keyCodes The array of key codes to check. + * @param keyExists An array at least as large as keyCodes whose entries will be set + * to true or false based on the presence or absence of support for the corresponding + * key codes. + * @return True if the lookup was successful, false otherwise. + */ + public boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists) { if (keyCodes == null) { throw new IllegalArgumentException("keyCodes must not be null."); } - if (keyExists == null) { - throw new IllegalArgumentException("keyExists must not be null."); + if (keyExists == null || keyExists.length < keyCodes.length) { + throw new IllegalArgumentException("keyExists must not be null and must be at " + + "least as large as keyCodes."); } - return nativeHasKeys(keyCodes, keyExists); + return nativeHasKeys(deviceId, sourceMask, keyCodes, keyExists); } public void registerInputChannel(InputChannel inputChannel) { @@ -218,19 +242,30 @@ public class InputManager { } /** - * Injects a key event into the event system on behalf of an application. - * This method may block even if sync is false because it must wait for previous events - * to be dispatched before it can determine whether input event injection will be - * permitted based on the current input focus. + * Injects an input event into the event system on behalf of an application. + * The synchronization mode determines whether the method blocks while waiting for + * input injection to proceed. + * + * {@link #INPUT_EVENT_INJECTION_SYNC_NONE} never blocks. Injection is asynchronous and + * is assumed always to be successful. + * + * {@link #INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT} waits for previous events to be + * dispatched so that the input dispatcher can determine whether input event injection will + * be permitted based on the current input focus. Does not wait for the input event to + * finish processing. + * + * {@link #INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH} waits for the input event to + * be completely processed. + * * @param event The event to inject. * @param injectorPid The pid of the injecting application. * @param injectorUid The uid of the injecting application. - * @param sync If true, waits for the event to be completed before returning. + * @param syncMode The synchronization mode. * @param timeoutMillis The injection timeout in milliseconds. * @return One of the INPUT_EVENT_INJECTION_XXX constants. */ - public int injectKeyEvent(KeyEvent event, int injectorPid, int injectorUid, - boolean sync, int timeoutMillis) { + public int injectInputEvent(InputEvent event, int injectorPid, int injectorUid, + int syncMode, int timeoutMillis) { if (event == null) { throw new IllegalArgumentException("event must not be null"); } @@ -240,38 +275,8 @@ public class InputManager { if (timeoutMillis <= 0) { throw new IllegalArgumentException("timeoutMillis must be positive"); } - - return nativeInjectKeyEvent(event, injectorPid, injectorUid, - sync, timeoutMillis); - } - - /** - * Injects a motion event into the event system on behalf of an application. - * This method may block even if sync is false because it must wait for previous events - * to be dispatched before it can determine whether input event injection will be - * permitted based on the current input focus. - * @param event The event to inject. - * @param sync If true, waits for the event to be completed before returning. - * @param injectorPid The pid of the injecting application. - * @param injectorUid The uid of the injecting application. - * @param sync If true, waits for the event to be completed before returning. - * @param timeoutMillis The injection timeout in milliseconds. - * @return One of the INPUT_EVENT_INJECTION_XXX constants. - */ - public int injectMotionEvent(MotionEvent event, int injectorPid, int injectorUid, - boolean sync, int timeoutMillis) { - if (event == null) { - throw new IllegalArgumentException("event must not be null"); - } - if (injectorPid < 0 || injectorUid < 0) { - throw new IllegalArgumentException("injectorPid and injectorUid must not be negative."); - } - if (timeoutMillis <= 0) { - throw new IllegalArgumentException("timeoutMillis must be positive"); - } - - return nativeInjectMotionEvent(event, injectorPid, injectorUid, - sync, timeoutMillis); + + return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis); } public void setInputWindows(InputWindow[] windows) { diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 11d0b7accb73..b29c6e683222 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -187,7 +187,9 @@ class PackageManagerService extends IPackageManager.Stub { static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService"); - + + static final String mTempContainerPrefix = "smdl2tmp"; + final HandlerThread mHandlerThread = new HandlerThread("PackageManager", Process.THREAD_PRIORITY_BACKGROUND); final PackageHandler mHandler; @@ -1495,6 +1497,7 @@ class PackageManagerService extends IPackageManager.Stub { ps.pkg.applicationInfo.publicSourceDir = ps.resourcePathString; ps.pkg.applicationInfo.sourceDir = ps.codePathString; ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg).getPath(); + ps.pkg.mSetEnabled = ps.enabled; } return generatePackageInfo(ps.pkg, flags); } @@ -6279,11 +6282,10 @@ class PackageManagerService extends IPackageManager.Stub { File dataDir = new File(pkg.applicationInfo.dataDir); dataDir.delete(); } + schedulePackageCleaning(packageName); } synchronized (mPackages) { if (deletedPs != null) { - schedulePackageCleaning(packageName); - if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { if (outInfo != null) { outInfo.removedUid = mSettings.removePackageLP(packageName); @@ -6875,6 +6877,7 @@ class PackageManagerService extends IPackageManager.Stub { return; } pkgSetting.enabled = newState; + pkgSetting.pkg.mSetEnabled = newState; } else { // We're dealing with a component level state change switch (newState) { @@ -8261,6 +8264,7 @@ class PackageManagerService extends IPackageManager.Stub { private void insertPackageSettingLP(PackageSetting p, PackageParser.Package pkg) { p.pkg = pkg; + pkg.mSetEnabled = p.enabled; String codePath = pkg.applicationInfo.sourceDir; String resourcePath = pkg.applicationInfo.publicSourceDir; // Update code path if needed @@ -9486,6 +9490,9 @@ class PackageManagerService extends IPackageManager.Stub { } boolean isEnabledLP(ComponentInfo componentInfo, int flags) { + if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { + return true; + } final PackageSetting packageSettings = mPackages.get(componentInfo.packageName); if (Config.LOGV) { Log.v(TAG, "isEnabledLock - packageName = " + componentInfo.packageName @@ -9501,14 +9508,20 @@ class PackageManagerService extends IPackageManager.Stub { Debug.waitForDebugger(); Log.i(TAG, "We will crash!"); } + return false; } - return ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) - || ((componentInfo.enabled - && ((packageSettings.enabled == COMPONENT_ENABLED_STATE_ENABLED) - || (componentInfo.applicationInfo.enabled - && packageSettings.enabled != COMPONENT_ENABLED_STATE_DISABLED)) - && !packageSettings.disabledComponents.contains(componentInfo.name)) - || packageSettings.enabledComponents.contains(componentInfo.name)); + if (packageSettings.enabled == COMPONENT_ENABLED_STATE_DISABLED + || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled + && packageSettings.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) { + return false; + } + if (packageSettings.enabledComponents.contains(componentInfo.name)) { + return true; + } + if (packageSettings.disabledComponents.contains(componentInfo.name)) { + return false; + } + return componentInfo.enabled; } } @@ -9537,48 +9550,28 @@ class PackageManagerService extends IPackageManager.Stub { } } - static String getTempContainerId() { - String prefix = "smdl2tmp"; - int tmpIdx = 1; - String list[] = PackageHelper.getSecureContainerList(); - if (list != null) { - int idx = 0; - int idList[] = new int[MAX_CONTAINERS]; - boolean neverFound = true; - for (String name : list) { - // Ignore null entries - if (name == null) { - continue; - } - int sidx = name.indexOf(prefix); - if (sidx == -1) { - // Not a temp file. just ignore - continue; - } - String subStr = name.substring(sidx + prefix.length()); - idList[idx] = -1; - if (subStr != null) { - try { - int cid = Integer.parseInt(subStr); - idList[idx++] = cid; - neverFound = false; - } catch (NumberFormatException e) { - } - } - } - if (!neverFound) { - // Sort idList - Arrays.sort(idList); - for (int j = 1; j <= idList.length; j++) { - if (idList[j-1] != j) { - tmpIdx = j; - break; - } - } - } - } - return prefix + tmpIdx; - } + /* package */ static String getTempContainerId() { + int tmpIdx = 1; + String list[] = PackageHelper.getSecureContainerList(); + if (list != null) { + for (final String name : list) { + // Ignore null and non-temporary container entries + if (name == null || !name.startsWith(mTempContainerPrefix)) { + continue; + } + + String subStr = name.substring(mTempContainerPrefix.length()); + try { + int cid = Integer.parseInt(subStr); + if (cid >= tmpIdx) { + tmpIdx = cid + 1; + } + } catch (NumberFormatException e) { + } + } + } + return mTempContainerPrefix + tmpIdx; + } /* * Update media status on PackageManager. @@ -9793,10 +9786,15 @@ class PackageManagerService extends IPackageManager.Stub { if (doGc) { Runtime.getRuntime().gc(); } - // List stale containers. + // List stale containers and destroy stale temporary containers. if (removeCids != null) { for (String cid : removeCids) { - Log.w(TAG, "Container " + cid + " is stale"); + if (cid.startsWith(mTempContainerPrefix)) { + Log.i(TAG, "Destroying stale temporary container " + cid); + PackageHelper.destroySdDir(cid); + } else { + Log.w(TAG, "Container " + cid + " is stale"); + } } } } diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index e9d5efce6bac..2fb481cbc133 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -1030,36 +1030,71 @@ class PowerManagerService extends IPowerManager.Stub } } - private void setTimeoutLocked(long now, int nextState) - { + private void setTimeoutLocked(long now, int nextState) { + setTimeoutLocked(now, -1, nextState); + } + + // If they gave a timeoutOverride it is the number of seconds + // to screen-off. Figure out where in the countdown cycle we + // should jump to. + private void setTimeoutLocked(long now, long timeoutOverride, int nextState) { if (mBootCompleted) { - mHandler.removeCallbacks(mTimeoutTask); - mTimeoutTask.nextState = nextState; - long when = now; - switch (nextState) - { - case SCREEN_BRIGHT: - when += mKeylightDelay; - break; - case SCREEN_DIM: - if (mDimDelay >= 0) { - when += mDimDelay; - break; - } else { - Slog.w(TAG, "mDimDelay=" + mDimDelay + " while trying to dim"); + synchronized (mLocks) { + mHandler.removeCallbacks(mTimeoutTask); + mTimeoutTask.nextState = nextState; + long when = 0; + if (timeoutOverride <= 0) { + switch (nextState) + { + case SCREEN_BRIGHT: + when = now + mKeylightDelay; + break; + case SCREEN_DIM: + if (mDimDelay >= 0) { + when = now + mDimDelay; + break; + } else { + Slog.w(TAG, "mDimDelay=" + mDimDelay + " while trying to dim"); + } + case SCREEN_OFF: + synchronized (mLocks) { + when = now + mScreenOffDelay; + } + break; + default: + when = now; + break; } - case SCREEN_OFF: - synchronized (mLocks) { - when += mScreenOffDelay; + } else { + override: { + if (timeoutOverride <= mScreenOffDelay) { + when = now + timeoutOverride; + nextState = SCREEN_OFF; + break override; + } + timeoutOverride -= mScreenOffDelay; + + if (mDimDelay >= 0) { + if (timeoutOverride <= mDimDelay) { + when = now + timeoutOverride; + nextState = SCREEN_DIM; + break override; + } + timeoutOverride -= mDimDelay; + } + + when = now + timeoutOverride; + nextState = SCREEN_BRIGHT; } - break; - } - if (mSpew) { - Slog.d(TAG, "setTimeoutLocked now=" + now + " nextState=" + nextState - + " when=" + when); + } + if (mSpew) { + Slog.d(TAG, "setTimeoutLocked now=" + now + + " timeoutOverride=" + timeoutOverride + + " nextState=" + nextState + " when=" + when); + } + mHandler.postAtTime(mTimeoutTask, when); + mNextTimeout = when; // for debugging } - mHandler.postAtTime(mTimeoutTask, when); - mNextTimeout = when; // for debugging } } @@ -1974,18 +2009,33 @@ class PowerManagerService extends IPowerManager.Stub public void userActivityWithForce(long time, boolean noChangeLights, boolean force) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - userActivity(time, noChangeLights, OTHER_EVENT, force); + userActivity(time, -1, noChangeLights, OTHER_EVENT, force); } public void userActivity(long time, boolean noChangeLights) { - userActivity(time, noChangeLights, OTHER_EVENT, false); + userActivity(time, -1, noChangeLights, OTHER_EVENT, false); } public void userActivity(long time, boolean noChangeLights, int eventType) { - userActivity(time, noChangeLights, eventType, false); + userActivity(time, -1, noChangeLights, eventType, false); } public void userActivity(long time, boolean noChangeLights, int eventType, boolean force) { + userActivity(time, -1, noChangeLights, eventType, force); + } + + /* + * Reset the user activity timeout to now + timeout. This overrides whatever else is going + * on with user activity. Don't use this function. + */ + public void clearUserActivityTimeout(long now, long timeout) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + Slog.i(TAG, "clearUserActivity for " + timeout + "ms from now"); + userActivity(now, timeout, false, OTHER_EVENT, false); + } + + private void userActivity(long time, long timeoutOverride, boolean noChangeLights, + int eventType, boolean force) { //mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0) @@ -2057,7 +2107,7 @@ class PowerManagerService extends IPowerManager.Stub mWakeLockState = mLocks.reactivateScreenLocksLocked(); setPowerState(mUserState | mWakeLockState, noChangeLights, WindowManagerPolicy.OFF_BECAUSE_OF_USER); - setTimeoutLocked(time, SCREEN_BRIGHT); + setTimeoutLocked(time, timeoutOverride, SCREEN_BRIGHT); } } } diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 2e28afb1b203..5615232a74a8 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -103,6 +103,7 @@ import android.view.IWindowManager; import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputDevice; +import android.view.InputEvent; import android.view.InputQueue; import android.view.KeyEvent; import android.view.MotionEvent; @@ -4307,7 +4308,7 @@ public class WindowManagerService extends IWindowManager.Stub "getSwitchState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mInputManager.getSwitchState(sw); + return mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, sw); } public int getSwitchStateForDevice(int devid, int sw) { @@ -4315,7 +4316,7 @@ public class WindowManagerService extends IWindowManager.Stub "getSwitchStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mInputManager.getSwitchState(devid, sw); + return mInputManager.getSwitchState(devid, InputDevice.SOURCE_ANY, sw); } public int getScancodeState(int sw) { @@ -4323,7 +4324,7 @@ public class WindowManagerService extends IWindowManager.Stub "getScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mInputManager.getScancodeState(sw); + return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_ANY, sw); } public int getScancodeStateForDevice(int devid, int sw) { @@ -4331,7 +4332,7 @@ public class WindowManagerService extends IWindowManager.Stub "getScancodeStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mInputManager.getScancodeState(devid, sw); + return mInputManager.getScanCodeState(devid, InputDevice.SOURCE_ANY, sw); } public int getTrackballScancodeState(int sw) { @@ -4339,7 +4340,7 @@ public class WindowManagerService extends IWindowManager.Stub "getTrackballScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mInputManager.getTrackballScancodeState(sw); + return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_TRACKBALL, sw); } public int getDPadScancodeState(int sw) { @@ -4347,7 +4348,7 @@ public class WindowManagerService extends IWindowManager.Stub "getDPadScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mInputManager.getDPadScancodeState(sw); + return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_DPAD, sw); } public int getKeycodeState(int sw) { @@ -4355,7 +4356,7 @@ public class WindowManagerService extends IWindowManager.Stub "getKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mInputManager.getKeycodeState(sw); + return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, sw); } public int getKeycodeStateForDevice(int devid, int sw) { @@ -4363,7 +4364,7 @@ public class WindowManagerService extends IWindowManager.Stub "getKeycodeStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mInputManager.getKeycodeState(devid, sw); + return mInputManager.getKeyCodeState(devid, InputDevice.SOURCE_ANY, sw); } public int getTrackballKeycodeState(int sw) { @@ -4371,7 +4372,7 @@ public class WindowManagerService extends IWindowManager.Stub "getTrackballKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mInputManager.getTrackballKeycodeState(sw); + return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_TRACKBALL, sw); } public int getDPadKeycodeState(int sw) { @@ -4379,11 +4380,11 @@ public class WindowManagerService extends IWindowManager.Stub "getDPadKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mInputManager.getDPadKeycodeState(sw); + return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_DPAD, sw); } public boolean hasKeys(int[] keycodes, boolean[] keyExists) { - return mInputManager.hasKeys(keycodes, keyExists); + return mInputManager.hasKeys(-1, InputDevice.SOURCE_ANY, keycodes, keyExists); } public void enableScreenAfterBoot() { @@ -5382,6 +5383,8 @@ public class WindowManagerService extends IWindowManager.Stub /** * Injects a keystroke event into the UI. + * Even when sync is false, this method may block while waiting for current + * input events to be dispatched. * * @param ev A motion event describing the keystroke action. (Be sure to use * {@link SystemClock#uptimeMillis()} as the timebase.) @@ -5414,8 +5417,10 @@ public class WindowManagerService extends IWindowManager.Stub final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); - final int result = mInputManager.injectKeyEvent(newEvent, - pid, uid, sync, INJECTION_TIMEOUT_MILLIS); + final int result = mInputManager.injectInputEvent(newEvent, pid, uid, + sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH + : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, + INJECTION_TIMEOUT_MILLIS); Binder.restoreCallingIdentity(ident); return reportInjectionResult(result); @@ -5423,6 +5428,8 @@ public class WindowManagerService extends IWindowManager.Stub /** * Inject a pointer (touch) event into the UI. + * Even when sync is false, this method may block while waiting for current + * input events to be dispatched. * * @param ev A motion event describing the pointer (touch) action. (As noted in * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use @@ -5440,8 +5447,10 @@ public class WindowManagerService extends IWindowManager.Stub newEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); } - final int result = mInputManager.injectMotionEvent(newEvent, - pid, uid, sync, INJECTION_TIMEOUT_MILLIS); + final int result = mInputManager.injectInputEvent(newEvent, pid, uid, + sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH + : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, + INJECTION_TIMEOUT_MILLIS); Binder.restoreCallingIdentity(ident); return reportInjectionResult(result); @@ -5449,6 +5458,8 @@ public class WindowManagerService extends IWindowManager.Stub /** * Inject a trackball (navigation device) event into the UI. + * Even when sync is false, this method may block while waiting for current + * input events to be dispatched. * * @param ev A motion event describing the trackball action. (As noted in * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use @@ -5466,8 +5477,31 @@ public class WindowManagerService extends IWindowManager.Stub newEvent.setSource(InputDevice.SOURCE_TRACKBALL); } - final int result = mInputManager.injectMotionEvent(newEvent, - pid, uid, sync, INJECTION_TIMEOUT_MILLIS); + final int result = mInputManager.injectInputEvent(newEvent, pid, uid, + sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH + : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, + INJECTION_TIMEOUT_MILLIS); + + Binder.restoreCallingIdentity(ident); + return reportInjectionResult(result); + } + + /** + * Inject an input event into the UI without waiting for dispatch to commence. + * This variant is useful for fire-and-forget input event injection. It does not + * block any longer than it takes to enqueue the input event. + * + * @param ev An input event. (Be sure to set the input source correctly.) + * @return Returns true if event was dispatched, false if it was dropped for any reason + */ + public boolean injectInputEventNoWait(InputEvent ev) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + + final int result = mInputManager.injectInputEvent(ev, pid, uid, + InputManager.INPUT_EVENT_INJECTION_SYNC_NONE, + INJECTION_TIMEOUT_MILLIS); Binder.restoreCallingIdentity(ident); return reportInjectionResult(result); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index e333a82ec312..bcbda3e0940f 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -6170,7 +6170,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return; boolean bufferWasEmpty; - + boolean needsFlush; final StringBuilder sb = isSystemApp ? mStrictModeBuffer : new StringBuilder(1024); synchronized (sb) { bufferWasEmpty = sb.length() == 0; @@ -6185,18 +6185,32 @@ public final class ActivityManagerService extends ActivityManagerNative implemen sb.append(crashInfo.stackTrace); } sb.append("\n"); + + // Only buffer up to ~64k. Various logging bits truncate + // things at 128k. + needsFlush = (sb.length() > 64 * 1024); } - // Non-system apps are isolated with a different tag & policy. - // They're also not batched. Batching is useful during system - // boot with strict system-wide logging policies and lots of - // things firing, but not common with regular apps, which - // won't ship with StrictMode dropboxing enabled. - if (!isSystemApp) { + // Flush immediately if the buffer's grown too large, or this + // is a non-system app. Non-system apps are isolated with a + // different tag & policy and not batched. + // + // Batching is useful during internal testing with + // StrictMode settings turned up high. Without batching, + // thousands of separate files could be created on boot. + if (!isSystemApp || needsFlush) { new Thread("Error dump: " + dropboxTag) { @Override public void run() { - dbox.addText(dropboxTag, sb.toString()); + String report; + synchronized (sb) { + report = sb.toString(); + sb.delete(0, sb.length()); + sb.trimToSize(); + } + if (report.length() != 0) { + dbox.addText(dropboxTag, report); + } } }.start(); return; @@ -6204,8 +6218,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // System app batching: if (!bufferWasEmpty) { - // An existing dropbox-writing thread is outstanding and - // will handle it. + // An existing dropbox-writing thread is outstanding, so + // we don't need to start it up. The existing thread will + // catch the buffer appends we just did. return; } @@ -6752,7 +6767,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } return; } else if ("service".equals(cmd)) { - dumpService(fd, pw, args, opti, true); + dumpService(fd, pw, args, opti, dumpAll); return; } else if ("services".equals(cmd) || "s".equals(cmd)) { synchronized (this) { @@ -7058,20 +7073,28 @@ public final class ActivityManagerService extends ActivityManagerNative implemen componentNameString = args[opti]; opti++; ComponentName componentName = ComponentName.unflattenFromString(componentNameString); - r = componentName != null ? mServices.get(componentName) : null; + synchronized (this) { + r = componentName != null ? mServices.get(componentName) : null; + } newArgs = new String[args.length - opti]; if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); } if (r != null) { - dumpService(fd, pw, r, newArgs); + dumpService(fd, pw, r, newArgs, dumpAll); } else { - for (ServiceRecord r1 : mServices.values()) { - if (componentNameString == null - || r1.name.flattenToString().contains(componentNameString)) { - dumpService(fd, pw, r1, newArgs); + ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); + synchronized (this) { + for (ServiceRecord r1 : mServices.values()) { + if (componentNameString == null + || r1.name.flattenToString().contains(componentNameString)) { + services.add(r1); + } } } + for (int i=0; i<services.size(); i++) { + dumpService(fd, pw, services.get(i), newArgs, dumpAll); + } } } @@ -7079,8 +7102,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * Invokes IApplicationThread.dumpService() on the thread of the specified service if * there is a thread associated with the service. */ - private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args) { + private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args, + boolean dumpAll) { pw.println(" Service " + r.name.flattenToString()); + if (dumpAll) { + synchronized (this) { + pw.print(" * "); pw.println(r); + r.dump(pw, " "); + } + pw.println(""); + } if (r.app != null && r.app.thread != null) { try { // flush anything that is already in the PrintWriter since the thread is going @@ -7088,6 +7119,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pw.flush(); r.app.thread.dumpService(fd, r, args); pw.print("\n"); + pw.flush(); } catch (RemoteException e) { pw.println("got a RemoteException while dumping the service"); } diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index b29f8756fa0e..eb0a8a9aea36 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -26,13 +26,13 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.hardware.Usb; import android.net.ConnectivityManager; import android.net.InterfaceConfiguration; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.NetworkInfo; import android.net.NetworkUtils; -import android.os.BatteryManager; import android.os.Binder; import android.os.Environment; import android.os.HandlerThread; @@ -135,7 +135,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mStateReceiver = new StateReceiver(); IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(Usb.ACTION_USB_STATE); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_BOOT_COMPLETED); mContext.registerReceiver(mStateReceiver, filter); @@ -424,10 +424,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private class StateReceiver extends BroadcastReceiver { public void onReceive(Context content, Intent intent) { String action = intent.getAction(); - if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - mUsbConnected = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) - == BatteryManager.BATTERY_PLUGGED_USB); - Tethering.this.updateUsbStatus(); + if (action.equals(Usb.ACTION_USB_STATE)) { + mUsbConnected = intent.getExtras().getBoolean(Usb.USB_CONNECTED); + updateUsbStatus(); } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) { mUsbMassStorageOff = false; updateUsbStatus(); diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index 4b4c784001c4..f8c9a6132848 100755 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -892,7 +892,7 @@ public class GpsLocationProvider implements LocationProviderInterface { Settings.Secure.ASSISTED_GPS_ENABLED, 1) != 0) { if (singleShot && hasCapability(GPS_CAPABILITY_MSA)) { mPositionMode = GPS_POSITION_MODE_MS_ASSISTED; - } else if (hasCapability(GPS_CAPABILITY_MSA)) { + } else if (hasCapability(GPS_CAPABILITY_MSB)) { mPositionMode = GPS_POSITION_MODE_MS_BASED; } } diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index 0992b33f877d..0982b32719a4 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -180,6 +180,14 @@ static struct { jfieldID token; } gInputApplicationClassInfo; +static struct { + jclass clazz; +} gKeyEventClassInfo; + +static struct { + jclass clazz; +} gMotionEventClassInfo; + // ---------------------------------------------------------------------------- static inline nsecs_t now() { @@ -219,11 +227,10 @@ public: int32_t* width, int32_t* height, int32_t* orientation); virtual void virtualKeyDownFeedback(); virtual int32_t interceptKey(nsecs_t when, int32_t deviceId, - bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags); - virtual int32_t interceptTrackball(nsecs_t when, bool buttonChanged, bool buttonDown, - bool rolled); - virtual int32_t interceptTouch(nsecs_t when); - virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue); + bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags); + virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue, + uint32_t& policyFlags); + virtual int32_t interceptGeneric(nsecs_t when, uint32_t& policyFlags); virtual bool filterTouchEvents(); virtual bool filterJumpyTouchEvents(); virtual void getVirtualKeyDefinitions(const String8& deviceName, @@ -343,6 +350,7 @@ private: InputApplication* mFocusedApplication; InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication + void dumpDeviceInfo(String8& dump); void dumpDispatchStateLd(String8& dump); void logDispatchStateLd(); @@ -409,12 +417,16 @@ NativeInputManager::~NativeInputManager() { String8 NativeInputManager::dump() { String8 dump; - dump.append("Native Input Dispatcher State:\n"); - { // acquire lock AutoMutex _l(mDisplayLock); + dump.append("Native Input Dispatcher State:\n"); dumpDispatchStateLd(dump); + dump.append("\n"); } // release lock + + dump.append("Input Devices:\n"); + dumpDeviceInfo(dump); + return dump; } @@ -566,9 +578,15 @@ bool NativeInputManager::getDisplayInfo(int32_t displayId, AutoMutex _l(mDisplayLock); if (mDisplayWidth > 0) { - *width = mDisplayWidth; - *height = mDisplayHeight; - *orientation = mDisplayOrientation; + if (width) { + *width = mDisplayWidth; + } + if (height) { + *height = mDisplayHeight; + } + if (orientation) { + *orientation = mDisplayOrientation; + } result = true; } } @@ -595,7 +613,7 @@ void NativeInputManager::virtualKeyDownFeedback() { } int32_t NativeInputManager::interceptKey(nsecs_t when, - int32_t deviceId, bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) { + int32_t deviceId, bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags) { #if DEBUG_INPUT_READER_POLICY LOGD("interceptKey - when=%lld, deviceId=%d, down=%d, keyCode=%d, scanCode=%d, " "policyFlags=0x%x", @@ -626,12 +644,12 @@ int32_t NativeInputManager::interceptKey(nsecs_t when, int32_t actions = InputReaderPolicyInterface::ACTION_NONE; if (! isScreenOn) { // Key presses and releases wake the device. - actions |= InputReaderPolicyInterface::ACTION_WOKE_HERE; + policyFlags |= POLICY_FLAG_WOKE_HERE; } if (! isScreenBright) { // Key presses and releases brighten the screen if dimmed. - actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE; + policyFlags |= POLICY_FLAG_BRIGHT_HERE; } if (wmActions & WM_ACTION_GO_TO_SLEEP) { @@ -658,42 +676,20 @@ int32_t NativeInputManager::interceptKey(nsecs_t when, return actions; } -int32_t NativeInputManager::interceptTouch(nsecs_t when) { -#if DEBUG_INPUT_READER_POLICY - LOGD("interceptTouch - when=%lld", when); -#endif - - int32_t actions = InputReaderPolicyInterface::ACTION_NONE; - if (isScreenOn()) { - // Only dispatch touch events when the device is awake. - // Do not wake the device. - actions |= InputReaderPolicyInterface::ACTION_DISPATCH; - - if (! isScreenBright()) { - // Brighten the screen if dimmed. - actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE; - } - } - - return actions; -} - -int32_t NativeInputManager::interceptTrackball(nsecs_t when, - bool buttonChanged, bool buttonDown, bool rolled) { +int32_t NativeInputManager::interceptGeneric(nsecs_t when, uint32_t& policyFlags) { #if DEBUG_INPUT_READER_POLICY - LOGD("interceptTrackball - when=%lld, buttonChanged=%d, buttonDown=%d, rolled=%d", - when, buttonChanged, buttonDown, rolled); + LOGD("interceptGeneric - when=%lld, policyFlags=0x%x", when, policyFlags); #endif int32_t actions = InputReaderPolicyInterface::ACTION_NONE; if (isScreenOn()) { - // Only dispatch trackball events when the device is awake. + // Only dispatch events when the device is awake. // Do not wake the device. actions |= InputReaderPolicyInterface::ACTION_DISPATCH; if (! isScreenBright()) { // Brighten the screen if dimmed. - actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE; + policyFlags |= POLICY_FLAG_BRIGHT_HERE; } } @@ -701,10 +697,10 @@ int32_t NativeInputManager::interceptTrackball(nsecs_t when, } int32_t NativeInputManager::interceptSwitch(nsecs_t when, int32_t switchCode, - int32_t switchValue) { + int32_t switchValue, uint32_t& policyFlags) { #if DEBUG_INPUT_READER_POLICY - LOGD("interceptSwitch - when=%lld, switchCode=%d, switchValue=%d", - when, switchCode, switchValue); + LOGD("interceptSwitch - when=%lld, switchCode=%d, switchValue=%d, policyFlags=0x%x", + when, switchCode, switchValue, policyFlags); #endif JNIEnv* env = jniEnv(); @@ -1718,6 +1714,56 @@ void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) android_server_PowerManagerService_userActivity(eventTime, eventType); } +static void dumpMotionRange(String8& dump, + const char* name, const InputDeviceInfo::MotionRange* range) { + if (range) { + dump.appendFormat(" %s = { min: %0.3f, max: %0.3f, flat: %0.3f, fuzz: %0.3f }\n", + name, range->min, range->max, range->flat, range->fuzz); + } +} + +#define DUMP_MOTION_RANGE(range) \ + dumpMotionRange(dump, #range, deviceInfo.getMotionRange(AINPUT_MOTION_RANGE_##range)); + +void NativeInputManager::dumpDeviceInfo(String8& dump) { + Vector<int32_t> deviceIds; + mInputManager->getInputDeviceIds(deviceIds); + + InputDeviceInfo deviceInfo; + for (size_t i = 0; i < deviceIds.size(); i++) { + int32_t deviceId = deviceIds[i]; + + status_t result = mInputManager->getInputDeviceInfo(deviceId, & deviceInfo); + if (result == NAME_NOT_FOUND) { + continue; + } else if (result != OK) { + dump.appendFormat(" ** Unexpected error %d getting information about input devices.\n", + result); + continue; + } + + dump.appendFormat(" Device %d: '%s'\n", + deviceInfo.getId(), deviceInfo.getName().string()); + dump.appendFormat(" sources = 0x%08x\n", + deviceInfo.getSources()); + dump.appendFormat(" keyboardType = %d\n", + deviceInfo.getKeyboardType()); + + dump.append(" motion ranges:\n"); + DUMP_MOTION_RANGE(X); + DUMP_MOTION_RANGE(Y); + DUMP_MOTION_RANGE(PRESSURE); + DUMP_MOTION_RANGE(SIZE); + DUMP_MOTION_RANGE(TOUCH_MAJOR); + DUMP_MOTION_RANGE(TOUCH_MINOR); + DUMP_MOTION_RANGE(TOOL_MAJOR); + DUMP_MOTION_RANGE(TOOL_MINOR); + DUMP_MOTION_RANGE(ORIENTATION); + } +} + +#undef DUMP_MOTION_RANGE + void NativeInputManager::logDispatchStateLd() { String8 dump; dumpDispatchStateLd(dump); @@ -1899,36 +1945,37 @@ static void android_server_InputManager_nativeSetDisplayOrientation(JNIEnv* env, } static jint android_server_InputManager_nativeGetScanCodeState(JNIEnv* env, jclass clazz, - jint deviceId, jint deviceClasses, jint scanCode) { + jint deviceId, jint sourceMask, jint scanCode) { if (checkInputManagerUnitialized(env)) { return AKEY_STATE_UNKNOWN; } return gNativeInputManager->getInputManager()->getScanCodeState( - deviceId, deviceClasses, scanCode); + deviceId, uint32_t(sourceMask), scanCode); } static jint android_server_InputManager_nativeGetKeyCodeState(JNIEnv* env, jclass clazz, - jint deviceId, jint deviceClasses, jint keyCode) { + jint deviceId, jint sourceMask, jint keyCode) { if (checkInputManagerUnitialized(env)) { return AKEY_STATE_UNKNOWN; } return gNativeInputManager->getInputManager()->getKeyCodeState( - deviceId, deviceClasses, keyCode); + deviceId, uint32_t(sourceMask), keyCode); } static jint android_server_InputManager_nativeGetSwitchState(JNIEnv* env, jclass clazz, - jint deviceId, jint deviceClasses, jint sw) { + jint deviceId, jint sourceMask, jint sw) { if (checkInputManagerUnitialized(env)) { return AKEY_STATE_UNKNOWN; } - return gNativeInputManager->getInputManager()->getSwitchState(deviceId, deviceClasses, sw); + return gNativeInputManager->getInputManager()->getSwitchState( + deviceId, uint32_t(sourceMask), sw); } static jboolean android_server_InputManager_nativeHasKeys(JNIEnv* env, jclass clazz, - jintArray keyCodes, jbooleanArray outFlags) { + jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) { if (checkInputManagerUnitialized(env)) { return JNI_FALSE; } @@ -1937,8 +1984,9 @@ static jboolean android_server_InputManager_nativeHasKeys(JNIEnv* env, jclass cl uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL); jsize numCodes = env->GetArrayLength(keyCodes); jboolean result; - if (numCodes == env->GetArrayLength(outFlags)) { - result = gNativeInputManager->getInputManager()->hasKeys(numCodes, codes, flags); + if (numCodes == env->GetArrayLength(keyCodes)) { + result = gNativeInputManager->getInputManager()->hasKeys( + deviceId, uint32_t(sourceMask), numCodes, codes, flags); } else { result = JNI_FALSE; } @@ -2011,32 +2059,29 @@ static void android_server_InputManager_nativeUnregisterInputChannel(JNIEnv* env } } -static jint android_server_InputManager_nativeInjectKeyEvent(JNIEnv* env, jclass clazz, - jobject keyEventObj, jint injectorPid, jint injectorUid, - jboolean sync, jint timeoutMillis) { +static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jclass clazz, + jobject inputEventObj, jint injectorPid, jint injectorUid, + jint syncMode, jint timeoutMillis) { if (checkInputManagerUnitialized(env)) { return INPUT_EVENT_INJECTION_FAILED; } - KeyEvent keyEvent; - android_view_KeyEvent_toNative(env, keyEventObj, & keyEvent); + if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) { + KeyEvent keyEvent; + android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent); - return gNativeInputManager->getInputManager()->injectInputEvent(& keyEvent, - injectorPid, injectorUid, sync, timeoutMillis); -} + return gNativeInputManager->getInputManager()->injectInputEvent(& keyEvent, + injectorPid, injectorUid, syncMode, timeoutMillis); + } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) { + MotionEvent motionEvent; + android_view_MotionEvent_toNative(env, inputEventObj, & motionEvent); -static jint android_server_InputManager_nativeInjectMotionEvent(JNIEnv* env, jclass clazz, - jobject motionEventObj, jint injectorPid, jint injectorUid, - jboolean sync, jint timeoutMillis) { - if (checkInputManagerUnitialized(env)) { + return gNativeInputManager->getInputManager()->injectInputEvent(& motionEvent, + injectorPid, injectorUid, syncMode, timeoutMillis); + } else { + jniThrowRuntimeException(env, "Invalid input event type."); return INPUT_EVENT_INJECTION_FAILED; } - - MotionEvent motionEvent; - android_view_MotionEvent_toNative(env, motionEventObj, & motionEvent); - - return gNativeInputManager->getInputManager()->injectInputEvent(& motionEvent, - injectorPid, injectorUid, sync, timeoutMillis); } static void android_server_InputManager_nativeSetInputWindows(JNIEnv* env, jclass clazz, @@ -2102,16 +2147,14 @@ static JNINativeMethod gInputManagerMethods[] = { (void*) android_server_InputManager_nativeGetKeyCodeState }, { "nativeGetSwitchState", "(III)I", (void*) android_server_InputManager_nativeGetSwitchState }, - { "nativeHasKeys", "([I[Z)Z", + { "nativeHasKeys", "(II[I[Z)Z", (void*) android_server_InputManager_nativeHasKeys }, { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;)V", (void*) android_server_InputManager_nativeRegisterInputChannel }, { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V", (void*) android_server_InputManager_nativeUnregisterInputChannel }, - { "nativeInjectKeyEvent", "(Landroid/view/KeyEvent;IIZI)I", - (void*) android_server_InputManager_nativeInjectKeyEvent }, - { "nativeInjectMotionEvent", "(Landroid/view/MotionEvent;IIZI)I", - (void*) android_server_InputManager_nativeInjectMotionEvent }, + { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIII)I", + (void*) android_server_InputManager_nativeInjectInputEvent }, { "nativeSetInputWindows", "([Lcom/android/server/InputWindow;)V", (void*) android_server_InputManager_nativeSetInputWindows }, { "nativeSetFocusedApplication", "(Lcom/android/server/InputApplication;)V", @@ -2278,6 +2321,14 @@ int register_android_server_InputManager(JNIEnv* env) { GET_FIELD_ID(gInputApplicationClassInfo.token, gInputApplicationClassInfo.clazz, "token", "Ljava/lang/Object;"); + // KeyEvent + + FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent"); + + // MotionEVent + + FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent"); + return 0; } diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 758da4e87653..629d993ca007 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -294,19 +294,9 @@ sp<GraphicBuffer> Layer::requestBuffer(int index, * This is called from the client's Surface::dequeue(). This can happen * at any time, especially while we're in the middle of using the * buffer 'index' as our front buffer. - * - * Make sure the buffer we're resizing is not the front buffer and has been - * dequeued. Once this condition is asserted, we are guaranteed that this - * buffer cannot become the front buffer under our feet, since we're called - * from Surface::dequeue() */ - status_t err = lcblk->assertReallocate(index); - LOGE_IF(err, "assertReallocate(%d) failed (%s)", index, strerror(-err)); - if (err != NO_ERROR) { - // the surface may have died - return buffer; - } + status_t err = NO_ERROR; uint32_t w, h, f; { // scope for the lock Mutex::Autolock _l(mLock); @@ -319,23 +309,17 @@ sp<GraphicBuffer> Layer::requestBuffer(int index, w = reqWidth ? reqWidth : mWidth; h = reqHeight ? reqHeight : mHeight; f = reqFormat ? reqFormat : mFormat; - buffer = mBufferManager.detachBuffer(index); if (fixedSizeChanged || formatChanged) { lcblk->reallocateAllExcept(index); } } + // here we have to reallocate a new buffer because the buffer could be + // used as the front buffer, or by a client in our process + // (eg: status bar), and we can't release the handle under its feet. const uint32_t effectiveUsage = getEffectiveUsage(usage); - if (buffer!=0 && buffer->getStrongCount() == 1) { - err = buffer->reallocate(w, h, f, effectiveUsage); - } else { - // here we have to reallocate a new buffer because we could have a - // client in our process with a reference to it (eg: status bar), - // and we can't release the handle under its feet. - buffer.clear(); - buffer = new GraphicBuffer(w, h, f, effectiveUsage); - err = buffer->initCheck(); - } + buffer = new GraphicBuffer(w, h, f, effectiveUsage); + err = buffer->initCheck(); if (err || buffer->handle == 0) { LOGE_IF(err || buffer->handle == 0, diff --git a/services/surfaceflinger/tests/surface/Android.mk b/services/surfaceflinger/tests/surface/Android.mk new file mode 100644 index 000000000000..ce0e8072f744 --- /dev/null +++ b/services/surfaceflinger/tests/surface/Android.mk @@ -0,0 +1,18 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + surface.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libbinder \ + libui \ + libsurfaceflinger_client + +LOCAL_MODULE:= test-surface + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_EXECUTABLE) diff --git a/services/surfaceflinger/tests/surface/surface.cpp b/services/surfaceflinger/tests/surface/surface.cpp new file mode 100644 index 000000000000..b4de4b459c38 --- /dev/null +++ b/services/surfaceflinger/tests/surface/surface.cpp @@ -0,0 +1,54 @@ +#include <cutils/memory.h> + +#include <utils/Log.h> + +#include <binder/IPCThreadState.h> +#include <binder/ProcessState.h> +#include <binder/IServiceManager.h> + +#include <surfaceflinger/Surface.h> +#include <surfaceflinger/ISurface.h> +#include <surfaceflinger/SurfaceComposerClient.h> + +#include <ui/Overlay.h> + +using namespace android; + +int main(int argc, char** argv) +{ + // set up the thread-pool + sp<ProcessState> proc(ProcessState::self()); + ProcessState::self()->startThreadPool(); + + // create a client to surfaceflinger + sp<SurfaceComposerClient> client = new SurfaceComposerClient(); + + // create pushbuffer surface + sp<SurfaceControl> surfaceControl = client->createSurface( + getpid(), 0, 160, 240, PIXEL_FORMAT_RGB_565); + client->openTransaction(); + surfaceControl->setLayer(100000); + client->closeTransaction(); + + // pretend it went cross-process + Parcel parcel; + SurfaceControl::writeSurfaceToParcel(surfaceControl, &parcel); + parcel.setDataPosition(0); + sp<Surface> surface = Surface::readFromParcel(parcel); + ANativeWindow* window = surface.get(); + + printf("window=%p\n", window); + + int err = native_window_set_buffer_count(window, 8); + android_native_buffer_t* buffer; + + for (int i=0 ; i<8 ; i++) { + window->dequeueBuffer(window, &buffer); + printf("buffer %d: %p\n", i, buffer); + } + + printf("test complete. CTRL+C to finish.\n"); + + IPCThreadState::self()->joinThreadPool(); + return 0; +} diff --git a/tests/LargeAssetTest/Android.mk b/tests/LargeAssetTest/Android.mk new file mode 100644 index 000000000000..cb7f01be8b40 --- /dev/null +++ b/tests/LargeAssetTest/Android.mk @@ -0,0 +1,11 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := LargeAssetTest +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/tests/LargeAssetTest/AndroidManifest.xml b/tests/LargeAssetTest/AndroidManifest.xml new file mode 100644 index 000000000000..c86118e492e5 --- /dev/null +++ b/tests/LargeAssetTest/AndroidManifest.xml @@ -0,0 +1,28 @@ +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.largeassettest"> + + <application> + <activity android:name="LargeAssetTest" android:label="Large Asset Test"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/tests/LargeAssetTest/assets/million-ints b/tests/LargeAssetTest/assets/million-ints Binary files differnew file mode 100644 index 000000000000..0607de1b7667 --- /dev/null +++ b/tests/LargeAssetTest/assets/million-ints diff --git a/tests/LargeAssetTest/res/layout/lat.xml b/tests/LargeAssetTest/res/layout/lat.xml new file mode 100644 index 000000000000..eda7b82e91fa --- /dev/null +++ b/tests/LargeAssetTest/res/layout/lat.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + > + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="25dp" + android:textColor="#ffffffff" + android:text="@string/prompt" + /> + + <TextView android:id="@+id/result" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginTop="25dp" + android:textSize="24sp" + android:textColor="#ffffffff" + /> + + <Button android:id="@+id/validate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/button_text" /> + + <TextView + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_weight="1" + /> +</LinearLayout> + + diff --git a/tests/LargeAssetTest/res/values/strings.xml b/tests/LargeAssetTest/res/values/strings.xml new file mode 100644 index 000000000000..54478fad4f6c --- /dev/null +++ b/tests/LargeAssetTest/res/values/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2010 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + + <string name="prompt">Click the button below to read and validate the large binary asset.</string> + <string name="button_text">Validate the asset</string> + +</resources> + diff --git a/tests/LargeAssetTest/src/com/android/largeassettest/LargeAssetTest.java b/tests/LargeAssetTest/src/com/android/largeassettest/LargeAssetTest.java new file mode 100644 index 000000000000..e3a9cf40b066 --- /dev/null +++ b/tests/LargeAssetTest/src/com/android/largeassettest/LargeAssetTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2006 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.largeassettest; + +import android.app.Activity; +import android.content.Context; +import android.content.res.AssetManager; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import java.io.InputStream; +import java.io.IOException; + +/** + * Skeleton to test large-asset handling. The asset in question is one million + * four-byte integers, in ascending numeric order. + */ +public class LargeAssetTest extends Activity { + Button mValidateButton; + TextView mResultText; + Validator mValidateThread; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.lat); + + mResultText = (TextView) findViewById(R.id.result); + mValidateButton = (Button) findViewById(R.id.validate); + + mValidateButton.setOnClickListener(mClickListener); + } + + View.OnClickListener mClickListener = new View.OnClickListener() { + public void onClick(View v) { + mValidateButton.setEnabled(false); + mValidateThread = new Validator(); + mValidateThread.execute(LargeAssetTest.this.getAssets()); + } + }; + + /** + * Validation happens in a separate thread + */ + class Validator extends AsyncTask<AssetManager, Integer, Boolean> { + static final String TAG = "Validator"; + + @Override + protected Boolean doInBackground(AssetManager... params) { + AssetManager am = params[0]; + try { + InputStream is = am.open("million-ints", AssetManager.ACCESS_STREAMING); + byte[] buf = new byte[4]; + + for (int i = 0; i < 1000000; i++) { + int num = is.read(buf, 0, 4); + if (num != 4) { + Log.e(TAG, "Wanted 4 bytes but read " + num); + return false; + } + // the byte array is stored in the asset in little-endian order + int value = (buf[3] << 24) + ((buf[2] & 0xFF) << 16) + + ((buf[1] & 0xFF) << 8) + (buf[0] & 0xFF); + if (value != i) { + Log.e(TAG, "Mismatch: index " + i + " : value " + value); + return false; + } + } + + is.close(); + } catch (IOException e) { + Log.w(TAG, "Couldn't open asset", e); + return false; + } + Log.i(TAG, "Finished, reporting valid"); + return true; + } + + @Override + protected void onPostExecute(Boolean result) { + CharSequence text = (result) ? "Valid!" : "NOT VALID"; + mResultText.setText(text); + mValidateButton.setEnabled(true); + } + } +} |