diff options
59 files changed, 1833 insertions, 176 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/api/current.xml b/api/current.xml index ded1d1b3d539..58aa1fd95376 100644 --- a/api/current.xml +++ b/api/current.xml @@ -15703,6 +15703,17 @@ visibility="public" > </field> +<field name="Theme_Holo_Light_NoActionBar" + type="int" + transient="false" + volatile="false" + value="16974064" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="Theme_Holo_Light_Panel" type="int" transient="false" @@ -259887,7 +259898,7 @@ synchronized="false" static="false" final="false" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > <parameter name="appWidgetId" type="int"> @@ -259897,6 +259908,21 @@ <parameter name="intent" type="android.content.Intent"> </parameter> </method> +<method name="setRemoteAdapter" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="viewId" type="int"> +</parameter> +<parameter name="intent" type="android.content.Intent"> +</parameter> +</method> <method name="setScrollPosition" return="void" abstract="false" 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/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java index d426d1247c7b..6e2168b21aee 100644 --- a/core/java/android/text/GraphicsOperations.java +++ b/core/java/android/text/GraphicsOperations.java @@ -58,6 +58,13 @@ extends CharSequence int flags, float[] advances, int advancesIndex, Paint paint); /** + * Just like {@link Paint#getTextRunAdvances}. + * @hide + */ + float getTextRunAdvancesICU(int start, int end, int contextStart, int contextEnd, + int flags, float[] advances, int advancesIndex, Paint paint); + + /** * Just like {@link Paint#getTextRunCursor}. * @hide */ diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index ea5cdfebfdea..ff6a4cddef15 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -1170,6 +1170,35 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable, } /** + * Don't call this yourself -- exists for Paint to use internally. + * {@hide} + */ + public float getTextRunAdvancesICU(int start, int end, int contextStart, int contextEnd, int flags, + float[] advances, int advancesPos, Paint p) { + + float ret; + + int contextLen = contextEnd - contextStart; + int len = end - start; + + if (end <= mGapStart) { + ret = p.getTextRunAdvancesICU(mText, start, len, contextStart, contextLen, + flags, advances, advancesPos); + } else if (start >= mGapStart) { + ret = p.getTextRunAdvancesICU(mText, start + mGapLength, len, + contextStart + mGapLength, contextLen, flags, advances, advancesPos); + } else { + char[] buf = TextUtils.obtain(contextLen); + getChars(contextStart, contextEnd, buf, 0); + ret = p.getTextRunAdvancesICU(buf, start - contextStart, len, + 0, contextLen, flags, advances, advancesPos); + TextUtils.recycle(buf); + } + + return ret; + } + + /** * Returns the next cursor position in the run. This avoids placing the cursor between * surrogates, between characters that form conjuncts, between base characters and combining * marks, or within a reordering cluster. 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/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index c854fac08626..9cf27181d3ae 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -125,7 +125,7 @@ public class RemoteViews implements Parcelable, Filter { * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! */ private abstract static class Action implements Parcelable { - public abstract void apply(View root) throws ActionException; + public abstract void apply(View root, ViewGroup rootParent) throws ActionException; public int describeContents() { return 0; @@ -183,7 +183,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root) { + public void apply(View root, ViewGroup rootParent) { final View view = root.findViewById(viewId); if (!(view instanceof AdapterView<?>)) return; @@ -214,7 +214,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root) { + public void apply(View root, ViewGroup rootParent) { final View target = root.findViewById(viewId); if (target == null) return; @@ -295,7 +295,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root) { + public void apply(View root, ViewGroup rootParent) { final View target = root.findViewById(viewId); if (target == null) return; @@ -360,6 +360,60 @@ public class RemoteViews implements Parcelable, Filter { public final static int TAG = 8; } + private class SetRemoteViewsAdapterIntent extends Action { + public SetRemoteViewsAdapterIntent(int id, Intent intent) { + this.viewId = id; + this.intent = intent; + } + + public SetRemoteViewsAdapterIntent(Parcel parcel) { + viewId = parcel.readInt(); + intent = Intent.CREATOR.createFromParcel(parcel); + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(TAG); + dest.writeInt(viewId); + intent.writeToParcel(dest, flags); + } + + @Override + public void apply(View root, ViewGroup rootParent) { + final View target = root.findViewById(viewId); + if (target == null) return; + + // Ensure that we are applying to an AppWidget root + if (!(rootParent instanceof AppWidgetHostView)) { + Log.e("RemoteViews", "SetRemoteViewsAdapterIntent action can only be used for " + + "AppWidgets (root id: " + viewId + ")"); + return; + } + // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it + if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { + Log.e("RemoteViews", "Cannot setRemoteViewsAdapter on a view which is not " + + "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); + return; + } + + // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent + // RemoteViewsService + AppWidgetHostView host = (AppWidgetHostView) rootParent; + intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId()); + if (target instanceof AbsListView) { + AbsListView v = (AbsListView) target; + v.setRemoteViewsAdapter(intent); + } else if (target instanceof AdapterViewAnimator) { + AdapterViewAnimator v = (AdapterViewAnimator) target; + v.setRemoteViewsAdapter(intent); + } + } + + int viewId; + Intent intent; + + public final static int TAG = 10; + } + /** * Equivalent to calling * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} @@ -383,7 +437,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root) { + public void apply(View root, ViewGroup rootParent) { final View target = root.findViewById(viewId); if (target == null) return; @@ -479,7 +533,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root) { + public void apply(View root, ViewGroup rootParent) { final View target = root.findViewById(viewId); if (target == null) return; @@ -539,7 +593,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root) { + public void apply(View root, ViewGroup rootParent) { final View view = root.findViewById(viewId); if (view == null) return; @@ -755,7 +809,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root) { + public void apply(View root, ViewGroup rootParent) { final View view = root.findViewById(viewId); if (view == null) return; @@ -850,7 +904,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root) { + public void apply(View root, ViewGroup rootParent) { final Context context = root.getContext(); final ViewGroup target = (ViewGroup) root.findViewById(viewId); if (target == null) return; @@ -952,6 +1006,9 @@ public class RemoteViews implements Parcelable, Filter { case SetOnClickFillInIntent.TAG: mActions.add(new SetOnClickFillInIntent(parcel)); break; + case SetRemoteViewsAdapterIntent.TAG: + mActions.add(new SetRemoteViewsAdapterIntent(parcel)); + break; default: throw new ActionException("Tag " + tag + " not found"); } @@ -1287,16 +1344,29 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. * - * @param appWidgetId The id of the app widget which contains the specified view + * @param appWidgetId The id of the app widget which contains the specified view. (This + * parameter is ignored in this deprecated method) * @param viewId The id of the view whose text should change * @param intent The intent of the service which will be * providing data to the RemoteViewsAdapter + * @deprecated This method has been deprecated. See + * {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)} */ + @Deprecated public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) { - // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent - // RemoteViewsService - intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, appWidgetId); - setIntent(viewId, "setRemoteViewsAdapter", intent); + setRemoteAdapter(viewId, intent); + } + + /** + * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. + * Can only be used for App Widgets. + * + * @param viewId The id of the view whose text should change + * @param intent The intent of the service which will be + * providing data to the RemoteViewsAdapter + */ + public void setRemoteAdapter(int viewId, Intent intent) { + addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); } /** @@ -1499,7 +1569,7 @@ public class RemoteViews implements Parcelable, Filter { result = inflater.inflate(mLayoutId, parent, false); - performApply(result); + performApply(result, parent); return result; } @@ -1514,15 +1584,15 @@ public class RemoteViews implements Parcelable, Filter { */ public void reapply(Context context, View v) { prepareContext(context); - performApply(v); + performApply(v, (ViewGroup) v.getParent()); } - private void performApply(View v) { + private void performApply(View v, ViewGroup parent) { if (mActions != null) { final int count = mActions.size(); for (int i = 0; i < count; i++) { Action a = mActions.get(i); - a.apply(v); + a.apply(v, parent); } } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index baf20a1eb9f5..9e482b46e3a7 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -2951,6 +2951,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener advancesIndex); } + public float getTextRunAdvancesICU(int start, int end, int contextStart, + int contextEnd, int flags, float[] advances, int advancesIndex, + Paint p) { + int count = end - start; + int contextCount = contextEnd - contextStart; + return p.getTextRunAdvancesICU(mChars, start + mStart, count, + contextStart + mStart, contextCount, flags, advances, + advancesIndex); + } + public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset, int cursorOpt, Paint p) { int contextCount = contextEnd - contextStart; diff --git a/core/jni/android/graphics/HarfbuzzSkia.cpp b/core/jni/android/graphics/HarfbuzzSkia.cpp index 58fb32b47319..92c743fe482f 100644 --- a/core/jni/android/graphics/HarfbuzzSkia.cpp +++ b/core/jni/android/graphics/HarfbuzzSkia.cpp @@ -34,6 +34,8 @@ #include "SkRect.h" #include "SkTypeface.h" +#include <utils/Log.h> + extern "C" { #include "harfbuzz-shaper.h" } @@ -43,20 +45,13 @@ extern "C" { namespace android { -static HB_Fixed SkiaScalarToHarfbuzzFixed(SkScalar value) -{ - // HB_Fixed is a 26.6 fixed point format. - return value * 64; -} - static void setupPaintWithFontData(SkPaint* paint, FontData* data) { - paint->setAntiAlias(true); - paint->setSubpixelText(true); - paint->setHinting(SkPaint::kSlight_Hinting); - paint->setTextSize(SkFloatToScalar(data->textSize)); paint->setTypeface(data->typeFace); - paint->setFakeBoldText(data->fakeBold); - paint->setTextSkewX(data->fakeItalic ? -SK_Scalar1/4 : 0); + paint->setTextSize(data->textSize); + paint->setTextSkewX(data->textSkewX); + paint->setTextScaleX(data->textScaleX); + paint->setFlags(data->flags); + paint->setHinting(data->hinting); } static HB_Bool stringToGlyphs(HB_Font hbFont, const HB_UChar16* characters, hb_uint32 length, @@ -67,16 +62,13 @@ static HB_Bool stringToGlyphs(HB_Font hbFont, const HB_UChar16* characters, hb_u setupPaintWithFontData(&paint, data); paint.setTextEncoding(SkPaint::kUTF16_TextEncoding); - int numGlyphs = paint.textToGlyphs(characters, length * sizeof(uint16_t), - reinterpret_cast<uint16_t*>(glyphs)); + uint16_t* skiaGlyphs = reinterpret_cast<uint16_t*>(glyphs); + int numGlyphs = paint.textToGlyphs(characters, length * sizeof(uint16_t), skiaGlyphs); // HB_Glyph is 32-bit, but Skia outputs only 16-bit numbers. So our // |glyphs| array needs to be converted. for (int i = numGlyphs - 1; i >= 0; --i) { - uint16_t value; - // We use a memcpy to avoid breaking strict aliasing rules. - memcpy(&value, reinterpret_cast<char*>(glyphs) + sizeof(uint16_t) * i, sizeof(value)); - glyphs[i] = value; + glyphs[i] = skiaGlyphs[i]; } *glyphsSize = numGlyphs; @@ -97,16 +89,17 @@ static void glyphsToAdvances(HB_Font hbFont, const HB_Glyph* glyphs, hb_uint32 n return; for (unsigned i = 0; i < numGlyphs; ++i) glyphs16[i] = glyphs[i]; - paint.getTextWidths(glyphs16, numGlyphs * sizeof(uint16_t), reinterpret_cast<SkScalar*>(advances)); + SkScalar* scalarAdvances = reinterpret_cast<SkScalar*>(advances); + paint.getTextWidths(glyphs16, numGlyphs * sizeof(uint16_t), scalarAdvances); // The |advances| values which Skia outputs are SkScalars, which are floats // in Chromium. However, Harfbuzz wants them in 26.6 fixed point format. // These two formats are both 32-bits long. for (unsigned i = 0; i < numGlyphs; ++i) { - float value; - // We use a memcpy to avoid breaking strict aliasing rules. - memcpy(&value, reinterpret_cast<char*>(advances) + sizeof(float) * i, sizeof(value)); - advances[i] = SkiaScalarToHarfbuzzFixed(value); + advances[i] = SkScalarToHBFixed(scalarAdvances[i]); +#if DEBUG_ADVANCES + LOGD("glyphsToAdvances -- advances[%d]=%d", i, advances[i]); +#endif } delete glyphs16; } @@ -156,8 +149,8 @@ static HB_Error getOutlinePoint(HB_Font hbFont, HB_Glyph glyph, int flags, hb_ui return HB_Err_Invalid_SubTable; // Skia does let us get a single point from the path. path.getPoints(points, point + 1); - *xPos = SkiaScalarToHarfbuzzFixed(points[point].fX); - *yPos = SkiaScalarToHarfbuzzFixed(points[point].fY); + *xPos = SkScalarToHBFixed(points[point].fX); + *yPos = SkScalarToHBFixed(points[point].fY); *resultingNumPoints = numPoints; delete points; @@ -176,12 +169,12 @@ static void getGlyphMetrics(HB_Font hbFont, HB_Glyph glyph, HB_GlyphMetrics* met SkRect bounds; paint.getTextWidths(&glyph16, sizeof(glyph16), &width, &bounds); - metrics->x = SkiaScalarToHarfbuzzFixed(bounds.fLeft); - metrics->y = SkiaScalarToHarfbuzzFixed(bounds.fTop); - metrics->width = SkiaScalarToHarfbuzzFixed(bounds.width()); - metrics->height = SkiaScalarToHarfbuzzFixed(bounds.height()); + metrics->x = SkScalarToHBFixed(bounds.fLeft); + metrics->y = SkScalarToHBFixed(bounds.fTop); + metrics->width = SkScalarToHBFixed(bounds.width()); + metrics->height = SkScalarToHBFixed(bounds.height()); - metrics->xOffset = SkiaScalarToHarfbuzzFixed(width); + metrics->xOffset = SkScalarToHBFixed(width); // We can't actually get the |y| correct because Skia doesn't export // the vertical advance. However, nor we do ever render vertical text at // the moment so it's unimportant. @@ -199,7 +192,7 @@ static HB_Fixed getFontMetric(HB_Font hbFont, HB_FontMetric metric) switch (metric) { case HB_FontAscent: - return SkiaScalarToHarfbuzzFixed(-skiaMetrics.fAscent); + return SkScalarToHBFixed(-skiaMetrics.fAscent); // We don't support getting the rest of the metrics and Harfbuzz doesn't seem to need them. default: return 0; diff --git a/core/jni/android/graphics/HarfbuzzSkia.h b/core/jni/android/graphics/HarfbuzzSkia.h index d057d76f2842..99b389a7ad4a 100644 --- a/core/jni/android/graphics/HarfbuzzSkia.h +++ b/core/jni/android/graphics/HarfbuzzSkia.h @@ -27,22 +27,38 @@ #ifndef HarfbuzzSkia_h #define HarfbuzzSkia_h +#include "SkScalar.h" #include "SkTypeface.h" +#include "SkPaint.h" extern "C" { #include "harfbuzz-shaper.h" } namespace android { - typedef struct { - SkTypeface* typeFace; - float textSize; - bool fakeBold; - bool fakeItalic; - } FontData; - - HB_Error harfbuzzSkiaGetTable(void* voidface, const HB_Tag, HB_Byte* buffer, HB_UInt* len); - extern const HB_FontClass harfbuzzSkiaClass; + +static inline float HBFixedToFloat(HB_Fixed v) { + // Harfbuzz uses 26.6 fixed point values for pixel offsets + return v * (1.0f / 64); +} + +static inline HB_Fixed SkScalarToHBFixed(SkScalar value) { + // HB_Fixed is a 26.6 fixed point format. + return SkScalarToFloat(value) * 64.0f; +} + +typedef struct { + SkTypeface* typeFace; + SkScalar textSize; + SkScalar textSkewX; + SkScalar textScaleX; + uint32_t flags; + SkPaint::Hinting hinting; +} FontData; + +HB_Error harfbuzzSkiaGetTable(void* voidface, const HB_Tag, HB_Byte* buffer, HB_UInt* len); +extern const HB_FontClass harfbuzzSkiaClass; + } // namespace android #endif diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 5c3497fdf4ea..27be87182490 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -414,6 +414,7 @@ public: for (int i = 0; i < glyphCount; i++) { glyphsArray[i] = (jchar) shaperItem.glyphs[i]; } + env->ReleaseCharArrayElements(glyphs, glyphsArray, JNI_ABORT); return glyphCount; } @@ -442,6 +443,21 @@ public: return totalAdvance; } + static jfloat doTextRunAdvancesICU(JNIEnv *env, SkPaint *paint, const jchar *text, + jint start, jint count, jint contextCount, jint flags, + jfloatArray advances, jint advancesIndex) { + jfloat advancesArray[count]; + jfloat totalAdvance; + + TextLayout::getTextRunAdvancesICU(paint, text, start, count, contextCount, flags, + advancesArray, totalAdvance); + + if (advances != NULL) { + env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray); + } + return totalAdvance; + } + static float getTextRunAdvances___CIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text, jint index, jint count, jint contextIndex, jint contextCount, jint flags, jfloatArray advances, jint advancesIndex) { @@ -463,6 +479,27 @@ public: return result; } + static float getTextRunAdvancesICU___CIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint, + jcharArray text, jint index, jint count, jint contextIndex, jint contextCount, + jint flags, jfloatArray advances, jint advancesIndex) { + jchar* textArray = env->GetCharArrayElements(text, NULL); + jfloat result = doTextRunAdvancesICU(env, paint, textArray + contextIndex, + index - contextIndex, count, contextCount, flags, advances, advancesIndex); + env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); + return result; + } + + static float getTextRunAdvancesICU__StringIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint, + jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags, + jfloatArray advances, jint advancesIndex) { + const jchar* textArray = env->GetStringChars(text, NULL); + jfloat result = doTextRunAdvancesICU(env, paint, textArray + contextStart, + start - contextStart, end - start, contextEnd - contextStart, flags, advances, + advancesIndex); + env->ReleaseStringChars(text, textArray); + return result; + } + static jint doTextRunCursor(JNIEnv *env, SkPaint* paint, const jchar *text, jint start, jint count, jint flags, jint offset, jint opt) { SkScalar scalarArray[count]; @@ -748,10 +785,14 @@ static JNINativeMethod methods[] = { {"native_breakText","(Ljava/lang/String;ZF[F)I", (void*) SkPaintGlue::breakTextS}, {"native_getTextWidths","(I[CII[F)I", (void*) SkPaintGlue::getTextWidths___CII_F}, {"native_getTextWidths","(ILjava/lang/String;II[F)I", (void*) SkPaintGlue::getTextWidths__StringII_F}, - {"native_getTextRunAdvances","(I[CIIIII[FI)F", (void*) - SkPaintGlue::getTextRunAdvances___CIIIII_FI}, + {"native_getTextRunAdvances","(I[CIIIII[FI)F", + (void*) SkPaintGlue::getTextRunAdvances___CIIIII_FI}, {"native_getTextRunAdvances","(ILjava/lang/String;IIIII[FI)F", (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FI}, + {"native_getTextRunAdvancesICU","(I[CIIIII[FI)F", + (void*) SkPaintGlue::getTextRunAdvancesICU___CIIIII_FI}, + {"native_getTextRunAdvancesICU","(ILjava/lang/String;IIIII[FI)F", + (void*) SkPaintGlue::getTextRunAdvancesICU__StringIIIII_FI}, {"native_getTextGlyphs","(ILjava/lang/String;IIIII[C)I", (void*) SkPaintGlue::getTextGlyphs__StringIIIII_C}, {"native_getTextRunCursor", "(I[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C}, diff --git a/core/jni/android/graphics/RtlProperties.h b/core/jni/android/graphics/RtlProperties.h index 2c68fa397f13..f41f4a1bef74 100644 --- a/core/jni/android/graphics/RtlProperties.h +++ b/core/jni/android/graphics/RtlProperties.h @@ -45,7 +45,12 @@ static RtlDebugLevel readRtlDebugLevel() { return kRtlDebugDisabled; } +// Define if we want to use Harfbuzz (1) or not (0) #define RTL_USE_HARFBUZZ 1 +// Define if we want (1) to have Advances debug values or not (0) +#define DEBUG_ADVANCES 0 + + } // namespace android #endif // ANDROID_RTL_PROPERTIES_H diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp index f1bb6963fbb0..434f63b8ed7b 100644 --- a/core/jni/android/graphics/TextLayout.cpp +++ b/core/jni/android/graphics/TextLayout.cpp @@ -269,6 +269,21 @@ void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint sta #endif } +void TextLayout::getTextRunAdvancesHB(SkPaint* paint, const jchar* chars, jint start, + jint count, jint contextCount, jint dirFlags, + jfloat* resultAdvances, jfloat& resultTotalAdvance) { + // Compute advances and return them + RunAdvanceDescription::computeAdvancesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags, + resultAdvances, &resultTotalAdvance); +} + +void TextLayout::getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start, + jint count, jint contextCount, jint dirFlags, + jfloat* resultAdvances, jfloat& resultTotalAdvance) { + // Compute advances and return them + RunAdvanceDescription::computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags, + resultAdvances, &resultTotalAdvance); +} // Draws a paragraph of text on a single line, running bidi and shaping void TextLayout::drawText(SkPaint* paint, const jchar* text, jsize len, diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h index a950d13e15ce..138983c19ccd 100644 --- a/core/jni/android/graphics/TextLayout.h +++ b/core/jni/android/graphics/TextLayout.h @@ -73,6 +73,14 @@ public: jint count, jint contextCount, jint dirFlags, jfloat* resultAdvances, jfloat& resultTotalAdvance); + static void getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start, + jint count, jint contextCount, jint dirFlags, + jfloat* resultAdvances, jfloat& resultTotalAdvance); + + static void getTextRunAdvancesHB(SkPaint* paint, const jchar* chars, jint start, + jint count, jint contextCount, jint dirFlags, + jfloat* resultAdvances, jfloat& resultTotalAdvance); + static void drawText(SkPaint* paint, const jchar* text, jsize len, jint bidiFlags, jfloat x, jfloat y, SkCanvas* canvas); diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h index 925bb7c72668..31d802cdb1c6 100644 --- a/core/jni/android/graphics/TextLayoutCache.h +++ b/core/jni/android/graphics/TextLayoutCache.h @@ -19,17 +19,20 @@ #include "RtlProperties.h" -#include "stddef.h" +#include <stddef.h> #include <utils/threads.h> #include <utils/String16.h> -#include "utils/GenerationCache.h" -#include "utils/Compare.h" +#include <utils/GenerationCache.h> +#include <utils/Compare.h> -#include "SkPaint.h" -#include "SkTemplates.h" +#include <SkPaint.h> +#include <SkTemplates.h> +#include <SkUtils.h> +#include <SkScalerContext.h> +#include <SkAutoKern.h> -#include "unicode/ubidi.h" -#include "unicode/ushape.h" +#include <unicode/ubidi.h> +#include <unicode/ushape.h> #include "HarfbuzzSkia.h" #include "harfbuzz-shaper.h" @@ -54,14 +57,8 @@ // Define the interval in number of cache hits between two statistics dump #define DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL 100 -// Define if we want to have Advances debug values -#define DEBUG_ADVANCES 0 - namespace android { -// Harfbuzz uses 26.6 fixed point values for pixel offsets -#define HB_FIXED_TO_FLOAT(v) (((float) v) * (1.0 / 64)) - /** * TextLayoutCacheKey is the Cache key */ @@ -202,6 +199,8 @@ public: shaperItem->font = font; shaperItem->face = HB_NewFace(shaperItem->font, harfbuzzSkiaGetTable); + shaperItem->kerning_applied = false; + // We cannot know, ahead of time, how many glyphs a given script run // will produce. We take a guess that script runs will not produce more // than twice as many glyphs as there are code points plus a bit of @@ -222,10 +221,12 @@ public: shaperItem->string = chars; shaperItem->stringLength = contextCount; - fontData->textSize = paint->getTextSize(); - fontData->fakeBold = paint->isFakeBoldText(); - fontData->fakeItalic = (paint->getTextSkewX() > 0); fontData->typeFace = paint->getTypeface(); + fontData->textSize = paint->getTextSize(); + fontData->textSkewX = paint->getTextSkewX(); + fontData->textScaleX = paint->getTextScaleX(); + fontData->flags = paint->getFlags(); + fontData->hinting = paint->getHinting(); shaperItem->font->userData = fontData; } @@ -248,6 +249,21 @@ public: } } +#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16) + + static int adjust(int prev, int next) { + int delta = next - prev; + if (delta >= 32) { + return -1; + } + else if (delta < -32) { + return +1; + } + else { + return 0; + } + } + static void computeAdvancesWithHarfbuzz(SkPaint* paint, const UChar* chars, size_t start, size_t count, size_t contextCount, int dirFlags, jfloat* outAdvances, jfloat* outTotalAdvance) { @@ -261,13 +277,15 @@ public: contextCount, dirFlags); #if DEBUG_ADVANCES - LOGD("HARFBUZZ -- num_glypth=%d", shaperItem.num_glyphs); + LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs, shaperItem.kerning_applied); + LOGD(" -- string= '%s'", String8(chars, contextCount).string()); + LOGD(" -- isDevKernText=%d", paint->isDevKernText()); #endif jfloat totalAdvance = 0; + for (size_t i = 0; i < count; i++) { - // Be careful: we need to use ceilf() for doing the same way as what Skia is doing - totalAdvance += outAdvances[i] = ceilf(HB_FIXED_TO_FLOAT(shaperItem.advances[i])); + totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[i]); #if DEBUG_ADVANCES LOGD("hb-adv = %d - rebased = %f - total = %f", shaperItem.advances[i], outAdvances[i], 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/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index cca7d8b3ad3f..968d99c6bbef 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -34,6 +34,8 @@ <dimen name="status_bar_height">25dip</dimen> <!-- Height of the status bar --> <dimen name="status_bar_icon_size">25dip</dimen> + <!-- Size of the giant number (unread count) in the notifications --> + <dimen name="status_bar_content_number_size">48sp</dimen> <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. --> <dimen name="status_bar_edge_ignore">5dp</dimen> <!-- Margin for permanent screen decorations at the bottom. --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 0fefbf24b7ca..4109ae1af64b 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1657,4 +1657,6 @@ <public type="attr" name="state_drag_can_accept" /> <public type="attr" name="state_drag_hovered" /> + <public type="style" name="Theme.Holo.Light.NoActionBar" /> + </resources> 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/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 96eb93662d06..0949beb812e1 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -1534,6 +1534,48 @@ public class Paint { } /** + * Convenience overload that takes a char array instead of a + * String. + * + * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int) + * @hide + */ + public float getTextRunAdvancesICU(char[] chars, int index, int count, + int contextIndex, int contextCount, int flags, float[] advances, + int advancesIndex) { + + if ((index | count | contextIndex | contextCount | advancesIndex + | (index - contextIndex) + | ((contextIndex + contextCount) - (index + count)) + | (chars.length - (contextIndex + contextCount)) + | (advances == null ? 0 : + (advances.length - (advancesIndex + count)))) < 0) { + throw new IndexOutOfBoundsException(); + } + if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) { + throw new IllegalArgumentException("unknown flags value: " + flags); + } + + if (!mHasCompatScaling) { + return native_getTextRunAdvancesICU(mNativePaint, chars, index, count, + contextIndex, contextCount, flags, advances, advancesIndex); + } + + final float oldSize = getTextSize(); + setTextSize(oldSize * mCompatScaling); + float res = native_getTextRunAdvancesICU(mNativePaint, chars, index, count, + contextIndex, contextCount, flags, advances, advancesIndex); + setTextSize(oldSize); + + if (advances != null) { + for (int i = advancesIndex, e = i + count; i < e; i++) { + advances[i] *= mInvCompatScaling; + } + } + return res * mInvCompatScaling; // assume errors are not significant + } + + /** * Convenience overload that takes a CharSequence instead of a * String. * @@ -1569,6 +1611,41 @@ public class Paint { } /** + * Convenience overload that takes a CharSequence instead of a + * String. + * + * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int) + * @hide + */ + public float getTextRunAdvancesICU(CharSequence text, int start, int end, + int contextStart, int contextEnd, int flags, float[] advances, + int advancesIndex) { + + if (text instanceof String) { + return getTextRunAdvancesICU((String) text, start, end, + contextStart, contextEnd, flags, advances, advancesIndex); + } + if (text instanceof SpannedString || + text instanceof SpannableString) { + return getTextRunAdvancesICU(text.toString(), start, end, + contextStart, contextEnd, flags, advances, advancesIndex); + } + if (text instanceof GraphicsOperations) { + return ((GraphicsOperations) text).getTextRunAdvancesICU(start, end, + contextStart, contextEnd, flags, advances, advancesIndex, this); + } + + int contextLen = contextEnd - contextStart; + int len = end - start; + char[] buf = TemporaryBuffer.obtain(contextLen); + TextUtils.getChars(text, start, end, buf, 0); + float result = getTextRunAdvancesICU(buf, start - contextStart, len, + 0, contextLen, flags, advances, advancesIndex); + TemporaryBuffer.recycle(buf); + return result; + } + + /** * Returns the total advance width for the characters in the run * between start and end, and if advances is not null, the advance * assigned to each of these characters (java chars). @@ -1644,6 +1721,44 @@ public class Paint { } /** + * Temporary - DO NOT USE + * + * @hide + */ + public float getTextRunAdvancesICU(String text, int start, int end, int contextStart, + int contextEnd, int flags, float[] advances, int advancesIndex) { + + if ((start | end | contextStart | contextEnd | advancesIndex | (end - start) + | (start - contextStart) | (contextEnd - end) + | (text.length() - contextEnd) + | (advances == null ? 0 : + (advances.length - advancesIndex - (end - start)))) < 0) { + throw new IndexOutOfBoundsException(); + } + if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) { + throw new IllegalArgumentException("unknown flags value: " + flags); + } + + if (!mHasCompatScaling) { + return native_getTextRunAdvancesICU(mNativePaint, text, start, end, + contextStart, contextEnd, flags, advances, advancesIndex); + } + + final float oldSize = getTextSize(); + setTextSize(oldSize * mCompatScaling); + float totalAdvance = native_getTextRunAdvances(mNativePaint, text, start, end, + contextStart, contextEnd, flags, advances, advancesIndex); + setTextSize(oldSize); + + if (advances != null) { + for (int i = advancesIndex, e = i + (end - start); i < e; i++) { + advances[i] *= mInvCompatScaling; + } + } + return totalAdvance * mInvCompatScaling; // assume errors are insignificant + } + + /** * Returns the next cursor position in the run. This avoids placing the * cursor between surrogates, between characters that form conjuncts, * between base characters and combining marks, or within a reordering @@ -1907,6 +2022,13 @@ public class Paint { String text, int start, int end, int contextStart, int contextEnd, int flags, float[] advances, int advancesIndex); + private static native float native_getTextRunAdvancesICU(int native_object, + char[] text, int index, int count, int contextIndex, int contextCount, + int flags, float[] advances, int advancesIndex); + private static native float native_getTextRunAdvancesICU(int native_object, + String text, int start, int end, int contextStart, int contextEnd, + int flags, float[] advances, int advancesIndex); + private native int native_getTextRunCursor(int native_object, char[] text, int contextStart, int contextLength, int flags, int offset, int cursorOpt); private native int native_getTextRunCursor(int native_object, String text, diff --git a/include/media/stagefright/DataSource.h b/include/media/stagefright/DataSource.h index d30e908bc15d..6b6fcdfd1cee 100644 --- a/include/media/stagefright/DataSource.h +++ b/include/media/stagefright/DataSource.h @@ -84,6 +84,8 @@ public: return String8(); } + virtual String8 getMIMEType() const; + protected: virtual ~DataSource() {} diff --git a/include/media/stagefright/MediaDefs.h b/include/media/stagefright/MediaDefs.h index 66dfff69f266..6a21627c5477 100644 --- a/include/media/stagefright/MediaDefs.h +++ b/include/media/stagefright/MediaDefs.h @@ -45,6 +45,7 @@ extern const char *MEDIA_MIMETYPE_CONTAINER_WAV; extern const char *MEDIA_MIMETYPE_CONTAINER_OGG; extern const char *MEDIA_MIMETYPE_CONTAINER_MATROSKA; extern const char *MEDIA_MIMETYPE_CONTAINER_MPEG2TS; +extern const char *MEDIA_MIMETYPE_CONTAINER_AVI; extern const char *MEDIA_MIMETYPE_CONTAINER_WVM; 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/MediaFile.java b/media/java/android/media/MediaFile.java index c41e0ad0b1d9..352579a51e55 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -66,8 +66,9 @@ public class MediaFile { public static final int FILE_TYPE_ASF = 26; public static final int FILE_TYPE_MKV = 27; public static final int FILE_TYPE_MP2TS = 28; + public static final int FILE_TYPE_AVI = 29; private static final int FIRST_VIDEO_FILE_TYPE = FILE_TYPE_MP4; - private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_MP2TS; + private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_AVI; // Image file types public static final int FILE_TYPE_JPEG = 31; @@ -198,6 +199,7 @@ public class MediaFile { addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska"); addFileType("WEBM", FILE_TYPE_MKV, "video/x-matroska"); addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts"); + addFileType("AVI", FILE_TYPE_AVI, "video/avi"); if (isWMVEnabled()) { addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv", MtpConstants.FORMAT_WMV); 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/AVIExtractor.cpp b/media/libstagefright/AVIExtractor.cpp new file mode 100644 index 000000000000..6313ca38a93d --- /dev/null +++ b/media/libstagefright/AVIExtractor.cpp @@ -0,0 +1,922 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "AVIExtractor" +#include <utils/Log.h> + +#include "include/AVIExtractor.h" + +#include <binder/ProcessState.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +namespace android { + +struct AVIExtractor::AVISource : public MediaSource { + AVISource(const sp<AVIExtractor> &extractor, size_t trackIndex); + + virtual status_t start(MetaData *params); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options); + +protected: + virtual ~AVISource(); + +private: + sp<AVIExtractor> mExtractor; + size_t mTrackIndex; + const AVIExtractor::Track &mTrack; + MediaBufferGroup *mBufferGroup; + size_t mSampleIndex; + + DISALLOW_EVIL_CONSTRUCTORS(AVISource); +}; + +//////////////////////////////////////////////////////////////////////////////// + +AVIExtractor::AVISource::AVISource( + const sp<AVIExtractor> &extractor, size_t trackIndex) + : mExtractor(extractor), + mTrackIndex(trackIndex), + mTrack(mExtractor->mTracks.itemAt(trackIndex)), + mBufferGroup(NULL) { +} + +AVIExtractor::AVISource::~AVISource() { + if (mBufferGroup) { + stop(); + } +} + +status_t AVIExtractor::AVISource::start(MetaData *params) { + CHECK(!mBufferGroup); + + mBufferGroup = new MediaBufferGroup; + + mBufferGroup->add_buffer(new MediaBuffer(mTrack.mMaxSampleSize)); + mBufferGroup->add_buffer(new MediaBuffer(mTrack.mMaxSampleSize)); + mSampleIndex = 0; + + return OK; +} + +status_t AVIExtractor::AVISource::stop() { + CHECK(mBufferGroup); + + delete mBufferGroup; + mBufferGroup = NULL; + + return OK; +} + +sp<MetaData> AVIExtractor::AVISource::getFormat() { + return mTrack.mMeta; +} + +status_t AVIExtractor::AVISource::read( + MediaBuffer **buffer, const ReadOptions *options) { + CHECK(mBufferGroup); + + *buffer = NULL; + + int64_t seekTimeUs; + ReadOptions::SeekMode seekMode; + if (options && options->getSeekTo(&seekTimeUs, &seekMode)) { + status_t err = + mExtractor->getSampleIndexAtTime( + mTrackIndex, seekTimeUs, seekMode, &mSampleIndex); + + if (err != OK) { + return ERROR_END_OF_STREAM; + } + } + + int64_t timeUs = + (mSampleIndex * 1000000ll * mTrack.mRate) / mTrack.mScale; + + off64_t offset; + size_t size; + bool isKey; + status_t err = mExtractor->getSampleInfo( + mTrackIndex, mSampleIndex, &offset, &size, &isKey); + + ++mSampleIndex; + + if (err != OK) { + return ERROR_END_OF_STREAM; + } + + MediaBuffer *out; + CHECK_EQ(mBufferGroup->acquire_buffer(&out), (status_t)OK); + + ssize_t n = mExtractor->mDataSource->readAt(offset, out->data(), size); + + if (n < (ssize_t)size) { + return n < 0 ? (status_t)n : (status_t)ERROR_MALFORMED; + } + + out->set_range(0, size); + + out->meta_data()->setInt64(kKeyTime, timeUs); + + if (isKey) { + out->meta_data()->setInt32(kKeyIsSyncFrame, 1); + } + + *buffer = out; + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +AVIExtractor::AVIExtractor(const sp<DataSource> &dataSource) + : mDataSource(dataSource) { + mInitCheck = parseHeaders(); + + if (mInitCheck != OK) { + mTracks.clear(); + } +} + +AVIExtractor::~AVIExtractor() { +} + +size_t AVIExtractor::countTracks() { + return mTracks.size(); +} + +sp<MediaSource> AVIExtractor::getTrack(size_t index) { + return index < mTracks.size() ? new AVISource(this, index) : NULL; +} + +sp<MetaData> AVIExtractor::getTrackMetaData( + size_t index, uint32_t flags) { + return index < mTracks.size() ? mTracks.editItemAt(index).mMeta : NULL; +} + +sp<MetaData> AVIExtractor::getMetaData() { + sp<MetaData> meta = new MetaData; + + if (mInitCheck == OK) { + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_AVI); + } + + return meta; +} + +status_t AVIExtractor::parseHeaders() { + mTracks.clear(); + mMovieOffset = 0; + mFoundIndex = false; + mOffsetsAreAbsolute = false; + + ssize_t res = parseChunk(0ll, -1ll); + + if (res < 0) { + return (status_t)res; + } + + if (mMovieOffset == 0ll || !mFoundIndex) { + return ERROR_MALFORMED; + } + + return OK; +} + +ssize_t AVIExtractor::parseChunk(off64_t offset, off64_t size, int depth) { + if (size >= 0 && size < 8) { + return ERROR_MALFORMED; + } + + uint8_t tmp[12]; + ssize_t n = mDataSource->readAt(offset, tmp, 8); + + if (n < 8) { + return (n < 0) ? n : (ssize_t)ERROR_MALFORMED; + } + + uint32_t fourcc = U32_AT(tmp); + uint32_t chunkSize = U32LE_AT(&tmp[4]); + + if (size >= 0 && chunkSize + 8 > size) { + return ERROR_MALFORMED; + } + + static const char kPrefix[] = " "; + const char *prefix = &kPrefix[strlen(kPrefix) - 2 * depth]; + + if (fourcc == FOURCC('L', 'I', 'S', 'T') + || fourcc == FOURCC('R', 'I', 'F', 'F')) { + // It's a list of chunks + + if (size >= 0 && size < 12) { + return ERROR_MALFORMED; + } + + n = mDataSource->readAt(offset + 8, &tmp[8], 4); + + if (n < 4) { + return (n < 0) ? n : (ssize_t)ERROR_MALFORMED; + } + + uint32_t subFourcc = U32_AT(&tmp[8]); + + LOGV("%s offset 0x%08llx LIST of '%c%c%c%c', size %d", + prefix, + offset, + (char)(subFourcc >> 24), + (char)((subFourcc >> 16) & 0xff), + (char)((subFourcc >> 8) & 0xff), + (char)(subFourcc & 0xff), + chunkSize - 4); + + if (subFourcc == FOURCC('m', 'o', 'v', 'i')) { + // We're not going to parse this, but will take note of the + // offset. + + mMovieOffset = offset; + } else { + off64_t subOffset = offset + 12; + off64_t subOffsetLimit = subOffset + chunkSize - 4; + while (subOffset < subOffsetLimit) { + ssize_t res = + parseChunk(subOffset, subOffsetLimit - subOffset, depth + 1); + + if (res < 0) { + return res; + } + + subOffset += res; + } + } + } else { + LOGV("%s offset 0x%08llx CHUNK '%c%c%c%c'", + prefix, + offset, + (char)(fourcc >> 24), + (char)((fourcc >> 16) & 0xff), + (char)((fourcc >> 8) & 0xff), + (char)(fourcc & 0xff)); + + status_t err = OK; + + switch (fourcc) { + case FOURCC('s', 't', 'r', 'h'): + { + err = parseStreamHeader(offset + 8, chunkSize); + break; + } + + case FOURCC('s', 't', 'r', 'f'): + { + err = parseStreamFormat(offset + 8, chunkSize); + break; + } + + case FOURCC('i', 'd', 'x', '1'): + { + err = parseIndex(offset + 8, chunkSize); + break; + } + + default: + break; + } + + if (err != OK) { + return err; + } + } + + if (chunkSize & 1) { + ++chunkSize; + } + + return chunkSize + 8; +} + +static const char *GetMIMETypeForHandler(uint32_t handler) { + switch (handler) { + // Wow... shamelessly copied from + // http://wiki.multimedia.cx/index.php?title=ISO_MPEG-4 + + case FOURCC('3', 'I', 'V', '2'): + case FOURCC('3', 'i', 'v', '2'): + case FOURCC('B', 'L', 'Z', '0'): + case FOURCC('D', 'I', 'G', 'I'): + case FOURCC('D', 'I', 'V', '1'): + case FOURCC('d', 'i', 'v', '1'): + case FOURCC('D', 'I', 'V', 'X'): + case FOURCC('d', 'i', 'v', 'x'): + case FOURCC('D', 'X', '5', '0'): + case FOURCC('d', 'x', '5', '0'): + case FOURCC('D', 'X', 'G', 'M'): + case FOURCC('E', 'M', '4', 'A'): + case FOURCC('E', 'P', 'H', 'V'): + case FOURCC('F', 'M', 'P', '4'): + case FOURCC('f', 'm', 'p', '4'): + case FOURCC('F', 'V', 'F', 'W'): + case FOURCC('H', 'D', 'X', '4'): + case FOURCC('h', 'd', 'x', '4'): + case FOURCC('M', '4', 'C', 'C'): + case FOURCC('M', '4', 'S', '2'): + case FOURCC('m', '4', 's', '2'): + case FOURCC('M', 'P', '4', 'S'): + case FOURCC('m', 'p', '4', 's'): + case FOURCC('M', 'P', '4', 'V'): + case FOURCC('m', 'p', '4', 'v'): + case FOURCC('M', 'V', 'X', 'M'): + case FOURCC('R', 'M', 'P', '4'): + case FOURCC('S', 'E', 'D', 'G'): + case FOURCC('S', 'M', 'P', '4'): + case FOURCC('U', 'M', 'P', '4'): + case FOURCC('W', 'V', '1', 'F'): + case FOURCC('X', 'V', 'I', 'D'): + case FOURCC('X', 'v', 'i', 'D'): + case FOURCC('x', 'v', 'i', 'd'): + case FOURCC('X', 'V', 'I', 'X'): + return MEDIA_MIMETYPE_VIDEO_MPEG4; + + default: + return NULL; + } +} + +status_t AVIExtractor::parseStreamHeader(off64_t offset, size_t size) { + if (size != 56) { + return ERROR_MALFORMED; + } + + if (mTracks.size() > 99) { + return -ERANGE; + } + + sp<ABuffer> buffer = new ABuffer(size); + ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size()); + + if (n < (ssize_t)size) { + return n < 0 ? (status_t)n : ERROR_MALFORMED; + } + + const uint8_t *data = buffer->data(); + + uint32_t type = U32_AT(data); + uint32_t handler = U32_AT(&data[4]); + uint32_t flags = U32LE_AT(&data[8]); + + sp<MetaData> meta = new MetaData; + + uint32_t rate = U32LE_AT(&data[20]); + uint32_t scale = U32LE_AT(&data[24]); + + const char *mime = NULL; + Track::Kind kind = Track::OTHER; + + if (type == FOURCC('v', 'i', 'd', 's')) { + mime = GetMIMETypeForHandler(handler); + + if (mime && strncasecmp(mime, "video/", 6)) { + return ERROR_MALFORMED; + } + + kind = Track::VIDEO; + } else if (type == FOURCC('a', 'u', 'd', 's')) { + if (mime && strncasecmp(mime, "audio/", 6)) { + return ERROR_MALFORMED; + } + + kind = Track::AUDIO; + } + + if (!mime) { + mime = "application/octet-stream"; + } + + meta->setCString(kKeyMIMEType, mime); + + mTracks.push(); + Track *track = &mTracks.editItemAt(mTracks.size() - 1); + + track->mMeta = meta; + track->mRate = rate; + track->mScale = scale; + track->mKind = kind; + track->mNumSyncSamples = 0; + track->mThumbnailSampleSize = 0; + track->mThumbnailSampleIndex = -1; + track->mMaxSampleSize = 0; + + return OK; +} + +status_t AVIExtractor::parseStreamFormat(off64_t offset, size_t size) { + if (mTracks.isEmpty()) { + return ERROR_MALFORMED; + } + + Track *track = &mTracks.editItemAt(mTracks.size() - 1); + + if (track->mKind == Track::OTHER) { + // We don't support this content, but that's not a parsing error. + return OK; + } + + bool isVideo = (track->mKind == Track::VIDEO); + + if ((isVideo && size < 40) || (!isVideo && size < 18)) { + // Expected a BITMAPINFO or WAVEFORMATEX structure, respectively. + return ERROR_MALFORMED; + } + + sp<ABuffer> buffer = new ABuffer(size); + ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size()); + + if (n < (ssize_t)size) { + return n < 0 ? (status_t)n : ERROR_MALFORMED; + } + + const uint8_t *data = buffer->data(); + + if (isVideo) { + uint32_t width = U32LE_AT(&data[4]); + uint32_t height = U32LE_AT(&data[8]); + + track->mMeta->setInt32(kKeyWidth, width); + track->mMeta->setInt32(kKeyHeight, height); + } else { + uint32_t format = U16LE_AT(data); + if (format == 0x55) { + track->mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG); + } + + uint32_t numChannels = U16LE_AT(&data[2]); + uint32_t sampleRate = U32LE_AT(&data[4]); + + track->mMeta->setInt32(kKeyChannelCount, numChannels); + track->mMeta->setInt32(kKeySampleRate, sampleRate); + } + + return OK; +} + +// static +bool AVIExtractor::IsCorrectChunkType( + ssize_t trackIndex, Track::Kind kind, uint32_t chunkType) { + uint32_t chunkBase = chunkType & 0xffff; + + switch (kind) { + case Track::VIDEO: + { + if (chunkBase != FOURCC(0, 0, 'd', 'c') + && chunkBase != FOURCC(0, 0, 'd', 'b')) { + return false; + } + break; + } + + case Track::AUDIO: + { + if (chunkBase != FOURCC(0, 0, 'w', 'b')) { + return false; + } + break; + } + + default: + break; + } + + if (trackIndex < 0) { + return true; + } + + uint8_t hi = chunkType >> 24; + uint8_t lo = (chunkType >> 16) & 0xff; + + if (hi < '0' || hi > '9' || lo < '0' || lo > '9') { + return false; + } + + if (trackIndex != (10 * (hi - '0') + (lo - '0'))) { + return false; + } + + return true; +} + +status_t AVIExtractor::parseIndex(off64_t offset, size_t size) { + if ((size % 16) != 0) { + return ERROR_MALFORMED; + } + + sp<ABuffer> buffer = new ABuffer(size); + ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size()); + + if (n < (ssize_t)size) { + return n < 0 ? (status_t)n : ERROR_MALFORMED; + } + + const uint8_t *data = buffer->data(); + + while (size > 0) { + uint32_t chunkType = U32_AT(data); + + uint8_t hi = chunkType >> 24; + uint8_t lo = (chunkType >> 16) & 0xff; + + if (hi < '0' || hi > '9' || lo < '0' || lo > '9') { + return ERROR_MALFORMED; + } + + size_t trackIndex = 10 * (hi - '0') + (lo - '0'); + + if (trackIndex >= mTracks.size()) { + return ERROR_MALFORMED; + } + + Track *track = &mTracks.editItemAt(trackIndex); + + if (!IsCorrectChunkType(-1, track->mKind, chunkType)) { + return ERROR_MALFORMED; + } + + if (track->mKind == Track::OTHER) { + data += 16; + size -= 16; + continue; + } + + uint32_t flags = U32LE_AT(&data[4]); + uint32_t offset = U32LE_AT(&data[8]); + uint32_t chunkSize = U32LE_AT(&data[12]); + + if (chunkSize > track->mMaxSampleSize) { + track->mMaxSampleSize = chunkSize; + } + + track->mSamples.push(); + + SampleInfo *info = + &track->mSamples.editItemAt(track->mSamples.size() - 1); + + info->mOffset = offset; + info->mIsKey = (flags & 0x10) != 0; + + if (info->mIsKey) { + static const size_t kMaxNumSyncSamplesToScan = 20; + + if (track->mNumSyncSamples < kMaxNumSyncSamplesToScan) { + if (chunkSize > track->mThumbnailSampleSize) { + track->mThumbnailSampleSize = chunkSize; + + track->mThumbnailSampleIndex = + track->mSamples.size() - 1; + } + } + + ++track->mNumSyncSamples; + } + + data += 16; + size -= 16; + } + + if (!mTracks.isEmpty()) { + off64_t offset; + size_t size; + bool isKey; + status_t err = getSampleInfo(0, 0, &offset, &size, &isKey); + + if (err != OK) { + mOffsetsAreAbsolute = !mOffsetsAreAbsolute; + err = getSampleInfo(0, 0, &offset, &size, &isKey); + + if (err != OK) { + return err; + } + } + + LOGV("Chunk offsets are %s", + mOffsetsAreAbsolute ? "absolute" : "movie-chunk relative"); + } + + for (size_t i = 0; i < mTracks.size(); ++i) { + Track *track = &mTracks.editItemAt(i); + + int64_t durationUs = + (track->mSamples.size() * 1000000ll * track->mRate) / track->mScale; + + LOGV("track %d duration = %.2f secs", i, durationUs / 1E6); + + track->mMeta->setInt64(kKeyDuration, durationUs); + track->mMeta->setInt32(kKeyMaxInputSize, track->mMaxSampleSize); + + const char *tmp; + CHECK(track->mMeta->findCString(kKeyMIMEType, &tmp)); + + AString mime = tmp; + + if (!strncasecmp("video/", mime.c_str(), 6) + && track->mThumbnailSampleIndex >= 0) { + int64_t thumbnailTimeUs = + (track->mThumbnailSampleIndex * 1000000ll * track->mRate) + / track->mScale; + + track->mMeta->setInt64(kKeyThumbnailTime, thumbnailTimeUs); + + if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_MPEG4)) { + status_t err = addMPEG4CodecSpecificData(i); + + if (err != OK) { + return err; + } + } + } + } + + mFoundIndex = true; + + return OK; +} + +static size_t GetSizeWidth(size_t x) { + size_t n = 1; + while (x > 127) { + ++n; + x >>= 7; + } + return n; +} + +static uint8_t *EncodeSize(uint8_t *dst, size_t x) { + while (x > 127) { + *dst++ = (x & 0x7f) | 0x80; + x >>= 7; + } + *dst++ = x; + return dst; +} + +sp<ABuffer> MakeMPEG4VideoCodecSpecificData(const sp<ABuffer> &config) { + size_t len1 = config->size() + GetSizeWidth(config->size()) + 1; + size_t len2 = len1 + GetSizeWidth(len1) + 1 + 13; + size_t len3 = len2 + GetSizeWidth(len2) + 1 + 3; + + sp<ABuffer> csd = new ABuffer(len3); + uint8_t *dst = csd->data(); + *dst++ = 0x03; + dst = EncodeSize(dst, len2 + 3); + *dst++ = 0x00; // ES_ID + *dst++ = 0x00; + *dst++ = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag + + *dst++ = 0x04; + dst = EncodeSize(dst, len1 + 13); + *dst++ = 0x01; // Video ISO/IEC 14496-2 Simple Profile + for (size_t i = 0; i < 12; ++i) { + *dst++ = 0x00; + } + + *dst++ = 0x05; + dst = EncodeSize(dst, config->size()); + memcpy(dst, config->data(), config->size()); + dst += config->size(); + + // hexdump(csd->data(), csd->size()); + + return csd; +} + +status_t AVIExtractor::addMPEG4CodecSpecificData(size_t trackIndex) { + Track *track = &mTracks.editItemAt(trackIndex); + + off64_t offset; + size_t size; + bool isKey; + status_t err = getSampleInfo(trackIndex, 0, &offset, &size, &isKey); + + if (err != OK) { + return err; + } + + sp<ABuffer> buffer = new ABuffer(size); + ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size()); + + if (n < (ssize_t)size) { + return n < 0 ? (status_t)n : ERROR_MALFORMED; + } + + // Extract everything up to the first VOP start code from the first + // frame's encoded data and use it to construct an ESDS with the + // codec specific data. + + size_t i = 0; + bool found = false; + while (i + 3 < buffer->size()) { + if (!memcmp("\x00\x00\x01\xb6", &buffer->data()[i], 4)) { + found = true; + break; + } + + ++i; + } + + if (!found) { + return ERROR_MALFORMED; + } + + buffer->setRange(0, i); + + sp<ABuffer> csd = MakeMPEG4VideoCodecSpecificData(buffer); + track->mMeta->setData(kKeyESDS, kTypeESDS, csd->data(), csd->size()); + + return OK; +} + +status_t AVIExtractor::getSampleInfo( + size_t trackIndex, size_t sampleIndex, + off64_t *offset, size_t *size, bool *isKey) { + if (trackIndex >= mTracks.size()) { + return -ERANGE; + } + + const Track &track = mTracks.itemAt(trackIndex); + + if (sampleIndex >= track.mSamples.size()) { + return -ERANGE; + } + + const SampleInfo &info = track.mSamples.itemAt(sampleIndex); + + if (!mOffsetsAreAbsolute) { + *offset = info.mOffset + mMovieOffset + 8; + } else { + *offset = info.mOffset; + } + + *size = 0; + + uint8_t tmp[8]; + ssize_t n = mDataSource->readAt(*offset, tmp, 8); + + if (n < 8) { + return n < 0 ? (status_t)n : (status_t)ERROR_MALFORMED; + } + + uint32_t chunkType = U32_AT(tmp); + + if (!IsCorrectChunkType(trackIndex, track.mKind, chunkType)) { + return ERROR_MALFORMED; + } + + *offset += 8; + *size = U32LE_AT(&tmp[4]); + + *isKey = info.mIsKey; + + return OK; +} + +status_t AVIExtractor::getSampleIndexAtTime( + size_t trackIndex, + int64_t timeUs, MediaSource::ReadOptions::SeekMode mode, + size_t *sampleIndex) const { + if (trackIndex >= mTracks.size()) { + return -ERANGE; + } + + const Track &track = mTracks.itemAt(trackIndex); + + ssize_t closestSampleIndex = + timeUs / track.mRate * track.mScale / 1000000ll; + + ssize_t numSamples = track.mSamples.size(); + + if (closestSampleIndex < 0) { + closestSampleIndex = 0; + } else if (closestSampleIndex >= numSamples) { + closestSampleIndex = numSamples - 1; + } + + if (mode == MediaSource::ReadOptions::SEEK_CLOSEST) { + *sampleIndex = closestSampleIndex; + + return OK; + } + + ssize_t prevSyncSampleIndex = closestSampleIndex; + while (prevSyncSampleIndex >= 0) { + const SampleInfo &info = + track.mSamples.itemAt(prevSyncSampleIndex); + + if (info.mIsKey) { + break; + } + + --prevSyncSampleIndex; + } + + ssize_t nextSyncSampleIndex = closestSampleIndex; + while (nextSyncSampleIndex < numSamples) { + const SampleInfo &info = + track.mSamples.itemAt(nextSyncSampleIndex); + + if (info.mIsKey) { + break; + } + + ++nextSyncSampleIndex; + } + + switch (mode) { + case MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC: + { + *sampleIndex = prevSyncSampleIndex; + + return prevSyncSampleIndex >= 0 ? OK : UNKNOWN_ERROR; + } + + case MediaSource::ReadOptions::SEEK_NEXT_SYNC: + { + *sampleIndex = nextSyncSampleIndex; + + return nextSyncSampleIndex < numSamples ? OK : UNKNOWN_ERROR; + } + + case MediaSource::ReadOptions::SEEK_CLOSEST_SYNC: + { + if (prevSyncSampleIndex < 0 && nextSyncSampleIndex >= numSamples) { + return UNKNOWN_ERROR; + } + + if (prevSyncSampleIndex < 0) { + *sampleIndex = nextSyncSampleIndex; + return OK; + } + + if (nextSyncSampleIndex >= numSamples) { + *sampleIndex = prevSyncSampleIndex; + return OK; + } + + size_t dist1 = closestSampleIndex - prevSyncSampleIndex; + size_t dist2 = nextSyncSampleIndex - closestSampleIndex; + + *sampleIndex = + (dist1 < dist2) ? prevSyncSampleIndex : nextSyncSampleIndex; + + return OK; + } + + default: + TRESPASS(); + break; + } +} + +bool SniffAVI( + const sp<DataSource> &source, String8 *mimeType, float *confidence, + sp<AMessage> *) { + char tmp[12]; + if (source->readAt(0, tmp, 12) < 12) { + return false; + } + + if (!memcmp(tmp, "RIFF", 4) && !memcmp(&tmp[8], "AVI ", 4)) { + mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_AVI); + *confidence = 0.2; + + return true; + } + + return false; +} + +} // namespace android diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index d2e8b4625b4f..2f3e14109eaf 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -8,6 +8,7 @@ LOCAL_SRC_FILES:= \ AACExtractor.cpp \ AMRExtractor.cpp \ AMRWriter.cpp \ + AVIExtractor.cpp \ AudioPlayer.cpp \ AudioSource.cpp \ AwesomePlayer.cpp \ diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 7940de04e75b..759bd869936f 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -1693,29 +1693,37 @@ status_t AwesomePlayer::finishSetDataSource_l() { dataSource = mCachedSource; - // We're going to prefill the cache before trying to instantiate - // the extractor below, as the latter is an operation that otherwise - // could block on the datasource for a significant amount of time. - // During that time we'd be unable to abort the preparation phase - // without this prefill. + String8 contentType = dataSource->getMIMEType(); - mLock.unlock(); + if (strncasecmp(contentType.string(), "audio/", 6)) { + // We're not doing this for streams that appear to be audio-only + // streams to ensure that even low bandwidth streams start + // playing back fairly instantly. - for (;;) { - status_t finalStatus; - size_t cachedDataRemaining = - mCachedSource->approxDataRemaining(&finalStatus); + // We're going to prefill the cache before trying to instantiate + // the extractor below, as the latter is an operation that otherwise + // could block on the datasource for a significant amount of time. + // During that time we'd be unable to abort the preparation phase + // without this prefill. + + mLock.unlock(); + + for (;;) { + status_t finalStatus; + size_t cachedDataRemaining = + mCachedSource->approxDataRemaining(&finalStatus); - if (finalStatus != OK || cachedDataRemaining >= kHighWaterMarkBytes - || (mFlags & PREPARE_CANCELLED)) { - break; + if (finalStatus != OK || cachedDataRemaining >= kHighWaterMarkBytes + || (mFlags & PREPARE_CANCELLED)) { + break; + } + + usleep(200000); } - usleep(200000); + mLock.lock(); } - mLock.lock(); - if (mFlags & PREPARE_CANCELLED) { LOGI("Prepare cancelled while waiting for initial cache fill."); return UNKNOWN_ERROR; diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp index b5c51f442243..c16b3b503219 100644 --- a/media/libstagefright/DataSource.cpp +++ b/media/libstagefright/DataSource.cpp @@ -15,6 +15,7 @@ */ #include "include/AMRExtractor.h" +#include "include/AVIExtractor.h" #include "include/MP3Extractor.h" #include "include/MPEG4Extractor.h" #include "include/WAVExtractor.h" @@ -111,6 +112,7 @@ void DataSource::RegisterDefaultSniffers() { RegisterSniffer(SniffMPEG2TS); RegisterSniffer(SniffMP3); RegisterSniffer(SniffAAC); + RegisterSniffer(SniffAVI); char value[PROPERTY_VALUE_MAX]; if (property_get("drm.service.enabled", value, NULL) @@ -144,4 +146,8 @@ sp<DataSource> DataSource::CreateFromURI( return source; } +String8 DataSource::getMIMEType() const { + return String8("application/octet-stream"); +} + } // namespace android diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp index 0be7261b46c0..8ca6ee888b17 100644 --- a/media/libstagefright/MediaDefs.cpp +++ b/media/libstagefright/MediaDefs.cpp @@ -43,6 +43,7 @@ const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/wav"; const char *MEDIA_MIMETYPE_CONTAINER_OGG = "application/ogg"; const char *MEDIA_MIMETYPE_CONTAINER_MATROSKA = "video/x-matroska"; const char *MEDIA_MIMETYPE_CONTAINER_MPEG2TS = "video/mp2ts"; +const char *MEDIA_MIMETYPE_CONTAINER_AVI = "video/avi"; const char *MEDIA_MIMETYPE_CONTAINER_WVM = "video/wvm"; diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp index 23bad5bb3d68..af0131ea8111 100644 --- a/media/libstagefright/MediaExtractor.cpp +++ b/media/libstagefright/MediaExtractor.cpp @@ -19,6 +19,7 @@ #include <utils/Log.h> #include "include/AMRExtractor.h" +#include "include/AVIExtractor.h" #include "include/MP3Extractor.h" #include "include/MPEG4Extractor.h" #include "include/WAVExtractor.h" @@ -108,6 +109,8 @@ sp<MediaExtractor> MediaExtractor::Create( ret = new MatroskaExtractor(source); } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) { ret = new MPEG2TSExtractor(source); + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_AVI)) { + ret = new AVIExtractor(source); } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WVM)) { ret = new WVMExtractor(source); } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) { diff --git a/media/libstagefright/NuCachedSource2.cpp b/media/libstagefright/NuCachedSource2.cpp index 3c99d1c715a0..c1aa46edb3e6 100644 --- a/media/libstagefright/NuCachedSource2.cpp +++ b/media/libstagefright/NuCachedSource2.cpp @@ -493,4 +493,8 @@ String8 NuCachedSource2::getUri() { return mSource->getUri(); } +String8 NuCachedSource2::getMIMEType() const { + return mSource->getMIMEType(); +} + } // namespace android diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp index 73daf12959c0..821ba9b9aa33 100644 --- a/media/libstagefright/NuHTTPDataSource.cpp +++ b/media/libstagefright/NuHTTPDataSource.cpp @@ -136,6 +136,7 @@ status_t NuHTTPDataSource::connect( unsigned port; mUri = uri; + mContentType = String8("application/octet-stream"); bool https; if (!ParseURL(uri, &host, &port, &path, &https)) { @@ -265,6 +266,15 @@ status_t NuHTTPDataSource::connect( } } + { + AString value; + if (mHTTP.find_header_value("Content-Type", &value)) { + mContentType = String8(value.c_str()); + } else { + mContentType = String8("application/octet-stream"); + } + } + applyTimeoutResponse(); if (offset == 0) { @@ -410,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; @@ -564,4 +581,8 @@ String8 NuHTTPDataSource::getUri() { return mUri; } +String8 NuHTTPDataSource::getMIMEType() const { + return mContentType; +} + } // namespace android 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/media/libstagefright/StagefrightMediaScanner.cpp b/media/libstagefright/StagefrightMediaScanner.cpp index 8b86e53085a5..f82ff32d083d 100644 --- a/media/libstagefright/StagefrightMediaScanner.cpp +++ b/media/libstagefright/StagefrightMediaScanner.cpp @@ -38,6 +38,7 @@ static bool FileHasAcceptableExtension(const char *extension) { ".mpeg", ".ogg", ".mid", ".smf", ".imy", ".wma", ".aac", ".wav", ".amr", ".midi", ".xmf", ".rtttl", ".rtx", ".ota", ".mkv", ".mka", ".webm", ".ts", ".fl", ".flac", ".mxmf", + ".avi", }; static const size_t kNumValidExtensions = sizeof(kValidExtensions) / sizeof(kValidExtensions[0]); diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp index e9e5ef967d05..76f47f74b117 100644 --- a/media/libstagefright/WAVExtractor.cpp +++ b/media/libstagefright/WAVExtractor.cpp @@ -425,6 +425,11 @@ bool SniffWAV( return false; } + sp<MediaExtractor> extractor = new WAVExtractor(source); + if (extractor->countTracks() == 0) { + return false; + } + *mimeType = MEDIA_MIMETYPE_CONTAINER_WAV; *confidence = 0.3f; diff --git a/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp b/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp index 949a5e4e8f24..1096717ff464 100644 --- a/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp +++ b/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp @@ -79,6 +79,7 @@ status_t ChromiumHTTPDataSource::connect_l( } mURI = uri; + mContentType = String8("application/octet-stream"); if (headers != NULL) { mHeaders = *headers; @@ -99,10 +100,12 @@ status_t ChromiumHTTPDataSource::connect_l( return mState == CONNECTED ? OK : mIOResult; } -void ChromiumHTTPDataSource::onConnectionEstablished(int64_t contentSize) { +void ChromiumHTTPDataSource::onConnectionEstablished( + int64_t contentSize, const char *contentType) { Mutex::Autolock autoLock(mLock); mState = CONNECTED; mContentSize = (contentSize < 0) ? -1 : contentSize + mCurrentOffset; + mContentType = String8(contentType); mCondition.broadcast(); } @@ -314,6 +317,12 @@ String8 ChromiumHTTPDataSource::getUri() { return String8(mURI.c_str()); } +String8 ChromiumHTTPDataSource::getMIMEType() const { + Mutex::Autolock autoLock(mLock); + + return mContentType; +} + void ChromiumHTTPDataSource::clearDRMState_l() { if (mDecryptHandle != NULL) { // To release mDecryptHandle diff --git a/media/libstagefright/chromium_http/support.cpp b/media/libstagefright/chromium_http/support.cpp index 7ac56e8674b6..af2f6ac484cc 100644 --- a/media/libstagefright/chromium_http/support.cpp +++ b/media/libstagefright/chromium_http/support.cpp @@ -253,7 +253,11 @@ void SfDelegate::OnResponseStarted(URLRequest *request) { MY_LOGV(StringPrintf("response headers: %s", headers.c_str()).c_str()); - mOwner->onConnectionEstablished(request->GetExpectedContentSize()); + std::string contentType; + request->GetResponseHeaderByName("Content-Type", &contentType); + + mOwner->onConnectionEstablished( + request->GetExpectedContentSize(), contentType.c_str()); } void SfDelegate::OnReadCompleted(URLRequest *request, int bytes_read) { diff --git a/media/libstagefright/include/AVIExtractor.h b/media/libstagefright/include/AVIExtractor.h new file mode 100644 index 000000000000..375a94d26f98 --- /dev/null +++ b/media/libstagefright/include/AVIExtractor.h @@ -0,0 +1,111 @@ +/* + * 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. + */ + +#ifndef AVI_EXTRACTOR_H_ + +#define AVI_EXTRACTOR_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaSource.h> +#include <utils/Vector.h> + +namespace android { + +struct AVIExtractor : public MediaExtractor { + AVIExtractor(const sp<DataSource> &dataSource); + + virtual size_t countTracks(); + + virtual sp<MediaSource> getTrack(size_t index); + + virtual sp<MetaData> getTrackMetaData( + size_t index, uint32_t flags); + + virtual sp<MetaData> getMetaData(); + +protected: + virtual ~AVIExtractor(); + +private: + struct AVISource; + + struct SampleInfo { + uint32_t mOffset; + bool mIsKey; + }; + + struct Track { + sp<MetaData> mMeta; + Vector<SampleInfo> mSamples; + uint32_t mRate; + uint32_t mScale; + + enum Kind { + AUDIO, + VIDEO, + OTHER + + } mKind; + + size_t mNumSyncSamples; + size_t mThumbnailSampleSize; + ssize_t mThumbnailSampleIndex; + size_t mMaxSampleSize; + }; + + sp<DataSource> mDataSource; + status_t mInitCheck; + Vector<Track> mTracks; + + off64_t mMovieOffset; + bool mFoundIndex; + bool mOffsetsAreAbsolute; + + ssize_t parseChunk(off64_t offset, off64_t size, int depth = 0); + status_t parseStreamHeader(off64_t offset, size_t size); + status_t parseStreamFormat(off64_t offset, size_t size); + status_t parseIndex(off64_t offset, size_t size); + + status_t parseHeaders(); + + status_t getSampleInfo( + size_t trackIndex, size_t sampleIndex, + off64_t *offset, size_t *size, bool *isKey); + + status_t getSampleIndexAtTime( + size_t trackIndex, + int64_t timeUs, MediaSource::ReadOptions::SeekMode mode, + size_t *sampleIndex) const; + + status_t addMPEG4CodecSpecificData(size_t trackIndex); + + static bool IsCorrectChunkType( + ssize_t trackIndex, Track::Kind kind, uint32_t chunkType); + + DISALLOW_EVIL_CONSTRUCTORS(AVIExtractor); +}; + +class String8; +struct AMessage; + +bool SniffAVI( + const sp<DataSource> &source, String8 *mimeType, float *confidence, + sp<AMessage> *); + +} // namespace android + +#endif // AVI_EXTRACTOR_H_ diff --git a/media/libstagefright/include/ChromiumHTTPDataSource.h b/media/libstagefright/include/ChromiumHTTPDataSource.h index af490594adfe..0e2927d43859 100644 --- a/media/libstagefright/include/ChromiumHTTPDataSource.h +++ b/media/libstagefright/include/ChromiumHTTPDataSource.h @@ -51,6 +51,8 @@ struct ChromiumHTTPDataSource : public HTTPBase { virtual String8 getUri(); + virtual String8 getMIMEType() const; + protected: virtual ~ChromiumHTTPDataSource(); @@ -90,6 +92,8 @@ private: int64_t mContentSize; + String8 mContentType; + List<BandwidthEntry> mBandwidthHistory; size_t mNumBandwidthHistoryItems; int64_t mTotalTransferTimeUs; @@ -110,7 +114,9 @@ private: void initiateRead(void *data, size_t size); - void onConnectionEstablished(int64_t contentSize); + void onConnectionEstablished( + int64_t contentSize, const char *contentType); + void onConnectionFailed(status_t err); void onReadCompleted(ssize_t size); void onDisconnectComplete(); diff --git a/media/libstagefright/include/NuCachedSource2.h b/media/libstagefright/include/NuCachedSource2.h index 02d5817e51f3..212868236589 100644 --- a/media/libstagefright/include/NuCachedSource2.h +++ b/media/libstagefright/include/NuCachedSource2.h @@ -40,6 +40,9 @@ struct NuCachedSource2 : public DataSource { virtual sp<DecryptHandle> DrmInitialization(); virtual void getDrmInfo(sp<DecryptHandle> &handle, DrmManagerClient **client); virtual String8 getUri(); + + virtual String8 getMIMEType() const; + //////////////////////////////////////////////////////////////////////////// size_t cachedSize(); diff --git a/media/libstagefright/include/NuHTTPDataSource.h b/media/libstagefright/include/NuHTTPDataSource.h index 7dd5d591555a..2ab1f194cc5c 100644 --- a/media/libstagefright/include/NuHTTPDataSource.h +++ b/media/libstagefright/include/NuHTTPDataSource.h @@ -51,6 +51,8 @@ struct NuHTTPDataSource : public HTTPBase { virtual void getDrmInfo(sp<DecryptHandle> &handle, DrmManagerClient **client); virtual String8 getUri(); + virtual String8 getMIMEType() const; + protected: virtual ~NuHTTPDataSource(); @@ -85,6 +87,8 @@ private: bool mContentLengthValid; bool mHasChunkedTransferEncoding; + String8 mContentType; + // The number of data bytes in the current chunk before any subsequent // chunk header (or -1 if no more chunks). ssize_t mChunkDataBytesLeft; 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/LightsService.java b/services/java/com/android/server/LightsService.java index 21f2bcf00be1..1e95f3e9dd69 100644 --- a/services/java/com/android/server/LightsService.java +++ b/services/java/com/android/server/LightsService.java @@ -148,7 +148,6 @@ public class LightsService { fis.close(); return (result != '0'); } catch (Exception e) { - Slog.e(TAG, "getFlashlightEnabled failed", e); return false; } } @@ -168,7 +167,7 @@ public class LightsService { fos.write(bytes); fos.close(); } catch (Exception e) { - Slog.e(TAG, "setFlashlightEnabled failed", e); + // fail silently } } }; 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/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/tests/BiDiTests/res/layout/biditest_main.xml b/tests/BiDiTests/res/layout/biditest_main.xml index 9f77ad2e5388..087c9a34f4a5 100644 --- a/tests/BiDiTests/res/layout/biditest_main.xml +++ b/tests/BiDiTests/res/layout/biditest_main.xml @@ -48,6 +48,11 @@ </LinearLayout> + <SeekBar android:id="@+id/seekbar" + android:layout_height="wrap_content" + android:layout_width="match_parent" + /> + <view class="com.android.bidi.BiDiTestView" android:id="@+id/main" android:layout_width="match_parent" diff --git a/tests/BiDiTests/res/values/strings.xml b/tests/BiDiTests/res/values/strings.xml index ecff76e69c56..632a02ed01cb 100644 --- a/tests/BiDiTests/res/values/strings.xml +++ b/tests/BiDiTests/res/values/strings.xml @@ -18,9 +18,11 @@ <string name="edittext_text">mmmmmmmmmmmmmmmmmmmmmmmm</string> <string name="normal_text">Normal String</string> <string name="normal_long_text">mmmmmmmmmmmmmmmmmmmmmmmm</string> + <string name="normal_long_text_2">nnnnnnnnnnnnnnnnnnnnnnnn</string> + <string name="normal_long_text_3">Notify me when an open network is available</string> <string name="arabic_text">لا</string> <string name="chinese_text">利比亚局势或影响美俄关系发展</string> <string name="italic_text">Italic String</string> - <string name="bold_text">Bold String</string> + <string name="bold_text">Bold String - other text</string> <string name="bold_italic_text">Bold Italic String</string> </resources>
\ No newline at end of file diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java index 3d7dd81dad86..6c71574c4611 100644 --- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java @@ -20,16 +20,44 @@ import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.View; +import android.widget.SeekBar; + +import static com.android.bidi.BiDiTestConstants.FONT_MIN_SIZE; +import static com.android.bidi.BiDiTestConstants.FONT_MAX_SIZE; public class BiDiTestActivity extends Activity { static final String TAG = "BiDiTestActivity"; + static final int INIT_TEXT_SIZE = (FONT_MAX_SIZE - FONT_MIN_SIZE) / 2; + + private BiDiTestView textView; + private SeekBar textSizeSeekBar; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.biditest_main); + + textView = (BiDiTestView) findViewById(R.id.main); + textView.setCurrentTextSize(INIT_TEXT_SIZE); + + textSizeSeekBar = (SeekBar) findViewById(R.id.seekbar); + textSizeSeekBar.setProgress(INIT_TEXT_SIZE); + textSizeSeekBar.setMax(FONT_MAX_SIZE - FONT_MIN_SIZE); + + textSizeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + textView.setCurrentTextSize(FONT_MIN_SIZE + progress); + } + + public void onStartTrackingTouch(SeekBar seekBar) { + } + + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); } @Override diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestConstants.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestConstants.java new file mode 100644 index 000000000000..5c28e3d56434 --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestConstants.java @@ -0,0 +1,22 @@ +/* + * 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.bidi; + +public class BiDiTestConstants { + public static final int FONT_MIN_SIZE = 8; + public static final int FONT_MAX_SIZE = 72; +} diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java index e9b6fa62f084..cd415c2f320e 100644 --- a/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java @@ -32,9 +32,8 @@ public class BiDiTestView extends View { private static final int BORDER_PADDING = 4; private static final int TEXT_PADDING = 16; - private static final int TEXT_SIZE = 32; - private static final int ORIGIN = 48; - private static final int DELTA_Y = TEXT_SIZE; + private static final int TEXT_SIZE = 16; + private static final int ORIGIN = 80; private static final float DEFAULT_ITALIC_SKEW_X = -0.25f; @@ -43,6 +42,8 @@ public class BiDiTestView extends View { private String NORMAL_TEXT; private String NORMAL_LONG_TEXT; + private String NORMAL_LONG_TEXT_2; + private String NORMAL_LONG_TEXT_3; private String ITALIC_TEXT; private String BOLD_TEXT; private String BOLD_ITALIC_TEXT; @@ -51,6 +52,8 @@ public class BiDiTestView extends View { private Typeface typeface; + private int currentTextSize; + public BiDiTestView(Context context) { super(context); init(context); @@ -69,6 +72,8 @@ public class BiDiTestView extends View { private void init(Context context) { NORMAL_TEXT = context.getString(R.string.normal_text); NORMAL_LONG_TEXT = context.getString(R.string.normal_long_text); + NORMAL_LONG_TEXT_2 = context.getString(R.string.normal_long_text_2); + NORMAL_LONG_TEXT_3 = context.getString(R.string.normal_long_text_3); ITALIC_TEXT = context.getString(R.string.italic_text); BOLD_TEXT = context.getString(R.string.bold_text); BOLD_ITALIC_TEXT = context.getString(R.string.bold_italic_text); @@ -79,34 +84,50 @@ public class BiDiTestView extends View { paint.setAntiAlias(true); } + public void setCurrentTextSize(int size) { + currentTextSize = size; + invalidate(); + } + @Override public void onDraw(Canvas canvas) { drawInsideRect(canvas, Color.BLACK); - int deltaX = testString(canvas, NORMAL_TEXT, ORIGIN, ORIGIN, paint, typeface, - false, false, Paint.DIRECTION_LTR); - deltaX += testString(canvas, ITALIC_TEXT, ORIGIN + deltaX, ORIGIN, paint, typeface, - true, false, Paint.DIRECTION_LTR); - deltaX += testString(canvas, BOLD_TEXT, ORIGIN + deltaX, ORIGIN, paint, typeface, - false, true, Paint.DIRECTION_LTR); - deltaX += testString(canvas, BOLD_ITALIC_TEXT, ORIGIN + deltaX, ORIGIN, paint, typeface, - true, true, Paint.DIRECTION_LTR); + int deltaX = testString(canvas, NORMAL_TEXT, ORIGIN, ORIGIN, + paint, typeface, false, false, Paint.DIRECTION_LTR, currentTextSize); + + deltaX += testString(canvas, ITALIC_TEXT, ORIGIN + deltaX, ORIGIN, + paint, typeface, true, false, Paint.DIRECTION_LTR, currentTextSize); + + deltaX += testString(canvas, BOLD_TEXT, ORIGIN + deltaX, ORIGIN, + paint, typeface, false, true, Paint.DIRECTION_LTR, currentTextSize); + + deltaX += testString(canvas, BOLD_ITALIC_TEXT, ORIGIN + deltaX, ORIGIN, + paint, typeface, true, true, Paint.DIRECTION_LTR, currentTextSize); // Test with a long string - deltaX = testString(canvas, NORMAL_LONG_TEXT, ORIGIN, ORIGIN + 2 * DELTA_Y, paint, typeface, - false, false, Paint.DIRECTION_LTR); + deltaX = testString(canvas, NORMAL_LONG_TEXT, ORIGIN, ORIGIN + 2 * currentTextSize, + paint, typeface, false, false, Paint.DIRECTION_LTR, currentTextSize); + + // Test with a long string + deltaX = testString(canvas, NORMAL_LONG_TEXT_2, ORIGIN, ORIGIN + 4 * currentTextSize, + paint, typeface, false, false, Paint.DIRECTION_LTR, currentTextSize); + + // Test with a long string + deltaX = testString(canvas, NORMAL_LONG_TEXT_3, ORIGIN, ORIGIN + 6 * currentTextSize, + paint, typeface, false, false, Paint.DIRECTION_LTR, currentTextSize); // Test Arabic ligature - deltaX = testString(canvas, ARABIC_TEXT, ORIGIN, ORIGIN + 4 * DELTA_Y, paint, typeface, - false, false, Paint.DIRECTION_RTL); + deltaX = testString(canvas, ARABIC_TEXT, ORIGIN, ORIGIN + 8 * currentTextSize, + paint, typeface, false, false, Paint.DIRECTION_RTL, currentTextSize); // Test Chinese - deltaX = testString(canvas, CHINESE_TEXT, ORIGIN, ORIGIN + 6 * DELTA_Y, paint, typeface, - false, false, Paint.DIRECTION_LTR); + deltaX = testString(canvas, CHINESE_TEXT, ORIGIN, ORIGIN + 10 * currentTextSize, + paint, typeface, false, false, Paint.DIRECTION_LTR, currentTextSize); } private int testString(Canvas canvas, String text, int x, int y, Paint paint, Typeface typeface, - boolean isItalic, boolean isBold, int dir) { + boolean isItalic, boolean isBold, int dir, int textSize) { paint.setTypeface(typeface); // Set paint properties @@ -118,27 +139,28 @@ public class BiDiTestView extends View { paint.setTextSkewX(DEFAULT_ITALIC_SKEW_X); } - drawTextWithCanvasDrawText(text, canvas, x, y, TEXT_SIZE, Color.WHITE); + drawTextWithCanvasDrawText(text, canvas, x, y, textSize, Color.WHITE); int length = text.length(); float[] advances = new float[length]; - float textWidth = paint.getTextRunAdvances(text, 0, length, 0, length, 0, advances, 0); + float textWidthHB = paint.getTextRunAdvances(text, 0, length, 0, length, 0, advances, 0); + float textWidthICU = paint.getTextRunAdvancesICU(text, 0, length, 0, length, 0, advances, 0); - logAdvances(text, textWidth, advances); - drawBoxAroundText(canvas, x, y, textWidth, TEXT_SIZE, Color.RED); + logAdvances(text, textWidthHB, textWidthICU, advances); + drawMetricsAroundText(canvas, x, y, textWidthHB, textWidthICU, textSize, Color.RED, Color.GREEN); paint.setColor(Color.WHITE); char[] glyphs = new char[2*length]; int count = getGlyphs(text, glyphs, dir); - logGlypths(glyphs, count); - drawTextWithDrawGlyph(canvas, glyphs, count, x, y + DELTA_Y); +// logGlypths(glyphs, count); + drawTextWithDrawGlyph(canvas, glyphs, count, x, y + currentTextSize); // Restore old paint properties paint.setFakeBoldText(oldFakeBold); paint.setTextSkewX(oldTextSkewX); - return (int) Math.ceil(textWidth) + TEXT_PADDING; + return (int) Math.ceil(textWidthHB) + TEXT_PADDING; } private void drawTextWithDrawGlyph(Canvas canvas, char[] glyphs, int count, int x, int y) { @@ -172,19 +194,21 @@ public class BiDiTestView extends View { canvas.drawText(text, x, y, paint); } - private void drawBoxAroundText(Canvas canvas, int x, int y, float textWidth, int textSize, - int color) { + private void drawMetricsAroundText(Canvas canvas, int x, int y, float textWidthHB, + float textWidthICU, int textSize, int color, int colorICU) { paint.setColor(color); canvas.drawLine(x, y - textSize, x, y + 8, paint); - canvas.drawLine(x, y + 8, x + textWidth, y + 8, paint); - canvas.drawLine(x + textWidth, y - textSize, x + textWidth, y + 8, paint); + canvas.drawLine(x, y + 8, x + textWidthHB, y + 8, paint); + canvas.drawLine(x + textWidthHB, y - textSize, x + textWidthHB, y + 8, paint); + paint.setColor(colorICU); + canvas.drawLine(x + textWidthICU, y - textSize, x + textWidthICU, y + 8, paint); } - private void logAdvances(String text, float textWidth, float[] advances) { - Log.v(TAG, "Advances for text: " + text + " total=" + textWidth); - int length = advances.length; - for(int n=0; n<length; n++){ - Log.v(TAG, "adv[" + n + "]=" + advances[n]); - } + private void logAdvances(String text, float textWidth, float textWidthICU, float[] advances) { + Log.v(TAG, "Advances for text: " + text + " total= " + textWidth + " - totalICU= " + textWidthICU); +// int length = advances.length; +// for(int n=0; n<length; n++){ +// Log.v(TAG, "adv[" + n + "]=" + advances[n]); +// } } } 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()); + } } |