diff options
38 files changed, 860 insertions, 108 deletions
diff --git a/Android.mk b/Android.mk index aae10dc0414e..3902971f0b5b 100644 --- a/Android.mk +++ b/Android.mk @@ -370,6 +370,7 @@ framework_docs_LOCAL_DROIDDOC_OPTIONS := \ -since ./frameworks/base/api/9.xml 9 \ -since ./frameworks/base/api/10.xml 10 \ -since ./frameworks/base/api/11.xml 11 \ + -since ./frameworks/base/api/12.xml 12 \ -werror -hide 113 \ -overview $(LOCAL_PATH)/core/java/overview.html diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 31119d7ea3da..72fa07c40d9d 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -57,11 +57,30 @@ public final class Configuration implements Parcelable, Comparable<Configuration */ public boolean userSetLocale; + /** Constant for {@link #screenLayout}: bits that encode the size. */ public static final int SCREENLAYOUT_SIZE_MASK = 0x0f; + /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK} + * value indicating that no size has been set. */ public static final int SCREENLAYOUT_SIZE_UNDEFINED = 0x00; + /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK} + * value indicating the screen is at least approximately 320x426 dp units. + * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting + * Multiple Screens</a> for more information. */ public static final int SCREENLAYOUT_SIZE_SMALL = 0x01; + /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK} + * value indicating the screen is at least approximately 320x470 dp units. + * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting + * Multiple Screens</a> for more information. */ public static final int SCREENLAYOUT_SIZE_NORMAL = 0x02; + /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK} + * value indicating the screen is at least approximately 480x640 dp units. + * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting + * Multiple Screens</a> for more information. */ public static final int SCREENLAYOUT_SIZE_LARGE = 0x03; + /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_SIZE_MASK} + * value indicating the screen is at least approximately 720x960 dp units. + * See <a href="{@docRoot}guide/practices/screens_support.html">Supporting + * Multiple Screens</a> for more information.*/ public static final int SCREENLAYOUT_SIZE_XLARGE = 0x04; public static final int SCREENLAYOUT_LONG_MASK = 0x30; @@ -88,6 +107,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration * <p>The {@link #SCREENLAYOUT_LONG_MASK} defines whether the screen * is wider/taller than normal. They may be one of * {@link #SCREENLAYOUT_LONG_NO} or {@link #SCREENLAYOUT_LONG_YES}. + * + * <p>See <a href="{@docRoot}guide/practices/screens_support.html">Supporting + * Multiple Screens</a> for more information. */ public int screenLayout; diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 0c6ab9ef5a27..c8cb1dee20f1 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -95,7 +95,7 @@ public class Process { * Defines the UID/GID for the NFC service process. * @hide */ - public static final int NFC_UID = 1022; + public static final int NFC_UID = 1023; /** * Defines the GID for the group that allows write access to the internal media storage. diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java index f6aeb398fa13..03189ca46feb 100755 --- a/core/java/android/view/InputEvent.java +++ b/core/java/android/view/InputEvent.java @@ -67,6 +67,15 @@ public abstract class InputEvent implements Parcelable { */ public abstract void setSource(int source); + /** + * Recycles the event. + * This method should only be used by the system since applications do not + * expect {@link KeyEvent} objects to be recycled, although {@link MotionEvent} + * objects are fine. See {@link KeyEvent#recycle()} for details. + * @hide + */ + public abstract void recycle(); + public int describeContents() { return 0; } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 334c68e9c06a..9d00d023514d 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -81,6 +81,8 @@ public interface WindowManagerPolicy { public final static int FLAG_INJECTED = 0x01000000; public final static int FLAG_TRUSTED = 0x02000000; + public final static int FLAG_FILTERED = 0x04000000; + public final static int FLAG_DISABLE_KEY_REPEAT = 0x08000000; public final static int FLAG_WOKE_HERE = 0x10000000; public final static int FLAG_BRIGHT_HERE = 0x20000000; diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index 3ce073092c7e..e21a02e8d8b7 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -857,6 +857,7 @@ public final class CacheManager { String cacheControl = headers.getCacheControl(); if (cacheControl != null) { String[] controls = cacheControl.toLowerCase().split("[ ,;]"); + boolean noCache = false; for (int i = 0; i < controls.length; i++) { if (NO_STORE.equals(controls[i])) { return null; @@ -867,7 +868,12 @@ public final class CacheManager { // can only be used in CACHE_MODE_CACHE_ONLY case if (NO_CACHE.equals(controls[i])) { ret.expires = 0; - } else if (controls[i].startsWith(MAX_AGE)) { + noCache = true; + // if cache control = no-cache has been received, ignore max-age + // header, according to http spec: + // If a request includes the no-cache directive, it SHOULD NOT + // include min-fresh, max-stale, or max-age. + } else if (controls[i].startsWith(MAX_AGE) && !noCache) { int separator = controls[i].indexOf('='); if (separator < 0) { separator = controls[i].indexOf(':'); diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java index 96365138a78d..3d159685be82 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -198,8 +198,6 @@ public class HTML5VideoFullScreen extends HTML5VideoView if (mProgressView != null) { mProgressView.setVisibility(View.GONE); - mLayout.removeView(mProgressView); - mProgressView = null; } mVideoWidth = mp.getVideoWidth(); @@ -321,4 +319,13 @@ public class HTML5VideoFullScreen extends HTML5VideoView return false; } + @Override + protected void switchProgressView(boolean playerBuffering) { + if (playerBuffering) { + mProgressView.setVisibility(View.VISIBLE); + } else { + mProgressView.setVisibility(View.GONE); + } + return; + } } diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java index ad6e5d3d4f31..fd3f358731db 100644 --- a/core/java/android/webkit/HTML5VideoView.java +++ b/core/java/android/webkit/HTML5VideoView.java @@ -78,6 +78,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { TIMEUPDATE_PERIOD); } mPlayer.start(); + setPlayerBuffering(false); } } @@ -296,4 +297,21 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { return 0; } + // This is true only when the player is buffering and paused + public boolean mPlayerBuffering = false; + + public boolean getPlayerBuffering() { + return mPlayerBuffering; + } + + public void setPlayerBuffering(boolean playerBuffering) { + mPlayerBuffering = playerBuffering; + switchProgressView(playerBuffering); + } + + + protected void switchProgressView(boolean playerBuffering) { + // Only used in HTML5VideoFullScreen + } + } diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java index 0ee1566196c5..14157c2d2fdf 100644 --- a/core/java/android/webkit/HTML5VideoViewProxy.java +++ b/core/java/android/webkit/HTML5VideoViewProxy.java @@ -95,8 +95,10 @@ class HTML5VideoViewProxy extends Handler // identify the exact layer on the UI thread to use the SurfaceTexture. private static int mBaseLayer = 0; - // This is true only when the player is buffering and paused - private static boolean mPlayerBuffering = false; + private static void setPlayerBuffering(boolean playerBuffering) { + mHTML5VideoView.setPlayerBuffering(playerBuffering); + } + // Every time webView setBaseLayer, this will be called. // When we found the Video layer, then we set the Surface Texture to it. // Otherwise, we may want to delete the Surface Texture to save memory. @@ -111,7 +113,7 @@ class HTML5VideoViewProxy extends Handler int currentVideoLayerId = mHTML5VideoView.getVideoLayerId(); if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) { int playerState = mHTML5VideoView.getCurrentState(); - if (mPlayerBuffering) + if (mHTML5VideoView.getPlayerBuffering()) playerState = HTML5VideoView.STATE_NOTPREPARED; boolean foundInTree = nativeSendSurfaceTexture(surfTexture, layer, currentVideoLayerId, textureName, @@ -166,7 +168,6 @@ class HTML5VideoViewProxy extends Handler WebChromeClient client, int videoLayerId) { int currentVideoLayerId = -1; boolean backFromFullScreenMode = false; - mPlayerBuffering = false; if (mHTML5VideoView != null) { currentVideoLayerId = mHTML5VideoView.getVideoLayerId(); if (mHTML5VideoView instanceof HTML5VideoFullScreen) { @@ -231,7 +232,6 @@ class HTML5VideoViewProxy extends Handler } public static void onPrepared() { - mPlayerBuffering = false; // The VideoView will decide whether to really kick off to play. mHTML5VideoView.start(); if (mBaseLayer != 0) { @@ -350,11 +350,11 @@ class HTML5VideoViewProxy extends Handler break; } case BUFFERING_START: { - VideoPlayer.mPlayerBuffering = true; + VideoPlayer.setPlayerBuffering(true); break; } case BUFFERING_END: { - VideoPlayer.mPlayerBuffering = false; + VideoPlayer.setPlayerBuffering(false); break; } } diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h index b57286592be7..bced40c8e9d4 100644 --- a/core/jni/android/graphics/TextLayoutCache.h +++ b/core/jni/android/graphics/TextLayoutCache.h @@ -65,7 +65,8 @@ namespace android { class TextLayoutCacheKey { public: TextLayoutCacheKey() : text(NULL), start(0), count(0), contextCount(0), - dirFlags(0), textSize(0), typeface(NULL), textSkewX(0), fakeBoldText(false) { + dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0), + hinting(SkPaint::kNo_Hinting) { } TextLayoutCacheKey(const SkPaint* paint, @@ -73,22 +74,28 @@ public: size_t contextCount, int dirFlags) : text(text), start(start), count(count), contextCount(contextCount), dirFlags(dirFlags) { - textSize = paint->getTextSize(); typeface = paint->getTypeface(); + textSize = paint->getTextSize(); textSkewX = paint->getTextSkewX(); - fakeBoldText = paint->isFakeBoldText(); + textScaleX = paint->getTextScaleX(); + flags = paint->getFlags(); + hinting = paint->getHinting(); } bool operator<(const TextLayoutCacheKey& rhs) const { LTE_INT(count) { LTE_INT(contextCount) { LTE_INT(start) { - LTE_FLOAT(textSize) { - LTE_INT(typeface) { - LTE_INT(textSkewX) { - LTE_INT(fakeBoldText) { - LTE_INT(dirFlags) { - return strncmp16(text, rhs.text, contextCount) < 0; + LTE_INT(typeface) { + LTE_FLOAT(textSize) { + LTE_FLOAT(textSkewX) { + LTE_FLOAT(textScaleX) { + LTE_INT(flags) { + LTE_INT(hinting) { + LTE_INT(dirFlags) { + return strncmp16(text, rhs.text, contextCount) < 0; + } + } } } } @@ -121,10 +128,12 @@ private: size_t count; size_t contextCount; int dirFlags; - float textSize; SkTypeface* typeface; - float textSkewX; - bool fakeBoldText; + SkScalar textSize; + SkScalar textSkewX; + SkScalar textScaleX; + uint32_t flags; + SkPaint::Hinting hinting; }; // TextLayoutCacheKey /* diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0c9a2ef76317..348ded7aaead 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1031,7 +1031,7 @@ <permission android:name="android.permission.STOP_APP_SWITCHES" android:label="@string/permlab_stopAppSwitches" android:description="@string/permdesc_stopAppSwitches" - android:protectionLevel="signature" /> + android:protectionLevel="signatureOrSystem" /> <!-- Allows an application to retrieve the current state of keys and switches. This is only for use by the system.--> diff --git a/core/res/res/drawable-hdpi/ic_media_video_poster.png b/core/res/res/drawable-hdpi/ic_media_video_poster.png Binary files differindex 6c1fd6bbbba4..77b6b0e671a5 100644 --- a/core/res/res/drawable-hdpi/ic_media_video_poster.png +++ b/core/res/res/drawable-hdpi/ic_media_video_poster.png diff --git a/core/res/res/drawable-ldpi/ic_media_video_poster.png b/core/res/res/drawable-ldpi/ic_media_video_poster.png Binary files differindex 786d0e6dcbaf..7b349135c626 100644 --- a/core/res/res/drawable-ldpi/ic_media_video_poster.png +++ b/core/res/res/drawable-ldpi/ic_media_video_poster.png diff --git a/core/res/res/drawable-mdpi/ic_media_video_poster.png b/core/res/res/drawable-mdpi/ic_media_video_poster.png Binary files differindex 10bbd7419df6..f457f2332d3c 100644..100755 --- a/core/res/res/drawable-mdpi/ic_media_video_poster.png +++ b/core/res/res/drawable-mdpi/ic_media_video_poster.png diff --git a/docs/html/guide/market/billing/billing_integrate.jd b/docs/html/guide/market/billing/billing_integrate.jd index 26bda66ceea5..56e471e50635 100755 --- a/docs/html/guide/market/billing/billing_integrate.jd +++ b/docs/html/guide/market/billing/billing_integrate.jd @@ -296,7 +296,7 @@ parent.link=index.html <pre> try { boolean bindResult = mContext.bindService( - new Intent(IMarketBillingService.class.getName()), this, Context.BIND_AUTO_CREATE); + new Intent("com.android.vending.billing.MarketBillingService.BIND"), this, Context.BIND_AUTO_CREATE); if (bindResult) { Log.i(TAG, "Service bind successful."); } else { diff --git a/docs/html/guide/practices/screens_support.jd b/docs/html/guide/practices/screens_support.jd index 520bd28853e4..9875a6ec85e8 100644 --- a/docs/html/guide/practices/screens_support.jd +++ b/docs/html/guide/practices/screens_support.jd @@ -189,12 +189,32 @@ offer UI designed for the generalized sizes and densities and let the system handle the actual rendering of the UI on the current device screen according to its characteristics. </p> - <img src="{@docRoot}images/screens_support/screens-ranges.png" /> <p class="img-caption"><strong>Figure 1.</strong> Illustration of how the Android platform maps actual screen densities and sizes to generalized density and size configurations. </p> +<p>Layout designs often need to be done against a minimum amount of +available space, so each screen size bucket has an associated minimum size. +These sizes are in "dp" units -- the same units you should use in defining +your layouts, which allow us to avoid worrying about changes in screen density.</p> + +<ul> +<li> <em>xlarge</em> screens are at least 960dp x 720dp. +<li> <em>large</em> screens are at least 640dp x 480dp. +<li> <em>normal</em> screens are at least 470dp x 320dp. +<li> <em>small</em> screens are at least 426dp x 320dp. +</ul> + +<p>Note that these minimum screen sizes were not +as well defined prior to Android 3.0, so you may encounter some devices +that are mis-classified between normal and large. These are also based +on the physical resolution of the screen, so may vary across devices -- +for example a 1024x720 tablet with a system bar would actually have a bit +less space available to the application due to it being used by the system +bar. Android does not currently support screens smaller than the "small" +426dp x 320dp size.</p> + <p>Although the platform lets your application provide customized resources for the various size and density configurations, you do not need to do write custom code or provide custom resources for every combination of screen size and density. @@ -212,7 +232,8 @@ of the emulator skins to replicate the characteristics of any specific screen.</p> <p class="table-caption" id="screens-table"><strong>Table 1.</strong> Screen -sizes and densities of emulator skins included in the Android SDK.</p> +sizes and densities of emulator skins included in the Android SDK and other +representative resolutions.</p> <table> <tbody> @@ -235,27 +256,33 @@ sizes and densities of emulator skins included in the Android SDK.</p> <td style="background-color:#f3f3f3"> <em>Small</em> screen </td> - <td style="font-size:.9em;">QVGA (240x320)</td> + <td style="font-size:.9em;"><strong>QVGA (240x320)</strong></td> </td> <td></td> - <td></td> + <td style="font-size:.9em;">480x640</td> <td></td> </tr> <tr> <td style="background-color:#f3f3f3"> <em>Normal</em> screen </td> - <td style="font-size:.9em;">WQVGA400 (240x400)<br>WQVGA432 (240x432)</td> - <td style="font-size:.9em;">HVGA (320x480)</td> - <td style="font-size:.9em;">WVGA800 (480x800)<br>WVGA854 (480x854)</td> - <td style="font-size:.9em;"></td> + <td style="font-size:.9em;"><strong>WQVGA400 (240x400)</strong> + <br><strong>WQVGA432 (240x432)</strong></td> + <td style="font-size:.9em;"><strong>HVGA (320x480)</strong></td> + <td style="font-size:.9em;"><strong>WVGA800 (480x800)</strong> + <br><strong>WVGA854 (480x854)</strong> + <br>600x1024</td> + <td style="font-size:.9em;">640x960</td> </tr> <tr> <td style="background-color:#f3f3f3"> <em>Large</em> screen </td> - <td></td> - <td style="font-size:.9em;">WVGA800* (480x800)<br>WVGA854* (480x854)</td> + <td style="font-size:.9em;"><strong>WVGA800** (480x800)</strong> + <br><strong>WVGA854** (480x854)</strong></td> + <td style="font-size:.9em;"><strong>WVGA800* (480x800)</strong> + <br><strong>WVGA854* (480x854)</strong> + <br>600x1024</td> <td></td> <td></td> </tr> @@ -263,10 +290,13 @@ sizes and densities of emulator skins included in the Android SDK.</p> <td style="background-color:#f3f3f3"> <em>Extra Large</em> screen </td> - <td></td> - <td></td> - <td></td> - <td></td> + <td style="font-size:.9em;">600x1024</td> + <td style="font-size:.9em;">768x1024<br><strong>WXGA (768x1280)</strong> + <br>800x1280</td> + <td style="font-size:.9em;">1152x1536<br>1152x1920 + <br>1200x1920</td> + <td style="font-size:.9em;">1536x2048<br>1536x2560 + <br>1600x2560</td> </tr> <tr> <td colspan="4" style="border:none;font-size:90%;">* To emulate this @@ -274,6 +304,12 @@ sizes and densities of emulator skins included in the Android SDK.</p> creating an AVD that uses a WVGA800 or WVGA854 skin. </td> </tr> + <tr> + <td colspan="4" style="border:none;font-size:90%;">** To emulate this + configuration, specify a custom density of 120 when + creating an AVD that uses a WVGA800 or WVGA854 skin. + </td> + </tr> </table> <p>For an overview of the relative numbers of high (hdpi), medium (mdpi), and diff --git a/docs/html/guide/topics/resources/providing-resources.jd b/docs/html/guide/topics/resources/providing-resources.jd index 1583deefc79b..1da2622f2b8a 100644 --- a/docs/html/guide/topics/resources/providing-resources.jd +++ b/docs/html/guide/topics/resources/providing-resources.jd @@ -337,21 +337,25 @@ indicates the current locale.</p> <li>{@code small}: Screens based on the space available on a low-density QVGA screen. Considering a portrait HVGA display, this has the same available width but less height—it is 3:4 vs. HVGA's - 2:3 aspect ratio. Examples are QVGA low density and VGA high + 2:3 aspect ratio. The minimum layout size for this screen configuration + is approximately 320x426 dp units. Examples are QVGA low density and VGA high density.</li> <li>{@code normal}: Screens based on the traditional medium-density HVGA screen. A screen is considered to be normal if it is - at least this size (independent of density) and not larger. Examples + at least this size (independent of density) and not larger. The minimum + layout size for this screen configuration is approximately 320x470 dp units. Examples of such screens a WQVGA low density, HVGA medium density, WVGA high density.</li> <li>{@code large}: Screens based on the space available on a medium-density VGA screen. Such a screen has significantly more available space in both width and height than an HVGA display. + The minimum layout size for this screen configuration is approximately 480x640 dp units. Examples are VGA and WVGA medium density screens.</li> <li>{@code xlarge}: Screens that are considerably larger than the traditional - medium-density HVGA screen. In most cases, devices with extra large screens would be too -large to carry in a pocket and would most likely be tablet-style devices. <em>Added in API Level -9.</em></li> + medium-density HVGA screen. The minimum layout size for this screen configuration + is approximately 720x960 dp units. In most cases, devices with extra large + screens would be too large to carry in a pocket and would most likely + be tablet-style devices. <em>Added in API Level 9.</em></li> </ul> <p><em>Added in API Level 4.</em></p> <p>See <a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple diff --git a/include/ui/Input.h b/include/ui/Input.h index 8e8b61ba3ae3..b22986d8ae9c 100644 --- a/include/ui/Input.h +++ b/include/ui/Input.h @@ -128,6 +128,12 @@ enum { // input device or an application with system-wide event injection permission. POLICY_FLAG_TRUSTED = 0x02000000, + // Indicates that the input event has passed through an input filter. + POLICY_FLAG_FILTERED = 0x04000000, + + // Disables automatic key repeating behavior. + POLICY_FLAG_DISABLE_KEY_REPEAT = 0x08000000, + /* These flags are set by the input reader policy as it intercepts each event. */ // Indicates that the screen was off when the event was received and the event diff --git a/include/ui/egl/android_natives.h b/include/ui/egl/android_natives.h index 972e79996230..0a6e4fbd5e92 100644 --- a/include/ui/egl/android_natives.h +++ b/include/ui/egl/android_natives.h @@ -291,6 +291,9 @@ struct ANativeWindow void* reserved_proc[2]; }; +// Backwards compatibility... please switch to ANativeWindow. +typedef struct ANativeWindow android_native_window_t; + /* * native_window_set_usage(..., usage) * Sets the intended usage flags for the next buffers diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java index 7fdf4483b24f..7c181eeb569a 100644 --- a/media/java/android/media/ThumbnailUtils.java +++ b/media/java/android/media/ThumbnailUtils.java @@ -83,7 +83,7 @@ public class ThumbnailUtils { * * @param filePath the path of image file * @param kind could be MINI_KIND or MICRO_KIND - * @return Bitmap + * @return Bitmap, or null on failures * * @hide This method is only used by media framework and media provider internally. */ @@ -123,6 +123,8 @@ public class ThumbnailUtils { bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options); } catch (IOException ex) { Log.e(TAG, "", ex); + } catch (OutOfMemoryError oom) { + Log.e(TAG, "Unable to decode file " + filePath + ". OutOfMemoryError.", oom); } } diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp index 346d0bb47eac..9928f44459c2 100644 --- a/media/libstagefright/ACodec.cpp +++ b/media/libstagefright/ACodec.cpp @@ -1081,8 +1081,8 @@ void ACodec::sendFormatChange() { android_native_rect_t crop; crop.left = rect.nLeft; crop.top = rect.nTop; - crop.right = rect.nLeft + rect.nWidth - 1; - crop.bottom = rect.nTop + rect.nHeight - 1; + crop.right = rect.nLeft + rect.nWidth; + crop.bottom = rect.nTop + rect.nHeight; CHECK_EQ(0, native_window_set_crop( mNativeWindow.get(), &crop)); diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp index 5c43a5b5caaa..821ba9b9aa33 100644 --- a/media/libstagefright/NuHTTPDataSource.cpp +++ b/media/libstagefright/NuHTTPDataSource.cpp @@ -420,7 +420,14 @@ ssize_t NuHTTPDataSource::readAt(off64_t offset, void *data, size_t size) { internalRead((uint8_t *)data + numBytesRead, size - numBytesRead); if (n < 0) { - return n; + if (numBytesRead == 0 || mContentLengthValid) { + return n; + } + + // If there was an error we want to at least return the data + // we've already successfully read. The next call to read will + // then return the error. + n = 0; } int64_t delayUs = ALooper::GetNowUs() - startTimeUs; diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 904bd6263530..c278992d4ae8 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -2240,8 +2240,8 @@ void OMXCodec::onEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { android_native_rect_t crop; crop.left = left; crop.top = top; - crop.right = right; - crop.bottom = bottom; + crop.right = right + 1; + crop.bottom = bottom + 1; // We'll ignore any errors here, if the surface is // already invalid, we'll know soon enough. diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp index 6ea068ae003e..8363e8bfc606 100644 --- a/services/input/InputDispatcher.cpp +++ b/services/input/InputDispatcher.cpp @@ -186,7 +186,7 @@ InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& polic mPolicy(policy), mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX), mNextUnblockedEvent(NULL), - mDispatchEnabled(true), mDispatchFrozen(false), + mDispatchEnabled(true), mDispatchFrozen(false), mInputFilterEnabled(false), mFocusedWindow(NULL), mFocusedApplication(NULL), mCurrentInputTargetsValid(false), @@ -725,7 +725,7 @@ bool InputDispatcher::dispatchKeyLocked( if (entry->repeatCount == 0 && entry->action == AKEY_EVENT_ACTION_DOWN && (entry->policyFlags & POLICY_FLAG_TRUSTED) - && !entry->isInjected()) { + && (!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) { if (mKeyRepeatState.lastKeyEntry && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) { // We have seen two identical key downs in a row which indicates that the device @@ -2402,7 +2402,18 @@ void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t so bool needWake; { // acquire lock - AutoMutex _l(mLock); + mLock.lock(); + + if (mInputFilterEnabled) { + mLock.unlock(); + + policyFlags |= POLICY_FLAG_FILTERED; + if (!mPolicy->filterInputEvent(&event, policyFlags)) { + return; // event was consumed by the filter + } + + mLock.lock(); + } int32_t repeatCount = 0; KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime, @@ -2410,6 +2421,7 @@ void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t so metaState, repeatCount, downTime); needWake = enqueueInboundEventLocked(newEntry); + mLock.unlock(); } // release lock if (needWake) { @@ -2452,7 +2464,23 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t bool needWake; { // acquire lock - AutoMutex _l(mLock); + mLock.lock(); + + if (mInputFilterEnabled) { + mLock.unlock(); + + MotionEvent event; + event.initialize(deviceId, source, action, flags, edgeFlags, metaState, 0, 0, + xPrecision, yPrecision, downTime, eventTime, + pointerCount, pointerIds, pointerCoords); + + policyFlags |= POLICY_FLAG_FILTERED; + if (!mPolicy->filterInputEvent(&event, policyFlags)) { + return; // event was consumed by the filter + } + + mLock.lock(); + } // Attempt batching and streaming of move events. if (action == AMOTION_EVENT_ACTION_MOVE @@ -2491,6 +2519,7 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t LOGD("Appended motion sample onto batch for most recent " "motion event for this device in the inbound queue."); #endif + mLock.unlock(); return; // done! } @@ -2579,6 +2608,7 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t true /*resumeWithAppendedMotionSample*/); runCommandsLockedInterruptible(); + mLock.unlock(); return; // done! } } @@ -2593,6 +2623,7 @@ NoBatchingOrStreaming:; pointerCount, pointerIds, pointerCoords); needWake = enqueueInboundEventLocked(newEntry); + mLock.unlock(); } // release lock if (needWake) { @@ -2612,16 +2643,17 @@ void InputDispatcher::notifySwitch(nsecs_t when, int32_t switchCode, int32_t swi } int32_t InputDispatcher::injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags) { #if DEBUG_INBOUND_EVENT_DETAILS LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, " - "syncMode=%d, timeoutMillis=%d", - event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis); + "syncMode=%d, timeoutMillis=%d, policyFlags=0x%08x", + event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis, policyFlags); #endif nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis); - uint32_t policyFlags = POLICY_FLAG_INJECTED; + policyFlags |= POLICY_FLAG_INJECTED; if (hasInjectionPermission(injectorPid, injectorUid)) { policyFlags |= POLICY_FLAG_TRUSTED; } @@ -2640,7 +2672,9 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, policyFlags |= POLICY_FLAG_VIRTUAL; } - mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags); + if (!(policyFlags & POLICY_FLAG_FILTERED)) { + mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags); + } if (policyFlags & POLICY_FLAG_WOKE_HERE) { flags |= AKEY_EVENT_FLAG_WOKE_HERE; @@ -2664,8 +2698,10 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, return INPUT_EVENT_INJECTION_FAILED; } - nsecs_t eventTime = motionEvent->getEventTime(); - mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags); + if (!(policyFlags & POLICY_FLAG_FILTERED)) { + nsecs_t eventTime = motionEvent->getEventTime(); + mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags); + } mLock.lock(); const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes(); @@ -2780,7 +2816,8 @@ void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t inject injectionResult, injectionState->injectorPid, injectionState->injectorUid); #endif - if (injectionState->injectionIsAsync) { + if (injectionState->injectionIsAsync + && !(entry->policyFlags & POLICY_FLAG_FILTERED)) { // Log the outcome since the injector did not wait for the injection result. switch (injectionResult) { case INPUT_EVENT_INJECTION_SUCCEEDED: @@ -2982,6 +3019,26 @@ void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) { } } +void InputDispatcher::setInputFilterEnabled(bool enabled) { +#if DEBUG_FOCUS + LOGD("setInputFilterEnabled: enabled=%d", enabled); +#endif + + { // acquire lock + AutoMutex _l(mLock); + + if (mInputFilterEnabled == enabled) { + return; + } + + mInputFilterEnabled = enabled; + resetAndDropEverythingLocked("input filter is being enabled or disabled"); + } // release lock + + // Wake up poll loop since there might be work to do to drop everything. + mLooper->wake(); +} + bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel, const sp<InputChannel>& toChannel) { #if DEBUG_FOCUS diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h index 48e4d439a6ec..162e6066ecb5 100644 --- a/services/input/InputDispatcher.h +++ b/services/input/InputDispatcher.h @@ -176,6 +176,13 @@ public: */ virtual int32_t getMaxEventsPerSecond() = 0; + /* Filters an input event. + * Return true to dispatch the event unmodified, false to consume the event. + * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED + * to injectInputEvent. + */ + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) = 0; + /* Intercepts a key event immediately before queueing it. * The policy can use this method as an opportunity to perform power management functions * and early event preprocessing such as updating policy flags. @@ -266,7 +273,8 @@ public: * 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, int32_t syncMode, int32_t timeoutMillis) = 0; + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags) = 0; /* Sets the list of input windows. * @@ -286,6 +294,14 @@ public: */ virtual void setInputDispatchMode(bool enabled, bool frozen) = 0; + /* Sets whether input event filtering is enabled. + * When enabled, incoming input events are sent to the policy's filterInputEvent + * method instead of being dispatched. The filter is expected to use + * injectInputEvent to inject the events it would like to have dispatched. + * It should include POLICY_FLAG_FILTERED in the policy flags during injection. + */ + virtual void setInputFilterEnabled(bool enabled) = 0; + /* Transfers touch focus from the window associated with one channel to the * window associated with the other channel. * @@ -345,11 +361,13 @@ public: int32_t switchCode, int32_t switchValue, uint32_t policyFlags) ; virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis); + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags); virtual void setInputWindows(const Vector<InputWindow>& inputWindows); virtual void setFocusedApplication(const InputApplication* inputApplication); virtual void setInputDispatchMode(bool enabled, bool frozen); + virtual void setInputFilterEnabled(bool enabled); virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, const sp<InputChannel>& toChannel); @@ -863,6 +881,7 @@ private: // Dispatch state. bool mDispatchEnabled; bool mDispatchFrozen; + bool mInputFilterEnabled; Vector<InputWindow> mWindows; diff --git a/services/input/tests/InputDispatcher_test.cpp b/services/input/tests/InputDispatcher_test.cpp index 2f846c47ba3c..3650da04f711 100644 --- a/services/input/tests/InputDispatcher_test.cpp +++ b/services/input/tests/InputDispatcher_test.cpp @@ -67,6 +67,10 @@ private: return 60; } + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) { + return true; + } + virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) { } @@ -124,7 +128,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { /*action*/ -1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject key events with undefined action."; // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API. @@ -132,7 +136,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { AKEY_EVENT_ACTION_MULTIPLE, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject key events with ACTION_MULTIPLE."; } @@ -150,7 +154,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with undefined action."; // Rejects pointer down with invalid index. @@ -160,7 +164,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer down index too large."; event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, @@ -169,7 +173,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer down index too small."; // Rejects pointer up with invalid index. @@ -179,7 +183,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer up index too large."; event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, @@ -188,7 +192,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer up index too small."; // Rejects motion events with invalid number of pointers. @@ -197,7 +201,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 0, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with 0 pointers."; event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, @@ -205,7 +209,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ MAX_POINTERS + 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with more than MAX_POINTERS pointers."; // Rejects motion events with invalid pointer ids. @@ -215,7 +219,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer ids less than 0."; pointerIds[0] = MAX_POINTER_ID + 1; @@ -224,7 +228,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer ids greater than MAX_POINTER_ID."; // Rejects motion events with duplicate pointer ids. @@ -235,7 +239,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 2, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with duplicate pointer ids."; } diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp index 60549c65fd0a..4c5f2397a564 100644 --- a/services/input/tests/InputReader_test.cpp +++ b/services/input/tests/InputReader_test.cpp @@ -369,7 +369,8 @@ private: } virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags) { ADD_FAILURE() << "Should never be called by input reader."; return INPUT_EVENT_INJECTION_FAILED; } @@ -386,6 +387,10 @@ private: ADD_FAILURE() << "Should never be called by input reader."; } + virtual void setInputFilterEnabled(bool enabled) { + ADD_FAILURE() << "Should never be called by input reader."; + } + virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, const sp<InputChannel>& toChannel) { ADD_FAILURE() << "Should never be called by input reader."; diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 12ac052617bf..0b0e1efaab42 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -543,18 +543,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ public NetworkInfo getActiveNetworkInfo() { enforceAccessPermission(); - for (int type=0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) { - if (mNetAttributes[type] == null || !mNetAttributes[type].isDefault()) { - continue; - } - NetworkStateTracker t = mNetTrackers[type]; - NetworkInfo info = t.getNetworkInfo(); - if (info.isConnected()) { - if (DBG && type != mActiveDefaultNetwork) { - loge("connected default network is not mActiveDefaultNetwork!"); - } - return info; - } + if (mActiveDefaultNetwork != -1) { + return mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); } return null; } @@ -1353,7 +1343,20 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleApplyDefaultProxy(netType); addDefaultRoute(mNetTrackers[netType]); } else { - addPrivateDnsRoutes(mNetTrackers[netType]); + // many radios add a default route even when we don't want one. + // remove the default interface unless we need it for our active network + if (mActiveDefaultNetwork != -1) { + LinkProperties linkProperties = + mNetTrackers[mActiveDefaultNetwork].getLinkProperties(); + LinkProperties newLinkProperties = + mNetTrackers[netType].getLinkProperties(); + String defaultIface = linkProperties.getInterfaceName(); + if (defaultIface != null && + !defaultIface.equals(newLinkProperties.getInterfaceName())) { + mNetTrackers[netType].removeDefaultRoute(); + } + } + mNetTrackers[netType].addPrivateDnsRoutes(); } } else { if (mNetAttributes[netType].isDefault()) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 4e1bbacb1d5f..cd8915d82877 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -16,6 +16,7 @@ package com.android.server; +import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.am.ActivityManagerService; import com.android.server.pm.PackageManagerService; import com.android.server.usb.UsbService; diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java index 2fcdb5d5502f..c39dc805e684 100755 --- a/services/java/com/android/server/VibratorService.java +++ b/services/java/com/android/server/VibratorService.java @@ -247,6 +247,7 @@ public class VibratorService extends IVibratorService.Stub { // Lock held on mVibrations private void startNextVibrationLocked() { if (mVibrations.size() <= 0) { + mCurrentVibration = null; return; } mCurrentVibration = mVibrations.getFirst(); @@ -273,17 +274,27 @@ public class VibratorService extends IVibratorService.Stub { Vibration vib = iter.next(); if (vib.mToken == token) { iter.remove(); + unlinkVibration(vib); return vib; } } // We might be looking for a simple vibration which is only stored in // mCurrentVibration. if (mCurrentVibration != null && mCurrentVibration.mToken == token) { + unlinkVibration(mCurrentVibration); return mCurrentVibration; } return null; } + private void unlinkVibration(Vibration vib) { + if (vib.mPattern != null) { + // If Vibration object has a pattern, + // the Vibration object has also been linkedToDeath. + vib.mToken.unlinkToDeath(vib, 0); + } + } + private class VibrateThread extends Thread { final Vibration mVibration; boolean mDone; @@ -360,6 +371,7 @@ public class VibratorService extends IVibratorService.Stub { // If this vibration finished naturally, start the next // vibration. mVibrations.remove(mVibration); + unlinkVibration(mVibration); startNextVibrationLocked(); } } diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java new file mode 100644 index 000000000000..ced8feb450da --- /dev/null +++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility; + +import com.android.server.wm.InputFilter; + +import android.content.Context; +import android.util.Slog; +import android.view.InputEvent; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy; + +/** + * Input filter for accessibility. + * + * Currently just a stub but will eventually implement touch exploration, etc. + */ +public class AccessibilityInputFilter extends InputFilter { + private static final String TAG = "AccessibilityInputFilter"; + private static final boolean DEBUG = true; + + private final Context mContext; + + public AccessibilityInputFilter(Context context) { + super(context.getMainLooper()); + mContext = context; + } + + @Override + public void onInstalled() { + if (DEBUG) { + Slog.d(TAG, "Accessibility input filter installed."); + } + super.onInstalled(); + } + + @Override + public void onUninstalled() { + if (DEBUG) { + Slog.d(TAG, "Accessibility input filter uninstalled."); + } + super.onUninstalled(); + } + + @Override + public void onInputEvent(InputEvent event, int policyFlags) { + if (DEBUG) { + Slog.d(TAG, "Accessibility input filter received input event: " + + event + ", policyFlags=0x" + Integer.toHexString(policyFlags)); + } + + // To prove that this is working as intended, we will silently transform + // Q key presses into non-repeating Z's as part of this stub implementation. + // TODO: Replace with the real thing. + if (event instanceof KeyEvent) { + final KeyEvent keyEvent = (KeyEvent)event; + if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_Q) { + if (keyEvent.getRepeatCount() == 0) { + sendInputEvent(new KeyEvent(keyEvent.getDownTime(), keyEvent.getEventTime(), + keyEvent.getAction(), KeyEvent.KEYCODE_Z, keyEvent.getRepeatCount(), + keyEvent.getMetaState(), keyEvent.getDeviceId(), keyEvent.getScanCode(), + keyEvent.getFlags(), keyEvent.getSource()), + policyFlags | WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT); + } + return; + } + } + + super.onInputEvent(event, policyFlags); + } +} diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 04ae4901d74b..5257fb0d1630 100644 --- a/services/java/com/android/server/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -14,11 +14,12 @@ ** limitations under the License. */ -package com.android.server; +package com.android.server.accessibility; import com.android.internal.content.PackageMonitor; import com.android.internal.os.HandlerCaller; import com.android.internal.os.HandlerCaller.SomeArgs; +import com.android.server.wm.WindowManagerService; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; @@ -43,6 +44,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; @@ -106,6 +108,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private int mHandledFeedbackTypes = 0; private boolean mIsEnabled; + private AccessibilityInputFilter mInputFilter; /** * Handler for delayed event dispatch. @@ -131,7 +134,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * * @param context A {@link Context} instance. */ - AccessibilityManagerService(Context context) { + public AccessibilityManagerService(Context context) { mContext = context; mPackageManager = mContext.getPackageManager(); mCaller = new HandlerCaller(context, this); @@ -209,6 +212,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } manageServicesLocked(); + updateInputFilterLocked(); } return; @@ -249,6 +253,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub unbindAllServicesLocked(); } updateClientsLocked(); + updateInputFilterLocked(); } } }); @@ -621,6 +626,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** + * Installs or removes the accessibility input filter when accessibility is enabled + * or disabled. + */ + private void updateInputFilterLocked() { + WindowManagerService wm = (WindowManagerService)ServiceManager.getService( + Context.WINDOW_SERVICE); + if (wm != null) { + if (mIsEnabled) { + if (mInputFilter == null) { + mInputFilter = new AccessibilityInputFilter(mContext); + } + wm.setInputFilter(mInputFilter); + } else { + wm.setInputFilter(null); + } + } + } + + /** * This class represents an accessibility service. It stores all per service * data required for the service management, provides API for starting/stopping the * service and is responsible for adding/removing the service in the data structures diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java index b7f63463ed4b..a151af07f8c2 100644 --- a/services/java/com/android/server/usb/UsbService.java +++ b/services/java/com/android/server/usb/UsbService.java @@ -561,11 +561,14 @@ public class UsbService extends IUsbManager.Stub { case MSG_UPDATE_STATE: if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) { if (mConnected == 0) { - // make sure accessory mode is off, and restore default functions - if (mCurrentAccessory != null && UsbManager.setFunctionEnabled( - UsbManager.USB_FUNCTION_ACCESSORY, false)) { + if (UsbManager.isFunctionEnabled( + UsbManager.USB_FUNCTION_ACCESSORY)) { + // make sure accessory mode is off, and restore default functions Log.d(TAG, "exited USB accessory mode"); - + if (!UsbManager.setFunctionEnabled + (UsbManager.USB_FUNCTION_ACCESSORY, false)) { + Log.e(TAG, "could not disable accessory function"); + } int count = mDefaultFunctions.size(); for (int i = 0; i < count; i++) { String function = mDefaultFunctions.get(i); @@ -574,8 +577,10 @@ public class UsbService extends IUsbManager.Stub { } } - mDeviceManager.accessoryDetached(mCurrentAccessory); - mCurrentAccessory = null; + if (mCurrentAccessory != null) { + mDeviceManager.accessoryDetached(mCurrentAccessory); + mCurrentAccessory = null; + } } } diff --git a/services/java/com/android/server/wm/InputFilter.java b/services/java/com/android/server/wm/InputFilter.java new file mode 100644 index 000000000000..78b87fe1b730 --- /dev/null +++ b/services/java/com/android/server/wm/InputFilter.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.InputEvent; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy; + +/** + * Filters input events before they are dispatched to the system. + * <p> + * At most one input filter can be installed by calling + * {@link WindowManagerService#setInputFilter}. When an input filter is installed, the + * system's behavior changes as follows: + * <ul> + * <li>Input events are first delivered to the {@link WindowManagerPolicy} + * interception methods before queueing as usual. This critical step takes care of managing + * the power state of the device and handling wake keys.</li> + * <li>Input events are then asynchronously delivered to the input filter's + * {@link #onInputEvent(InputEvent)} method instead of being enqueued for dispatch to + * applications as usual. The input filter only receives input events that were + * generated by input device; the input filter will not receive input events that were + * injected into the system by other means, such as by instrumentation.</li> + * <li>The input filter processes and optionally transforms the stream of events. For example, + * it may transform a sequence of motion events representing an accessibility gesture into + * a different sequence of motion events, key presses or other system-level interactions. + * The input filter can send events to be dispatched by calling + * {@link #sendInputEvent(InputEvent)} and passing appropriate policy flags for the + * input event.</li> + * </ul> + * </p> + * <h3>The importance of input event consistency</h3> + * <p> + * The input filter mechanism is very low-level. At a minimum, it needs to ensure that it + * sends an internally consistent stream of input events to the dispatcher. There are + * very important invariants to be maintained. + * </p><p> + * For example, if a key down is sent, a corresponding key up should also be sent eventually. + * Likewise, for touch events, each pointer must individually go down with + * {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN} and then + * individually go up with {@link MotionEvent#ACTION_POINTER_UP} or {@link MotionEvent#ACTION_UP} + * and the sequence of pointer ids used must be consistent throughout the gesture. + * </p><p> + * Sometimes a filter may wish to cancel a previously dispatched key or motion. It should + * use {@link KeyEvent#FLAG_CANCELED} or {@link MotionEvent#ACTION_CANCEL} accordingly. + * </p><p> + * The input filter must take into account the fact that the input events coming from different + * devices or even different sources all consist of distinct streams of input. + * Use {@link InputEvent#getDeviceId()} and {@link InputEvent#getSource()} to identify + * the source of the event and its semantics. There are be multiple sources of keys, + * touches and other input: they must be kept separate. + * </p> + * <h3>Policy flags</h3> + * <p> + * Input events received from the dispatcher and sent to the dispatcher have policy flags + * associated with them. Policy flags control some functions of the dispatcher. + * </p><p> + * The early policy interception decides whether an input event should be delivered + * to applications or dropped. The policy indicates its decision by setting the + * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} policy flag. The input filter may + * sometimes receive events that do not have this flag set. It should take note of + * the fact that the policy intends to drop the event, clean up its state, and + * then send appropriate cancelation events to the dispatcher if needed. + * </p><p> + * For example, suppose the input filter is processing a gesture and one of the touch events + * it receives does not have the {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag set. + * The input filter should clear its internal state about the gesture and then send key or + * motion events to the dispatcher to cancel any keys or pointers that are down. + * </p><p> + * Corollary: Events that set sent to the dispatcher should usually include the + * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped! + * </p><p> + * It may be prudent to disable automatic key repeating for synthetically generated + * keys by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag. + * </p> + */ +public abstract class InputFilter { + private static final int MSG_INSTALL = 1; + private static final int MSG_UNINSTALL = 2; + private static final int MSG_INPUT_EVENT = 3; + + private final H mH; + private Host mHost; + + /** + * Creates the input filter. + * + * @param looper The looper to run callbacks on. + */ + public InputFilter(Looper looper) { + mH = new H(looper); + } + + /** + * Called when the input filter is installed. + * This method is guaranteed to be non-reentrant. + * + * @param host The input filter host environment. + */ + final void install(Host host) { + mH.obtainMessage(MSG_INSTALL, host).sendToTarget(); + } + + /** + * Called when the input filter is uninstalled. + * This method is guaranteed to be non-reentrant. + */ + final void uninstall() { + mH.obtainMessage(MSG_UNINSTALL).sendToTarget(); + } + + /** + * Called to enqueue the input event for filtering. + * The event will be recycled after the input filter processes it. + * This method is guaranteed to be non-reentrant. + * + * @param event The input event to enqueue. + */ + final void filterInputEvent(InputEvent event, int policyFlags) { + mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, 0, event).sendToTarget(); + } + + /** + * Sends an input event to the dispatcher. + * + * @param event The input event to publish. + * @param policyFlags The input event policy flags. + */ + public void sendInputEvent(InputEvent event, int policyFlags) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + if (mHost == null) { + throw new IllegalStateException("Cannot send input event because the input filter " + + "is not installed."); + } + mHost.sendInputEvent(event, policyFlags); + } + + /** + * Called when an input event has been received from the dispatcher. + * <p> + * The default implementation sends the input event back to the dispatcher, unchanged. + * </p><p> + * The event will be recycled when this method returns. If you want to keep it around, + * make a copy! + * </p> + * + * @param event The input event that was received. + * @param policyFlags The input event policy flags. + */ + public void onInputEvent(InputEvent event, int policyFlags) { + sendInputEvent(event, policyFlags); + } + + /** + * Called when the filter is installed into the dispatch pipeline. + * <p> + * This method is called before the input filter receives any input events. + * The input filter should take this opportunity to prepare itself. + * </p> + */ + public void onInstalled() { + } + + /** + * Called when the filter is uninstalled from the dispatch pipeline. + * <p> + * This method is called after the input filter receives its last input event. + * The input filter should take this opportunity to clean up. + * </p> + */ + public void onUninstalled() { + } + + private final class H extends Handler { + public H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_INSTALL: + mHost = (Host)msg.obj; + onInstalled(); + break; + + case MSG_UNINSTALL: + try { + onUninstalled(); + } finally { + mHost = null; + } + break; + + case MSG_INPUT_EVENT: { + final InputEvent event = (InputEvent)msg.obj; + try { + onInputEvent(event, msg.arg1); + } finally { + event.recycle(); + } + break; + } + } + } + } + + interface Host { + public void sendInputEvent(InputEvent event, int policyFlags); + } +} diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java index ca1da95f34eb..b0978a3f251c 100644 --- a/services/java/com/android/server/wm/InputManager.java +++ b/services/java/com/android/server/wm/InputManager.java @@ -42,6 +42,7 @@ import android.view.KeyEvent; import android.view.Surface; import android.view.ViewConfiguration; import android.view.WindowManager; +import android.view.WindowManagerPolicy; import java.io.File; import java.io.FileNotFoundException; @@ -78,8 +79,10 @@ public class InputManager { private static native void nativeRegisterInputChannel(InputChannel inputChannel, InputWindowHandle inputWindowHandle, boolean monitor); private static native void nativeUnregisterInputChannel(InputChannel inputChannel); + private static native void nativeSetInputFilterEnabled(boolean enable); private static native int nativeInjectInputEvent(InputEvent event, - int injectorPid, int injectorUid, int syncMode, int timeoutMillis); + int injectorPid, int injectorUid, int syncMode, int timeoutMillis, + int policyFlags); private static native void nativeSetInputWindows(InputWindow[] windows); private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen); private static native void nativeSetSystemUiVisibility(int visibility); @@ -117,6 +120,11 @@ public class InputManager { /** 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; + // State for the currently installed input filter. + final Object mInputFilterLock = new Object(); + InputFilter mInputFilter; + InputFilterHost mInputFilterHost; + public InputManager(Context context, WindowManagerService windowManagerService) { this.mContext = context; this.mWindowManagerService = windowManagerService; @@ -268,7 +276,42 @@ public class InputManager { nativeUnregisterInputChannel(inputChannel); } - + + /** + * Sets an input filter that will receive all input events before they are dispatched. + * The input filter may then reinterpret input events or inject new ones. + * + * To ensure consistency, the input dispatcher automatically drops all events + * in progress whenever an input filter is installed or uninstalled. After an input + * filter is uninstalled, it can no longer send input events unless it is reinstalled. + * Any events it attempts to send after it has been uninstalled will be dropped. + * + * @param filter The input filter, or null to remove the current filter. + */ + public void setInputFilter(InputFilter filter) { + synchronized (mInputFilterLock) { + final InputFilter oldFilter = mInputFilter; + if (oldFilter == filter) { + return; // nothing to do + } + + if (oldFilter != null) { + mInputFilter = null; + mInputFilterHost.disconnectLocked(); + mInputFilterHost = null; + oldFilter.uninstall(); + } + + if (filter != null) { + mInputFilter = filter; + mInputFilterHost = new InputFilterHost(); + filter.install(mInputFilterHost); + } + + nativeSetInputFilterEnabled(filter != null); + } + } + /** * Injects an input event into the event system on behalf of an application. * The synchronization mode determines whether the method blocks while waiting for @@ -304,9 +347,10 @@ public class InputManager { throw new IllegalArgumentException("timeoutMillis must be positive"); } - return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis); + return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis, + WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT); } - + /** * Gets information about the input device with the specified id. * @param id The device id. @@ -370,6 +414,27 @@ public class InputManager { } } + private final class InputFilterHost implements InputFilter.Host { + private boolean mDisconnected; + + public void disconnectLocked() { + mDisconnected = true; + } + + public void sendInputEvent(InputEvent event, int policyFlags) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + + synchronized (mInputFilterLock) { + if (!mDisconnected) { + nativeInjectInputEvent(event, 0, 0, INPUT_EVENT_INJECTION_SYNC_NONE, 0, + policyFlags | WindowManagerPolicy.FLAG_FILTERED); + } + } + } + } + private static final class PointerIcon { public Bitmap bitmap; public float hotSpotX; @@ -415,7 +480,7 @@ public class InputManager { /* * Callbacks from native. */ - private class Callbacks { + private final class Callbacks { static final String TAG = "InputManager-Callbacks"; private static final boolean DEBUG_VIRTUAL_KEYS = false; @@ -443,7 +508,19 @@ public class InputManager { return mWindowManagerService.mInputMonitor.notifyANR( inputApplicationHandle, inputWindowHandle); } - + + @SuppressWarnings("unused") + final boolean filterInputEvent(InputEvent event, int policyFlags) { + synchronized (mInputFilterLock) { + if (mInputFilter != null) { + mInputFilter.filterInputEvent(event, policyFlags); + return false; + } + } + event.recycle(); + return true; + } + @SuppressWarnings("unused") public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing( diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 33e6a36d73b8..79c45187ed71 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -4604,6 +4604,10 @@ public class WindowManagerService extends IWindowManager.Stub return mInputManager.monitorInput(inputChannelName); } + public void setInputFilter(InputFilter filter) { + mInputManager.setInputFilter(filter); + } + public InputDevice getInputDevice(int deviceId) { return mInputManager.getInputDevice(deviceId); } diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index 96cf4bd451ae..ab2c125667ca 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -56,6 +56,7 @@ static struct { jmethodID notifyLidSwitchChanged; jmethodID notifyInputChannelBroken; jmethodID notifyANR; + jmethodID filterInputEvent; jmethodID interceptKeyBeforeQueueing; jmethodID interceptMotionBeforeQueueingWhenScreenOff; jmethodID interceptKeyBeforeDispatching; @@ -174,6 +175,7 @@ public: virtual nsecs_t getKeyRepeatTimeout(); virtual nsecs_t getKeyRepeatDelay(); virtual int32_t getMaxEventsPerSecond(); + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags); virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags); virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags); virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle, @@ -638,6 +640,38 @@ bool NativeInputManager::isScreenBright() { return android_server_PowerManagerService_isScreenBright(); } +bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) { + jobject inputEventObj; + + JNIEnv* env = jniEnv(); + switch (inputEvent->getType()) { + case AINPUT_EVENT_TYPE_KEY: + inputEventObj = android_view_KeyEvent_fromNative(env, + static_cast<const KeyEvent*>(inputEvent)); + break; + case AINPUT_EVENT_TYPE_MOTION: + inputEventObj = android_view_MotionEvent_obtainAsCopy(env, + static_cast<const MotionEvent*>(inputEvent)); + break; + default: + return true; // dispatch the event normally + } + + if (!inputEventObj) { + LOGE("Failed to obtain input event object for filterInputEvent."); + return true; // dispatch the event normally + } + + // The callee is responsible for recycling the event. + jboolean pass = env->CallBooleanMethod(mCallbacksObj, gCallbacksClassInfo.filterInputEvent, + inputEventObj, policyFlags); + if (checkAndClearExceptionFromCallback(env, "filterInputEvent")) { + pass = true; + } + env->DeleteLocalRef(inputEventObj); + return pass; +} + void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) { // Policy: @@ -1005,9 +1039,18 @@ static void android_server_InputManager_nativeUnregisterInputChannel(JNIEnv* env } } +static void android_server_InputManager_nativeSetInputFilterEnabled(JNIEnv* env, jclass clazz, + jboolean enabled) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->getInputManager()->getDispatcher()->setInputFilterEnabled(enabled); +} + static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jclass clazz, jobject inputEventObj, jint injectorPid, jint injectorUid, - jint syncMode, jint timeoutMillis) { + jint syncMode, jint timeoutMillis, jint policyFlags) { if (checkInputManagerUnitialized(env)) { return INPUT_EVENT_INJECTION_FAILED; } @@ -1021,7 +1064,8 @@ static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jcla } return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent( - & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis); + & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis, + uint32_t(policyFlags)); } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) { const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj); if (!motionEvent) { @@ -1030,7 +1074,8 @@ static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jcla } return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent( - motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis); + motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis, + uint32_t(policyFlags)); } else { jniThrowRuntimeException(env, "Invalid input event type."); return INPUT_EVENT_INJECTION_FAILED; @@ -1200,7 +1245,9 @@ static JNINativeMethod gInputManagerMethods[] = { (void*) android_server_InputManager_nativeRegisterInputChannel }, { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V", (void*) android_server_InputManager_nativeUnregisterInputChannel }, - { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIII)I", + { "nativeSetInputFilterEnabled", "(Z)V", + (void*) android_server_InputManager_nativeSetInputFilterEnabled }, + { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIIII)I", (void*) android_server_InputManager_nativeInjectInputEvent }, { "nativeSetInputWindows", "([Lcom/android/server/wm/InputWindow;)V", (void*) android_server_InputManager_nativeSetInputWindows }, @@ -1257,6 +1304,9 @@ int register_android_server_InputManager(JNIEnv* env) { "notifyANR", "(Lcom/android/server/wm/InputApplicationHandle;Lcom/android/server/wm/InputWindowHandle;)J"); + GET_METHOD_ID(gCallbacksClassInfo.filterInputEvent, clazz, + "filterInputEvent", "(Landroid/view/InputEvent;I)Z"); + GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeQueueing, clazz, "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;IZ)I"); diff --git a/tests/CoreTests/android/core/HttpHeaderTest.java b/tests/CoreTests/android/core/HttpHeaderTest.java index a5d4857824c2..eedbc3fbada1 100644 --- a/tests/CoreTests/android/core/HttpHeaderTest.java +++ b/tests/CoreTests/android/core/HttpHeaderTest.java @@ -19,12 +19,19 @@ import android.test.AndroidTestCase; import org.apache.http.util.CharArrayBuffer; import android.net.http.Headers; +import android.util.Log; +import android.webkit.CacheManager; +import android.webkit.CacheManager.CacheResult; + +import java.lang.reflect.Method; public class HttpHeaderTest extends AndroidTestCase { static final String LAST_MODIFIED = "Last-Modified: Fri, 18 Jun 2010 09:56:47 GMT"; static final String CACHE_CONTROL_MAX_AGE = "Cache-Control:max-age=15"; static final String CACHE_CONTROL_PRIVATE = "Cache-Control: private"; + static final String CACHE_CONTROL_COMPOUND = "Cache-Control: no-cache, max-age=200000"; + static final String CACHE_CONTROL_COMPOUND2 = "Cache-Control: max-age=200000, no-cache"; /** * Tests that cache control header supports multiple instances of the header, @@ -59,4 +66,39 @@ public class HttpHeaderTest extends AndroidTestCase { h.parseHeader(buffer); assertEquals("max-age=15,private", h.getCacheControl()); } + + // Test that cache behaves correctly when receiving a compund + // cache-control statement containing no-cache and max-age argument. + // + // If a cache control header contains both a max-age arument and + // a no-cache argument the max-age argument should be ignored. + // The resource can be cached, but a validity check must be done on + // every request. Test case checks that the expiry time is 0 for + // this item, so item will be validated on subsequent requests. + public void testCacheControlMultipleArguments() throws Exception { + // get private method CacheManager.parseHeaders() + Method m = CacheManager.class.getDeclaredMethod("parseHeaders", + new Class[] {int.class, Headers.class, String.class}); + m.setAccessible(true); + + // create indata + Headers h = new Headers(); + CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append(CACHE_CONTROL_COMPOUND); + h.parseHeader(buffer); + + CacheResult c = (CacheResult)m.invoke(null, 200, h, "text/html"); + + // Check that expires is set to 0, to ensure that no-cache has overridden + // the max-age argument + assertEquals(0, c.getExpires()); + + // check reverse order + buffer.clear(); + buffer.append(CACHE_CONTROL_COMPOUND2); + h.parseHeader(buffer); + + c = (CacheResult)m.invoke(null, 200, h, "text/html"); + assertEquals(0, c.getExpires()); + } } |