diff options
366 files changed, 12909 insertions, 2685 deletions
@@ -444,7 +444,7 @@ web_docs_sample_code_flags := \ ## SDK version identifiers used in the published docs # major[.minor] version for current SDK. (full releases only) -framework_docs_SDK_VERSION:=2.2 +framework_docs_SDK_VERSION:=2.3 # release version (ie "Release x") (full releases only) framework_docs_SDK_REL_ID:=1 # flag to build offline docs for a preview release diff --git a/api/current.xml b/api/current.xml index 3d5a265..7624086 100644 --- a/api/current.xml +++ b/api/current.xml @@ -6774,6 +6774,39 @@ visibility="public" > </field> +<field name="overScrollFooter" + type="int" + transient="false" + volatile="false" + value="16843459" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="overScrollHeader" + type="int" + transient="false" + volatile="false" + value="16843458" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="overScrollMode" + type="int" + transient="false" + volatile="false" + value="16843457" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="padding" type="int" transient="false" @@ -12904,6 +12937,23 @@ > </field> </class> +<class name="R.fraction" + extends="java.lang.Object" + abstract="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +<constructor name="R.fraction" + type="android.R.fraction" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +</class> <class name="R.id" extends="java.lang.Object" abstract="false" @@ -18944,7 +18994,7 @@ visibility="public" > <method name="after" - return="void" + return="android.animation.AnimatorSet.Builder" abstract="false" native="false" synchronized="false" @@ -18957,7 +19007,7 @@ </parameter> </method> <method name="after" - return="void" + return="android.animation.AnimatorSet.Builder" abstract="false" native="false" synchronized="false" @@ -18970,7 +19020,7 @@ </parameter> </method> <method name="before" - return="void" + return="android.animation.AnimatorSet.Builder" abstract="false" native="false" synchronized="false" @@ -18983,7 +19033,7 @@ </parameter> </method> <method name="with" - return="void" + return="android.animation.AnimatorSet.Builder" abstract="false" native="false" synchronized="false" @@ -23927,6 +23977,21 @@ <parameter name="packageName" type="java.lang.String"> </parameter> </method> +<method name="moveTaskToFront" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="taskId" type="int"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> <method name="restartPackage" return="void" abstract="false" @@ -23940,6 +24005,17 @@ <parameter name="packageName" type="java.lang.String"> </parameter> </method> +<field name="MOVE_TASK_WITH_HOME" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="RECENT_WITH_EXCLUDED" type="int" transient="false" @@ -33066,6 +33142,25 @@ visibility="public" > </method> +<method name="getActivities" + return="android.app.PendingIntent" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="requestCode" type="int"> +</parameter> +<parameter name="intents" type="android.content.Intent[]"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> <method name="getActivity" return="android.app.PendingIntent" abstract="false" @@ -47145,6 +47240,19 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="startActivities" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="intents" type="android.content.Intent[]"> +</parameter> +</method> <method name="startActivity" return="void" abstract="true" @@ -48602,6 +48710,19 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="startActivities" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="intents" type="android.content.Intent[]"> +</parameter> +</method> <method name="startActivity" return="void" abstract="false" @@ -52554,6 +52675,17 @@ visibility="public" > </field> +<field name="FLAG_ACTIVITY_CLEAR_TASK" + type="int" + transient="false" + volatile="false" + value="32768" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="FLAG_ACTIVITY_CLEAR_TOP" type="int" transient="false" @@ -52708,6 +52840,17 @@ visibility="public" > </field> +<field name="FLAG_ACTIVITY_TASK_ON_HOME" + type="int" + transient="false" + volatile="false" + value="16384" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="FLAG_DEBUG_LOG_RESOLUTION" type="int" transient="false" @@ -70609,17 +70752,6 @@ <parameter name="message" type="java.lang.String"> </parameter> </constructor> -<field name="TYPE_DRM_INFO_ACQUISITION_FAILED" - type="int" - transient="false" - volatile="false" - value="2008" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="TYPE_NOT_SUPPORTED" type="int" transient="false" @@ -70753,17 +70885,6 @@ visibility="public" > </method> -<field name="DRM_INFO_OBJECT" - type="java.lang.String" - transient="false" - volatile="false" - value=""drm_info_object"" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="DRM_INFO_STATUS_OBJECT" type="java.lang.String" transient="false" @@ -70786,17 +70907,6 @@ visibility="public" > </field> -<field name="TYPE_DRM_INFO_ACQUIRED" - type="int" - transient="false" - volatile="false" - value="1003" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="TYPE_DRM_INFO_PROCESSED" type="int" transient="false" @@ -71272,6 +71382,19 @@ </parameter> </constructor> <method name="acquireDrmInfo" + return="android.drm.DrmInfo" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="drmInfoRequest" type="android.drm.DrmInfoRequest"> +</parameter> +</method> +<method name="acquireRights" return="int" abstract="false" native="false" @@ -71469,6 +71592,32 @@ <parameter name="mimeType" type="java.lang.String"> </parameter> </method> +<method name="getMetadata" + return="android.content.ContentValues" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="path" type="java.lang.String"> +</parameter> +</method> +<method name="getMetadata" + return="android.content.ContentValues" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="android.net.Uri"> +</parameter> +</method> <method name="getOriginalMimeType" return="java.lang.String" abstract="false" @@ -98978,6 +99127,17 @@ visibility="public" > </field> +<field name="MODE_IN_COMMUNICATION" + type="int" + transient="false" + volatile="false" + value="3" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="MODE_NORMAL" type="int" transient="false" @@ -144138,6 +144298,17 @@ visibility="public" > </method> +<method name="penaltyFlashScreen" + return="android.os.StrictMode.ThreadPolicy.Builder" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="penaltyLog" return="android.os.StrictMode.ThreadPolicy.Builder" abstract="false" @@ -155992,6 +156163,17 @@ visibility="public" > </field> +<field name="GROUP_IS_READ_ONLY" + type="java.lang.String" + transient="false" + volatile="false" + value=""group_is_read_only"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="GROUP_VISIBLE" type="java.lang.String" transient="false" @@ -157824,6 +158006,17 @@ visibility="public" > </field> +<field name="ACTION_MTP_SESSION_END" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.provider.action.MTP_SESSION_END"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ACTION_VIDEO_CAPTURE" type="java.lang.String" transient="false" @@ -174449,6 +174642,19 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="startActivities" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="intents" type="android.content.Intent[]"> +</parameter> +</method> <method name="startActivity" return="void" abstract="false" @@ -205520,6 +205726,17 @@ visibility="public" > </method> +<method name="getOverScrollMode" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getPaddingBottom" return="int" abstract="false" @@ -206851,6 +207068,25 @@ <parameter name="heightMeasureSpec" type="int"> </parameter> </method> +<method name="onOverScrolled" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="protected" +> +<parameter name="scrollX" type="int"> +</parameter> +<parameter name="scrollY" type="int"> +</parameter> +<parameter name="clampedX" type="boolean"> +</parameter> +<parameter name="clampedY" type="boolean"> +</parameter> +</method> <method name="onRestoreInstanceState" return="void" abstract="false" @@ -207004,6 +207240,35 @@ <parameter name="visibility" type="int"> </parameter> </method> +<method name="overScrollBy" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="protected" +> +<parameter name="deltaX" type="int"> +</parameter> +<parameter name="deltaY" type="int"> +</parameter> +<parameter name="scrollX" type="int"> +</parameter> +<parameter name="scrollY" type="int"> +</parameter> +<parameter name="scrollRangeX" type="int"> +</parameter> +<parameter name="scrollRangeY" type="int"> +</parameter> +<parameter name="maxOverScrollX" type="int"> +</parameter> +<parameter name="maxOverScrollY" type="int"> +</parameter> +<parameter name="isTouchEvent" type="boolean"> +</parameter> +</method> <method name="performClick" return="boolean" abstract="false" @@ -207921,6 +208186,19 @@ <parameter name="l" type="android.view.View.OnTouchListener"> </parameter> </method> +<method name="setOverScrollMode" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="overScrollMode" type="int"> +</parameter> +</method> <method name="setPadding" return="void" abstract="false" @@ -208755,6 +209033,39 @@ visibility="public" > </field> +<field name="OVER_SCROLL_ALWAYS" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="OVER_SCROLL_IF_CONTENT_SCROLLS" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="OVER_SCROLL_NEVER" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET" type="int[]" transient="false" @@ -209635,6 +209946,28 @@ visibility="public" > </method> +<method name="getScaledOverflingDistance" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getScaledOverscrollDistance" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getScaledPagingTouchSlop" return="int" abstract="false" @@ -220872,6 +221205,17 @@ visibility="public" > </method> +<method name="getShortcutInputMethodsAndSubtypes" + return="java.util.Map<android.view.inputmethod.InputMethodInfo, java.util.List<android.view.inputmethod.InputMethodSubtype>>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="hideSoftInputFromInputMethod" return="void" abstract="false" @@ -221049,6 +221393,23 @@ <parameter name="id" type="java.lang.String"> </parameter> </method> +<method name="setInputMethodAndSubtype" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="token" type="android.os.IBinder"> +</parameter> +<parameter name="id" type="java.lang.String"> +</parameter> +<parameter name="subtype" type="android.view.inputmethod.InputMethodSubtype"> +</parameter> +</method> <method name="showInputMethodAndSubtypeEnabler" return="void" abstract="false" @@ -224474,6 +224835,17 @@ visibility="public" > </method> +<method name="getUseWebViewBackgroundForOverscrollBackground" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getUseWideViewPort" return="boolean" abstract="false" @@ -225079,6 +225451,19 @@ <parameter name="use" type="boolean"> </parameter> </method> +<method name="setUseWebViewBackgroundForOverscrollBackground" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="view" type="boolean"> +</parameter> +</method> <method name="setUseWideViewPort" return="void" abstract="false" @@ -237520,6 +237905,28 @@ visibility="public" > </method> +<method name="getOverscrollFooter" + return="android.graphics.drawable.Drawable" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getOverscrollHeader" + return="android.graphics.drawable.Drawable" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="removeFooterView" return="boolean" abstract="false" @@ -237611,6 +238018,32 @@ <parameter name="itemsCanFocus" type="boolean"> </parameter> </method> +<method name="setOverscrollFooter" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="footer" type="android.graphics.drawable.Drawable"> +</parameter> +</method> +<method name="setOverscrollHeader" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="header" type="android.graphics.drawable.Drawable"> +</parameter> +</method> <method name="setSelection" return="void" abstract="false" @@ -238162,6 +238595,334 @@ </parameter> </method> </interface> +<class name="OverScroller" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="OverScroller" + type="android.widget.OverScroller" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +</constructor> +<constructor name="OverScroller" + type="android.widget.OverScroller" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="interpolator" type="android.graphics.Interpolator"> +</parameter> +<parameter name="bounceCoefficientX" type="float"> +</parameter> +<parameter name="bounceCoefficientY" type="float"> +</parameter> +<parameter name="flywheel" type="boolean"> +</parameter> +</constructor> +<method name="abortAnimation" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="computeScrollOffset" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="fling" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="startX" type="int"> +</parameter> +<parameter name="startY" type="int"> +</parameter> +<parameter name="velocityX" type="int"> +</parameter> +<parameter name="velocityY" type="int"> +</parameter> +<parameter name="minX" type="int"> +</parameter> +<parameter name="maxX" type="int"> +</parameter> +<parameter name="minY" type="int"> +</parameter> +<parameter name="maxY" type="int"> +</parameter> +</method> +<method name="fling" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="startX" type="int"> +</parameter> +<parameter name="startY" type="int"> +</parameter> +<parameter name="velocityX" type="int"> +</parameter> +<parameter name="velocityY" type="int"> +</parameter> +<parameter name="minX" type="int"> +</parameter> +<parameter name="maxX" type="int"> +</parameter> +<parameter name="minY" type="int"> +</parameter> +<parameter name="maxY" type="int"> +</parameter> +<parameter name="overX" type="int"> +</parameter> +<parameter name="overY" type="int"> +</parameter> +</method> +<method name="forceFinished" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="finished" type="boolean"> +</parameter> +</method> +<method name="getCurrX" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getCurrY" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getFinalX" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getFinalY" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getStartX" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getStartY" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isFinished" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isOverScrolled" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="notifyHorizontalEdgeReached" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="startX" type="int"> +</parameter> +<parameter name="finalX" type="int"> +</parameter> +<parameter name="overX" type="int"> +</parameter> +</method> +<method name="notifyVerticalEdgeReached" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="startY" type="int"> +</parameter> +<parameter name="finalY" type="int"> +</parameter> +<parameter name="overY" type="int"> +</parameter> +</method> +<method name="setFriction" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="friction" type="float"> +</parameter> +</method> +<method name="springBack" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="startX" type="int"> +</parameter> +<parameter name="startY" type="int"> +</parameter> +<parameter name="minX" type="int"> +</parameter> +<parameter name="maxX" type="int"> +</parameter> +<parameter name="minY" type="int"> +</parameter> +<parameter name="maxY" type="int"> +</parameter> +</method> +<method name="startScroll" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="startX" type="int"> +</parameter> +<parameter name="startY" type="int"> +</parameter> +<parameter name="dx" type="int"> +</parameter> +<parameter name="dy" type="int"> +</parameter> +</method> +<method name="startScroll" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="startX" type="int"> +</parameter> +<parameter name="startY" type="int"> +</parameter> +<parameter name="dx" type="int"> +</parameter> +<parameter name="dy" type="int"> +</parameter> +<parameter name="duration" type="int"> +</parameter> +</method> +</class> <class name="PopupMenu" extends="java.lang.Object" abstract="false" diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk index 654d9dc..93baefd 100644 --- a/cmds/stagefright/Android.mk +++ b/cmds/stagefright/Android.mk @@ -7,13 +7,16 @@ LOCAL_SRC_FILES:= \ SineSource.cpp LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia libutils libbinder libstagefright_foundation + libstagefright libmedia libutils libbinder libstagefright_foundation \ + libskia LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ frameworks/base/media/libstagefright \ frameworks/base/media/libstagefright/include \ - $(TOP)/frameworks/base/include/media/stagefright/openmax + $(TOP)/frameworks/base/include/media/stagefright/openmax \ + external/skia/include/core \ + external/skia/include/images \ LOCAL_CFLAGS += -Wno-multichar diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index 579d8c3..7e7f6d1 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -49,6 +49,10 @@ #include <media/stagefright/MPEG2TSWriter.h> #include <media/stagefright/MPEG4Writer.h> +#include <private/media/VideoFrame.h> +#include <SkBitmap.h> +#include <SkImageEncoder.h> + #include <fcntl.h> using namespace android; @@ -681,6 +685,19 @@ int main(int argc, char **argv) { if (mem != NULL) { printf("captureFrame(%s) => OK\n", filename); + + VideoFrame *frame = (VideoFrame *)mem->pointer(); + + SkBitmap bitmap; + bitmap.setConfig( + SkBitmap::kRGB_565_Config, frame->mWidth, frame->mHeight); + + bitmap.setPixels((uint8_t *)frame + sizeof(VideoFrame)); + + CHECK(SkImageEncoder::EncodeFile( + "/sdcard/out.jpg", bitmap, + SkImageEncoder::kJPEG_Type, + SkImageEncoder::kDefaultQuality)); } else { mem = retriever->extractAlbumArt(); diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 9ba9388..f5420d1 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -341,6 +341,20 @@ public final class AnimatorSet extends Animator { return this; } + @Override + public void setupStartValues() { + for (Node node : mNodes) { + node.animation.setupStartValues(); + } + } + + @Override + public void setupEndValues() { + for (Node node : mNodes) { + node.animation.setupEndValues(); + } + } + /** * {@inheritDoc} * @@ -401,6 +415,7 @@ public final class AnimatorSet extends Animator { } } }); + delayAnim.start(); } if (mListeners != null) { ArrayList<AnimatorListener> tmpListeners = @@ -408,6 +423,11 @@ public final class AnimatorSet extends Animator { int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationStart(this); + if (mNodes.size() == 0) { + // Handle unusual case where empty AnimatorSet is started - should send out + // end event immediately since the event will not be sent out at all otherwise + tmpListeners.get(i).onAnimationEnd(this); + } } } } @@ -894,7 +914,7 @@ public final class AnimatorSet extends Animator { * @param anim The animation that will play when the animation supplied to the * {@link AnimatorSet#play(Animator)} method starts. */ - public void with(Animator anim) { + public Builder with(Animator anim) { Node node = mNodeMap.get(anim); if (node == null) { node = new Node(anim); @@ -903,6 +923,7 @@ public final class AnimatorSet extends Animator { } Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH); node.addDependency(dependency); + return this; } /** @@ -913,7 +934,7 @@ public final class AnimatorSet extends Animator { * @param anim The animation that will play when the animation supplied to the * {@link AnimatorSet#play(Animator)} method ends. */ - public void before(Animator anim) { + public Builder before(Animator anim) { Node node = mNodeMap.get(anim); if (node == null) { node = new Node(anim); @@ -922,6 +943,7 @@ public final class AnimatorSet extends Animator { } Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER); node.addDependency(dependency); + return this; } /** @@ -932,7 +954,7 @@ public final class AnimatorSet extends Animator { * @param anim The animation whose end will cause the animation supplied to the * {@link AnimatorSet#play(Animator)} method to play. */ - public void after(Animator anim) { + public Builder after(Animator anim) { Node node = mNodeMap.get(anim); if (node == null) { node = new Node(anim); @@ -941,6 +963,7 @@ public final class AnimatorSet extends Animator { } Dependency dependency = new Dependency(node, Dependency.AFTER); mCurrentNode.addDependency(dependency); + return this; } /** @@ -951,11 +974,12 @@ public final class AnimatorSet extends Animator { * @param delay The number of milliseconds that should elapse before the * animation starts. */ - public void after(long delay) { + public Builder after(long delay) { // setup dummy ValueAnimator just to run the clock ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); anim.setDuration(delay); after(anim); + return this; } } diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index b021e75..e192067 100755 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -19,6 +19,7 @@ package android.animation; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.util.AndroidRuntimeException; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AnimationUtils; @@ -860,21 +861,22 @@ public class ValueAnimator extends Animator { /** * Start the animation playing. This version of start() takes a boolean flag that indicates * whether the animation should play in reverse. The flag is usually false, but may be set - * to true if called from the reverse() method/ + * to true if called from the reverse() method. + * + * <p>The animation started by calling this method will be run on the thread that called + * this method. This thread should have a Looper on it (a runtime exception will be thrown if + * this is not the case). Also, if the animation will animate + * properties of objects in the view hierarchy, then the calling thread should be the UI + * thread for that view hierarchy.</p> * * @param playBackwards Whether the ValueAnimator should start playing in reverse. */ private void start(boolean playBackwards) { - mPlayingBackwards = playBackwards; - Looper looper = Looper.getMainLooper(); - final boolean isUiThread; - if (looper != null) { - isUiThread = Thread.currentThread() == looper.getThread(); - } else { - // ignore check if we don't have a Looper (this isn't an Activity) - isUiThread = true; + if (Looper.myLooper() == null) { + throw new AndroidRuntimeException("Animators may only be run on Looper threads"); } - if ((mStartDelay == 0) && isUiThread) { + mPlayingBackwards = playBackwards; + if (mStartDelay == 0) { if (mListeners != null) { ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); @@ -912,9 +914,14 @@ public class ValueAnimator extends Animator { listener.onAnimationCancel(this); } } - // Just set the CANCELED flag - this causes the animation to end the next time a frame - // is processed. - mPlayingState = CANCELED; + // Only cancel if the animation is actually running or has been started and is about + // to run + if (mPlayingState != STOPPED || sPendingAnimations.get().contains(this) || + sDelayedAnims.get().contains(this)) { + // Just set the CANCELED flag - this causes the animation to end the next time a frame + // is processed. + mPlayingState = CANCELED; + } } @Override diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 5174f19..d69a179 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -3135,6 +3135,30 @@ public class Activity extends ContextThemeWrapper } /** + * Launch a new activity. You will not receive any information about when + * the activity exits. This implementation overrides the base version, + * providing information about + * the activity performing the launch. Because of this additional + * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not + * required; if not specified, the new activity will be added to the + * task of the caller. + * + * <p>This method throws {@link android.content.ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param intents The intents to start. + * + * @throws android.content.ActivityNotFoundException + * + * @see #startActivityForResult + */ + @Override + public void startActivities(Intent[] intents) { + mInstrumentation.execStartActivities(this, mMainThread.getApplicationThread(), + mToken, this, intents); + } + + /** * Like {@link #startActivity(Intent)}, but taking a IntentSender * to start; see * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} @@ -3616,7 +3640,7 @@ public class Activity extends ContextThemeWrapper ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, mParent == null ? mToken : mParent.mToken, - mEmbeddedID, requestCode, data, null, flags); + mEmbeddedID, requestCode, new Intent[] { data }, null, flags); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { // Empty diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index fe1e7d7..e168034 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -334,6 +334,32 @@ public class ActivityManager { } /** + * Flag for {@link #moveTaskToFront(int, int)}: also move the "home" + * activity along with the task, so it is positioned immediately behind + * the task. + */ + public static final int MOVE_TASK_WITH_HOME = 0x00000001; + + /** + * Ask that the task associated with a given task ID be moved to the + * front of the stack, so it is now visible to the user. Requires that + * the caller hold permission {@link android.Manifest.permission#REORDER_TASKS} + * or a SecurityException will be thrown. + * + * @param taskId The identifier of the task to be moved, as found in + * {@link RunningTaskInfo} or {@link RecentTaskInfo}. + * @param flags Additional operational flags, 0 or more of + * {@link #MOVE_TASK_WITH_HOME}. + */ + public void moveTaskToFront(int taskId, int flags) { + try { + ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + } + } + + /** * Information you can retrieve about a particular Service that is * currently running in the system. */ diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 8cc6428..f3cc4ee 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -41,6 +41,7 @@ import android.os.StrictMode; import android.text.TextUtils; import android.util.Config; import android.util.Log; +import android.util.Singleton; import java.util.ArrayList; import java.util.List; @@ -52,8 +53,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM * Cast a Binder object into an activity manager interface, generating * a proxy if needed. */ - static public IActivityManager asInterface(IBinder obj) - { + static public IActivityManager asInterface(IBinder obj) { if (obj == null) { return null; } @@ -62,27 +62,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM if (in != null) { return in; } - + return new ActivityManagerProxy(obj); } - + /** * Retrieve the system's default/global activity manager. */ - static public IActivityManager getDefault() - { - if (gDefault != null) { - //if (Config.LOGV) Log.v( - // "ActivityManager", "returning cur default = " + gDefault); - return gDefault; - } - IBinder b = ServiceManager.getService("activity"); - if (Config.LOGV) Log.v( - "ActivityManager", "default service binder = " + b); - gDefault = asInterface(b); - if (Config.LOGV) Log.v( - "ActivityManager", "default service = " + gDefault); - return gDefault; + static public IActivityManager getDefault() { + return gDefault.get(); } /** @@ -95,13 +83,12 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return sSystemReady; } static boolean sSystemReady = false; - + /** * Convenience for sending a sticky broadcast. For internal use only. * If you don't care about permission, use null. */ - static public void broadcastStickyIntent(Intent intent, String permission) - { + static public void broadcastStickyIntent(Intent intent, String permission) { try { getDefault().broadcastIntent( null, intent, null, null, Activity.RESULT_OK, null, null, @@ -117,8 +104,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM } } - public ActivityManagerNative() - { + public ActivityManagerNative() { attachInterface(this, descriptor); } @@ -492,7 +478,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case MOVE_TASK_TO_FRONT_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int task = data.readInt(); - moveTaskToFront(task); + int fl = data.readInt(); + moveTaskToFront(task, fl); reply.writeNoException(); return true; } @@ -791,13 +778,19 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IBinder token = data.readStrongBinder(); String resultWho = data.readString(); int requestCode = data.readInt(); - Intent requestIntent = data.readInt() != 0 - ? Intent.CREATOR.createFromParcel(data) : null; - String requestResolvedType = data.readString(); + Intent[] requestIntents; + String[] requestResolvedTypes; + if (data.readInt() != 0) { + requestIntents = data.createTypedArray(Intent.CREATOR); + requestResolvedTypes = data.createStringArray(); + } else { + requestIntents = null; + requestResolvedTypes = null; + } int fl = data.readInt(); IIntentSender res = getIntentSender(type, packageName, token, - resultWho, requestCode, requestIntent, - requestResolvedType, fl); + resultWho, requestCode, requestIntents, + requestResolvedTypes, fl); reply.writeNoException(); reply.writeStrongBinder(res != null ? res.asBinder() : null); return true; @@ -1355,17 +1348,55 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case START_ACTIVITIES_IN_PACKAGE_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + int uid = data.readInt(); + Intent[] intents = data.createTypedArray(Intent.CREATOR); + String[] resolvedTypes = data.createStringArray(); + IBinder resultTo = data.readStrongBinder(); + int result = startActivitiesInPackage(uid, intents, resolvedTypes, resultTo); + reply.writeNoException(); + reply.writeInt(result); + return true; + } + + case START_ACTIVITIES_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + Intent[] intents = data.createTypedArray(Intent.CREATOR); + String[] resolvedTypes = data.createStringArray(); + IBinder resultTo = data.readStrongBinder(); + int result = startActivities(app, intents, resolvedTypes, resultTo); + reply.writeNoException(); + reply.writeInt(result); + return true; } - + + } + return super.onTransact(code, data, reply, flags); } - public IBinder asBinder() - { + public IBinder asBinder() { return this; } - private static IActivityManager gDefault; + private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() { + protected IActivityManager create() { + IBinder b = ServiceManager.getService("activity"); + if (Config.LOGV) { + Log.v("ActivityManager", "default service binder = " + b); + } + IActivityManager am = asInterface(b); + if (Config.LOGV) { + Log.v("ActivityManager", "default service = " + am); + } + return am; + } + }; } class ActivityManagerProxy implements IActivityManager @@ -1829,12 +1860,13 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return list; } - public void moveTaskToFront(int task) throws RemoteException + public void moveTaskToFront(int task, int flags) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(task); + data.writeInt(flags); mRemote.transact(MOVE_TASK_TO_FRONT_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); @@ -2283,7 +2315,7 @@ class ActivityManagerProxy implements IActivityManager } public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) + int requestCode, Intent[] intents, String[] resolvedTypes, int flags) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2293,13 +2325,13 @@ class ActivityManagerProxy implements IActivityManager data.writeStrongBinder(token); data.writeString(resultWho); data.writeInt(requestCode); - if (intent != null) { + if (intents != null) { data.writeInt(1); - intent.writeToParcel(data, 0); + data.writeTypedArray(intents, 0); + data.writeStringArray(resolvedTypes); } else { data.writeInt(0); } - data.writeString(resolvedType); data.writeInt(flags); mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0); reply.readException(); @@ -3026,5 +3058,39 @@ class ActivityManagerProxy implements IActivityManager return res; } + public int startActivities(IApplicationThread caller, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + data.writeTypedArray(intents, 0); + data.writeStringArray(resolvedTypes); + data.writeStrongBinder(resultTo); + mRemote.transact(START_ACTIVITIES_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } + + public int startActivitiesInPackage(int uid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(uid); + data.writeTypedArray(intents, 0); + data.writeStringArray(resolvedTypes); + data.writeStrongBinder(resultTo); + mRemote.transact(START_ACTIVITIES_IN_PACKAGE_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index c0714e3..5c4f57a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -870,6 +870,12 @@ public final class ActivityThread { (dbStats.dbSize > 0) ? String.valueOf(dbStats.dbSize) : " ", (dbStats.lookaside > 0) ? String.valueOf(dbStats.lookaside) : " ", dbStats.cache, dbStats.dbName); + if (dbStats.dataDump != null) { + int size = dbStats.dataDump.size(); + for (int dumpIndex = 0; dumpIndex < size; dumpIndex++) { + printRow(pw, "%s", dbStats.dataDump.get(dumpIndex)); + } + } } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 82b3f75..d2de382 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -844,6 +844,19 @@ class ContextImpl extends Context { } @Override + public void startActivities(Intent[] intents) { + if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + throw new AndroidRuntimeException( + "Calling startActivities() from outside of an Activity " + + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent." + + " Is this really what you want?"); + } + mMainThread.getInstrumentation().execStartActivities( + getOuterContext(), mMainThread.getApplicationThread(), null, + (Activity)null, intents); + } + + @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index 09a21f8..4aa4d77 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -841,6 +841,12 @@ public class DownloadManager { } ContentValues values = new ContentValues(); values.put(Downloads.Impl.COLUMN_DELETED, 1); + // if only one id is passed in, then include it in the uri itself. + // this will eliminate a full database scan in the download service. + if (ids.length == 1) { + return mResolver.update(ContentUris.withAppendedId(mBaseUri, ids[0]), values, + null, null); + } return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)); } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index c9d5448..dc18083 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -134,7 +134,7 @@ public interface IActivityManager extends IInterface { public List getServices(int maxNum, int flags) throws RemoteException; public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() throws RemoteException; - public void moveTaskToFront(int task) throws RemoteException; + public void moveTaskToFront(int task, int flags) throws RemoteException; public void moveTaskToBack(int task) throws RemoteException; public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException; public void moveTaskBackwards(int task) throws RemoteException; @@ -199,7 +199,8 @@ public interface IActivityManager extends IInterface { public static final int INTENT_SENDER_SERVICE = 4; public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) throws RemoteException; + int requestCode, Intent[] intents, String[] resolvedTypes, + int flags) throws RemoteException; public void cancelIntentSender(IIntentSender sender) throws RemoteException; public boolean clearApplicationUserData(final String packageName, final IPackageDataObserver observer) throws RemoteException; @@ -208,7 +209,8 @@ public interface IActivityManager extends IInterface { public void setProcessLimit(int max) throws RemoteException; public int getProcessLimit() throws RemoteException; - public void setProcessForeground(IBinder token, int pid, boolean isForeground) throws RemoteException; + public void setProcessForeground(IBinder token, int pid, + boolean isForeground) throws RemoteException; public int checkPermission(String permission, int pid, int uid) throws RemoteException; @@ -332,6 +334,11 @@ public interface IActivityManager extends IInterface { public boolean dumpHeap(String process, boolean managed, String path, ParcelFileDescriptor fd) throws RemoteException; + public int startActivities(IApplicationThread caller, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException; + public int startActivitiesInPackage(int uid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException; + /* * Private non-Binder interfaces */ @@ -544,4 +551,6 @@ public interface IActivityManager extends IInterface { int REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+117; int CHECK_GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118; int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+119; + int START_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+120; + int START_ACTIVITIES_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+121; } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 1deb9fb..ad811d8 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1354,8 +1354,8 @@ public class Instrumentation { * {@hide} */ public ActivityResult execStartActivity( - Context who, IBinder contextThread, IBinder token, Activity target, - Intent intent, int requestCode) { + Context who, IBinder contextThread, IBinder token, Activity target, + Intent intent, int requestCode) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { @@ -1386,6 +1386,44 @@ public class Instrumentation { /** * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)}, + * but accepts an array of activities to be started. Note that active + * {@link ActivityMonitor} objects only match against the first activity in + * the array. + * + * {@hide} + */ + public void execStartActivities(Context who, IBinder contextThread, + IBinder token, Activity target, Intent[] intents) { + IApplicationThread whoThread = (IApplicationThread) contextThread; + if (mActivityMonitors != null) { + synchronized (mSync) { + final int N = mActivityMonitors.size(); + for (int i=0; i<N; i++) { + final ActivityMonitor am = mActivityMonitors.get(i); + if (am.match(who, null, intents[0])) { + am.mHits++; + if (am.isBlocking()) { + return; + } + break; + } + } + } + } + try { + String[] resolvedTypes = new String[intents.length]; + for (int i=0; i<intents.length; i++) { + resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver()); + } + int result = ActivityManagerNative.getDefault() + .startActivities(whoThread, intents, resolvedTypes, token); + checkStartActivityResult(result, intents[0]); + } catch (RemoteException e) { + } + } + + /** + * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)}, * but for calls from a {#link Fragment}. * * @param who The Context from which the activity is being started. diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 920e457..f5a46c5 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -742,7 +742,6 @@ public class Notification implements Parcelable mLedArgb = argb; mLedOnMs = onMs; mLedOffMs = offMs; - mFlags |= FLAG_SHOW_LIGHTS; return this; } @@ -763,10 +762,6 @@ public class Notification implements Parcelable public Builder setDefaults(int defaults) { mDefaults = defaults; - int moreFlags = 0; - if ((defaults & DEFAULT_LIGHTS) != 0) { - moreFlags |= FLAG_SHOW_LIGHTS; - } return this; } @@ -851,6 +846,12 @@ public class Notification implements Parcelable n.ledOffMS = mLedOffMs; n.defaults = mDefaults; n.flags = mFlags; + if (mLedOnMs != 0 && mLedOffMs != 0) { + n.flags |= FLAG_SHOW_LIGHTS; + } + if ((mDefaults & DEFAULT_LIGHTS) != 0) { + n.flags |= FLAG_SHOW_LIGHTS; + } return n; } } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index be1dc4a..5b43b65 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -195,7 +195,67 @@ public final class PendingIntent implements Parcelable { IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_ACTIVITY, packageName, - null, null, requestCode, intent, resolvedType, flags); + null, null, requestCode, new Intent[] { intent }, + resolvedType != null ? new String[] { resolvedType } : null, flags); + return target != null ? new PendingIntent(target) : null; + } catch (RemoteException e) { + } + return null; + } + + /** + * Like {@link #getActivity(Context, int, Intent, int)}, but allows an + * array of Intents to be supplied. The first Intent in the array is + * taken as the primary key for the PendingIntent, like the single Intent + * given to {@link #getActivity(Context, int, Intent, int)}. Upon sending + * the resulting PendingIntent, all of the Intents are started in the same + * way as they would be by passing them to {@link Context#startActivities(Intent[])}. + * + * <p class="note"> + * The <em>first</em> intent in the array will be started outside of the context of an + * existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK + * Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent. (Activities after + * the first in the array are started in the context of the previous activity + * in the array, so FLAG_ACTIVITY_NEW_TASK is not needed nor desired for them.) + * </p> + * + * <p class="note"> + * The <em>last</em> intent in the array represents the key for the + * PendingIntent. In other words, it is the significant element for matching + * (as done with the single intent given to {@link #getActivity(Context, int, Intent, int)}, + * its content will be the subject of replacement by + * {@link #send(Context, int, Intent)} and {@link #FLAG_UPDATE_CURRENT}, etc. + * This is because it is the most specific of the supplied intents, and the + * UI the user actually sees when the intents are started. + * </p> + * + * @param context The Context in which this PendingIntent should start + * the activity. + * @param requestCode Private request code for the sender (currently + * not used). + * @param intents Array of Intents of the activities to be launched. + * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE}, + * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT}, + * or any of the flags as supported by + * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts + * of the intent that can be supplied when the actual send happens. + * + * @return Returns an existing or new PendingIntent matching the given + * parameters. May return null only if {@link #FLAG_NO_CREATE} has been + * supplied. + */ + public static PendingIntent getActivities(Context context, int requestCode, + Intent[] intents, int flags) { + String packageName = context.getPackageName(); + String[] resolvedTypes = new String[intents.length]; + for (int i=0; i<intents.length; i++) { + resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver()); + } + try { + IIntentSender target = + ActivityManagerNative.getDefault().getIntentSender( + IActivityManager.INTENT_SENDER_ACTIVITY, packageName, + null, null, requestCode, intents, resolvedTypes, flags); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { } @@ -230,7 +290,8 @@ public final class PendingIntent implements Parcelable { IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_BROADCAST, packageName, - null, null, requestCode, intent, resolvedType, flags); + null, null, requestCode, new Intent[] { intent }, + resolvedType != null ? new String[] { resolvedType } : null, flags); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { } @@ -266,7 +327,8 @@ public final class PendingIntent implements Parcelable { IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_SERVICE, packageName, - null, null, requestCode, intent, resolvedType, flags); + null, null, requestCode, new Intent[] { intent }, + resolvedType != null ? new String[] { resolvedType } : null, flags); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { } diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 18602df..0c280f9 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -1109,6 +1109,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS * @return true if a successful launch, false if could not (e.g. bad position). */ protected boolean launchSuggestion(int position, int actionKey, String actionMsg) { + if (mSuggestionsAdapter == null) { + return false; + } Cursor c = mSuggestionsAdapter.getCursor(); if ((c != null) && c.moveToPosition(position)) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index a463afc..27be51a 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -725,6 +725,28 @@ public abstract class Context { public abstract void startActivity(Intent intent); /** + * Launch multiple new activities. This is generally the same as calling + * {@link #startActivity(Intent)} for the first Intent in the array, + * that activity during its creation calling {@link #startActivity(Intent)} + * for the second entry, etc. Note that unlike that approach, generally + * none of the activities except the last in the array will be created + * at this point, but rather will be created when the user first visits + * them (due to pressing back from the activity on top). + * + * <p>This method throws {@link ActivityNotFoundException} + * if there was no Activity found for <em>any</em> given Intent. In this + * case the state of the activity stack is undefined (some Intents in the + * list may be on it, some not), so you probably want to avoid such situations. + * + * @param intents An array of Intents to be started. + * + * @throws ActivityNotFoundException + * + * @see PackageManager#resolveActivity + */ + public abstract void startActivities(Intent[] intents); + + /** * Like {@link #startActivity(Intent)}, but taking a IntentSender * to start. If the IntentSender is for an activity, that activity will be started * as if you had called the regular {@link #startActivity(Intent)} diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 3f5d215..f8928e4 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -266,6 +266,11 @@ public class ContextWrapper extends Context { } @Override + public void startActivities(Intent[] intents) { + mBase.startActivities(intents); + } + + @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 4b6333e..84cb2fb 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2614,13 +2614,29 @@ public class Intent implements Parcelable, Cloneable { * animation to go to the next activity state. This doesn't mean an * animation will never run -- if another activity change happens that doesn't * specify this flag before the activity started here is displayed, then - * that transition will be used. This this flag can be put to good use + * that transition will be used. This flag can be put to good use * when you are going to do a series of activity operations but the * animation seen by the user shouldn't be driven by the first activity * change but rather a later one. */ public static final int FLAG_ACTIVITY_NO_ANIMATION = 0X00010000; /** + * If set in an Intent passed to {@link Context#startActivity Context.startActivity()}, + * this flag will cause any existing task that would be associated with the + * activity to be cleared before the activity is started. That is, the activity + * becomes the new root of an otherwise empty task, and any old activities + * are finished. This can only be used in conjunction with {@link #FLAG_ACTIVITY_NEW_TASK}. + */ + public static final int FLAG_ACTIVITY_CLEAR_TASK = 0X00008000; + /** + * If set in an Intent passed to {@link Context#startActivity Context.startActivity()}, + * this flag will cause a newly launching task to be placed on top of the current + * home activity task (if there is one). That is, pressing back from the task + * will always return the user to home even if that was not the last activity they + * saw. This can only be used in conjunction with {@link #FLAG_ACTIVITY_NEW_TASK}. + */ + public static final int FLAG_ACTIVITY_TASK_ON_HOME = 0X00004000; + /** * If set, when sending a broadcast only registered receivers will be * called -- no BroadcastReceiver components will be launched. */ @@ -4871,18 +4887,22 @@ public class Intent implements Parcelable, Cloneable { * @see #FLAG_DEBUG_LOG_RESOLUTION * @see #FLAG_FROM_BACKGROUND * @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT - * @see #FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET + * @see #FLAG_ACTIVITY_CLEAR_TASK * @see #FLAG_ACTIVITY_CLEAR_TOP + * @see #FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET * @see #FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS * @see #FLAG_ACTIVITY_FORWARD_RESULT * @see #FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY * @see #FLAG_ACTIVITY_MULTIPLE_TASK * @see #FLAG_ACTIVITY_NEW_TASK + * @see #FLAG_ACTIVITY_NO_ANIMATION * @see #FLAG_ACTIVITY_NO_HISTORY * @see #FLAG_ACTIVITY_NO_USER_ACTION * @see #FLAG_ACTIVITY_PREVIOUS_IS_TOP * @see #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + * @see #FLAG_ACTIVITY_REORDER_TO_FRONT * @see #FLAG_ACTIVITY_SINGLE_TOP + * @see #FLAG_ACTIVITY_TASK_ON_HOME * @see #FLAG_RECEIVER_REGISTERED_ONLY */ public Intent setFlags(int flags) { diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 7efb7fd..87f55d2 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -2507,7 +2507,7 @@ public class SQLiteDatabase extends SQLiteClosable { if (pageCount > 0) { dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(), lookasideUsed, db.getCacheHitNum(), db.getCacheMissNum(), - db.getCachesize())); + db.getCachesize(), getDataDump(db))); } } // if there are pooled connections, return the cache stats for them also. @@ -2518,7 +2518,7 @@ public class SQLiteDatabase extends SQLiteClosable { for (SQLiteDatabase pDb : connPool.getConnectionList()) { dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") " + lastnode, 0, 0, 0, pDb.getCacheHitNum(), - pDb.getCacheMissNum(), pDb.getCachesize())); + pDb.getCacheMissNum(), pDb.getCachesize(), null)); } } } catch (SQLiteException e) { @@ -2529,6 +2529,44 @@ public class SQLiteDatabase extends SQLiteClosable { return dbStatsList; } + private static ArrayList<String> getDataDump(SQLiteDatabase db) { + // create database dump of certain data from certain databases for debugging purposes + if (db.getPath().equalsIgnoreCase( + "/data/data/com.android.providers.downloads/databases/downloads.db")) { + String sql = + "select * from downloads " + + " where notificationpackage = 'com.google.android.gsf'" + + " or status >= 400"; + Cursor cursor = db.rawQuery(sql, null); + try { + int count = cursor.getCount(); + if (count == 0) { + return null; + } + ArrayList<String> buff = new ArrayList<String>(); + buff.add(" Data from downloads.db"); + int columnCount = cursor.getColumnCount(); + for (int i =0; i < count && cursor.moveToNext(); i++) { + buff.add(" Row#" + i + ""); + for (int j = 0; j < columnCount; j++) { + String colName = cursor.getColumnName(j); + String value = cursor.getString(j); + buff.add(" " + colName + " = " + value); + } + } + for (String s : buff) Log.i("vnoritag", s); + return buff; + } catch (SQLiteException e) { + Log.w(TAG, "exception in executing the sql: " + sql, e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + return null; + } + /** * Returns list of full pathnames of all attached databases including the main database * by executing 'pragma database_list' on the database. diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java index 9496079..72377f0 100644 --- a/core/java/android/database/sqlite/SQLiteDebug.java +++ b/core/java/android/database/sqlite/SQLiteDebug.java @@ -121,27 +121,31 @@ public final class SQLiteDebug { */ public static class DbStats { /** name of the database */ - public String dbName; + public final String dbName; /** the page size for the database */ - public long pageSize; + public final long pageSize; /** the database size */ - public long dbSize; + public final long dbSize; /** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */ - public int lookaside; + public final int lookaside; /** statement cache stats: hits/misses/cachesize */ - public String cache; + public final String cache; + + /** database dump of 'useful info for debugging only */ + public final ArrayList<String> dataDump; public DbStats(String dbName, long pageCount, long pageSize, int lookaside, - int hits, int misses, int cachesize) { + int hits, int misses, int cachesize, ArrayList<String> data) { this.dbName = dbName; this.pageSize = pageSize / 1024; dbSize = (pageCount * pageSize) / 1024; this.lookaside = lookaside; this.cache = hits + "/" + misses + "/" + cachesize; + this.dataDump = data; } } diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 2b4f39a..1c295a7 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -513,21 +513,27 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo } /** - * Count the number and aggregate size of memory allocations between - * two points. + * Start counting the number and aggregate size of memory allocations. * - * The "start" function resets the counts and enables counting. The - * "stop" function disables the counting so that the analysis code - * doesn't cause additional allocations. The "get" function returns - * the specified value. + * <p>The {@link #startAllocCounting() start} function resets the counts and enables counting. + * The {@link #stopAllocCounting() stop} function disables the counting so that the analysis + * code doesn't cause additional allocations. The various <code>get</code> functions return + * the specified value. And the various <code>reset</code> functions reset the specified + * count.</p> * - * Counts are kept for the system as a whole and for each thread. + * <p>Counts are kept for the system as a whole and for each thread. * The per-thread counts for threads other than the current thread - * are not cleared by the "reset" or "start" calls. + * are not cleared by the "reset" or "start" calls.</p> */ public static void startAllocCounting() { VMDebug.startAllocCounting(); } + + /** + * Stop counting the number and aggregate size of memory allocations. + * + * @see #startAllocCounting() + */ public static void stopAllocCounting() { VMDebug.stopAllocCounting(); } @@ -671,11 +677,11 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo * for catching regressions in code that is expected to operate * without causing any allocations. * - * Pass in the maximum number of allowed allocations. Use -1 to disable - * the limit. Returns the previous limit. - * - * The preferred way to use this is: + * <p>Pass in the maximum number of allowed allocations. Use -1 to disable + * the limit. Returns the previous limit.</p> * + * <p>The preferred way to use this is: + * <pre> * int prevLimit = -1; * try { * prevLimit = Debug.setAllocationLimit(0); @@ -683,16 +689,16 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo * } finally { * Debug.setAllocationLimit(prevLimit); * } - * + * </pre> * This allows limits to be nested. The try/finally ensures that the - * limit is reset if something fails. + * limit is reset if something fails.</p> * - * Exceeding the limit causes a dalvik.system.AllocationLimitError to + * <p>Exceeding the limit causes a dalvik.system.AllocationLimitError to * be thrown from a memory allocation call. The limit is reset to -1 - * when this happens. + * when this happens.</p> * - * The feature may be disabled in the VM configuration. If so, this - * call has no effect, and always returns -1. + * <p>The feature may be disabled in the VM configuration. If so, this + * call has no effect, and always returns -1.</p> */ public static int setAllocationLimit(int limit) { return VMDebug.setAllocationLimit(limit); @@ -846,6 +852,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo * API for gathering and querying instruction counts. * * Example usage: + * <pre> * Debug.InstructionCount icount = new Debug.InstructionCount(); * icount.resetAndStart(); * [... do lots of stuff ...] @@ -855,6 +862,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo * System.out.println("Method invocations: " * + icount.globalMethodInvocations()); * } + * </pre> */ public static class InstructionCount { private static final int NUM_INSTR = 256; diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index d360140..898c642 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -17,7 +17,9 @@ package android.os; import android.util.Config; +import android.util.Log; import android.util.Printer; +import android.util.PrefixPrinter; /** * Class used to run a message loop for a thread. Threads by default do @@ -31,37 +33,38 @@ import android.util.Printer; * <p>This is a typical example of the implementation of a Looper thread, * using the separation of {@link #prepare} and {@link #loop} to create an * initial Handler to communicate with the Looper. - * + * * <pre> * class LooperThread extends Thread { * public Handler mHandler; - * + * * public void run() { * Looper.prepare(); - * + * * mHandler = new Handler() { * public void handleMessage(Message msg) { * // process incoming messages here * } * }; - * + * * Looper.loop(); * } * }</pre> */ public class Looper { - private static final boolean DEBUG = false; - private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + private static final String TAG = "Looper"; + private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE); // sThreadLocal.get() will return null unless you've called prepare(). - private static final ThreadLocal sThreadLocal = new ThreadLocal(); + private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); final MessageQueue mQueue; + final Thread mThread; volatile boolean mRun; - Thread mThread; + private Printer mLogging = null; - private static Looper mMainLooper = null; - + private static Looper mMainLooper = null; // guarded by Looper.class + /** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call @@ -74,13 +77,13 @@ public class Looper { } sThreadLocal.set(new Looper()); } - - /** Initialize the current thread as a looper, marking it as an application's main - * looper. The main looper for your application is created by the Android environment, - * so you should never need to call this function yourself. - * {@link #prepare()} + + /** + * Initialize the current thread as a looper, marking it as an + * application's main looper. The main looper for your application + * is created by the Android environment, so you should never need + * to call this function yourself. See also: {@link #prepare()} */ - public static final void prepareMainLooper() { prepare(); setMainLooper(myLooper()); @@ -92,7 +95,7 @@ public class Looper { private synchronized static void setMainLooper(Looper looper) { mMainLooper = looper; } - + /** Returns the application's main looper, which lives in the main thread of the application. */ public synchronized static final Looper getMainLooper() { @@ -100,28 +103,28 @@ public class Looper { } /** - * Run the message queue in this thread. Be sure to call + * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static final void loop() { Looper me = myLooper(); + if (me == null) { + throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); + } MessageQueue queue = me.mQueue; while (true) { Message msg = queue.next(); // might block - //if (!me.mRun) { - // break; - //} if (msg != null) { if (msg.target == null) { // No target is a magic identifier for the quit message. return; } - if (me.mLogging!= null) me.mLogging.println( + if (me.mLogging != null) me.mLogging.println( ">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what ); msg.target.dispatchMessage(msg); - if (me.mLogging!= null) me.mLogging.println( + if (me.mLogging != null) me.mLogging.println( "<<<<< Finished to " + msg.target + " " + msg.callback); msg.recycle(); @@ -134,7 +137,7 @@ public class Looper { * null if the calling thread is not associated with a Looper. */ public static final Looper myLooper() { - return (Looper)sThreadLocal.get(); + return sThreadLocal.get(); } /** @@ -179,28 +182,29 @@ public class Looper { public Thread getThread() { return mThread; } - + /** @hide */ public MessageQueue getQueue() { return mQueue; } - + public void dump(Printer pw, String prefix) { - pw.println(prefix + this); - pw.println(prefix + "mRun=" + mRun); - pw.println(prefix + "mThread=" + mThread); - pw.println(prefix + "mQueue=" + ((mQueue != null) ? mQueue : "(null")); + pw = PrefixPrinter.create(pw, prefix); + pw.println(this.toString()); + pw.println("mRun=" + mRun); + pw.println("mThread=" + mThread); + pw.println("mQueue=" + ((mQueue != null) ? mQueue : "(null")); if (mQueue != null) { synchronized (mQueue) { long now = SystemClock.uptimeMillis(); Message msg = mQueue.mMessages; int n = 0; while (msg != null) { - pw.println(prefix + " Message " + n + ": " + msg.toString(now)); + pw.println(" Message " + n + ": " + msg.toString(now)); n++; msg = msg.next; } - pw.println(prefix + "(Total messages: " + n + ")"); + pw.println("(Total messages: " + n + ")"); } } } @@ -226,4 +230,3 @@ public class Looper { } } } - diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 4facc39..854428f 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -19,9 +19,12 @@ import android.animation.ValueAnimator; import android.app.ActivityManagerNative; import android.app.ActivityThread; import android.app.ApplicationErrorReport; +import android.app.IActivityManager; import android.content.Intent; import android.util.Log; import android.util.Printer; +import android.util.Singleton; +import android.view.IWindowManager; import com.android.internal.os.RuntimeInit; @@ -108,6 +111,7 @@ public final class StrictMode { private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE); private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE); + private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE); // Only log a duplicate stack trace to the logs every second. private static final long MIN_LOG_INTERVAL_MS = 1000; @@ -180,6 +184,13 @@ public final class StrictMode { public static final int PENALTY_DEATH_ON_NETWORK = 0x200; /** + * Flash the screen during violations. + * + * @hide + */ + public static final int PENALTY_FLASH = 0x800; + + /** * @hide */ public static final int PENALTY_DROPBOX = 0x80; @@ -202,7 +213,7 @@ public final class StrictMode { */ private static final int PENALTY_MASK = PENALTY_LOG | PENALTY_DIALOG | PENALTY_DEATH | PENALTY_DROPBOX | PENALTY_GATHER | - PENALTY_DEATH_ON_NETWORK; + PENALTY_DEATH_ON_NETWORK | PENALTY_FLASH; /** * The current VmPolicy in effect. @@ -377,6 +388,13 @@ public final class StrictMode { } /** + * Flash the screen during a violation. + */ + public Builder penaltyFlashScreen() { + return enable(PENALTY_FLASH); + } + + /** * Log detected violations to the system log. */ public Builder penaltyLog() { @@ -710,7 +728,9 @@ public final class StrictMode { StrictMode.DETECT_DISK_WRITE | StrictMode.DETECT_DISK_READ | StrictMode.DETECT_NETWORK | - StrictMode.PENALTY_DROPBOX); + StrictMode.PENALTY_DROPBOX | + (IS_ENG_BUILD ? StrictMode.PENALTY_FLASH : 0) + ); sVmPolicyMask = StrictMode.DETECT_VM_CURSOR_LEAKS | StrictMode.DETECT_VM_CLOSABLE_LEAKS | StrictMode.PENALTY_DROPBOX; @@ -904,6 +924,15 @@ public final class StrictMode { return; } + final IWindowManager windowManager = (info.policy & PENALTY_FLASH) != 0 ? + sWindowManager.get() : null; + if (windowManager != null) { + try { + windowManager.showStrictModeViolation(true); + } catch (RemoteException unused) { + } + } + queue.addIdleHandler(new MessageQueue.IdleHandler() { public boolean queueIdle() { long loopFinishTime = SystemClock.uptimeMillis(); @@ -915,6 +944,12 @@ public final class StrictMode { handleViolation(v); } records.clear(); + if (windowManager != null) { + try { + windowManager.showStrictModeViolation(false); + } catch (RemoteException unused) { + } + } return false; // remove this idle handler from the array } }); @@ -953,7 +988,7 @@ public final class StrictMode { } // Not perfect, but fast and good enough for dup suppression. - Integer crashFingerprint = info.crashInfo.stackTrace.hashCode(); + Integer crashFingerprint = info.hashCode(); long lastViolationTime = 0; if (mLastViolationTime.containsKey(crashFingerprint)) { lastViolationTime = mLastViolationTime.get(crashFingerprint); @@ -1057,11 +1092,15 @@ public final class StrictMode { public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); try { - ActivityManagerNative.getDefault(). - handleApplicationStrictModeViolation( - RuntimeInit.getApplicationObject(), - violationMaskSubset, - info); + IActivityManager am = ActivityManagerNative.getDefault(); + if (am == null) { + Log.d(TAG, "No activity manager; failed to Dropbox violation."); + } else { + am.handleApplicationStrictModeViolation( + RuntimeInit.getApplicationObject(), + violationMaskSubset, + info); + } } catch (RemoteException e) { Log.e(TAG, "RemoteException handling StrictMode violation", e); } @@ -1369,6 +1408,12 @@ public final class StrictMode { } }; + private static Singleton<IWindowManager> sWindowManager = new Singleton<IWindowManager>() { + protected IWindowManager create() { + return IWindowManager.Stub.asInterface(ServiceManager.getService("window")); + } + }; + /** * Enter a named critical span (e.g. an animation) * @@ -1510,6 +1555,24 @@ public final class StrictMode { } } + @Override + public int hashCode() { + int result = 17; + result = 37 * result + crashInfo.stackTrace.hashCode(); + if (numAnimationsRunning != 0) { + result *= 37; + } + if (broadcastIntentAction != null) { + result = 37 * result + broadcastIntentAction.hashCode(); + } + if (tags != null) { + for (String tag : tags) { + result = 37 * result + tag.hashCode(); + } + } + return result; + } + /** * Create an instance of ViolationInfo initialized from a Parcel. */ diff --git a/core/java/android/preference/PreferenceFrameLayout.java b/core/java/android/preference/PreferenceFrameLayout.java new file mode 100644 index 0000000..426abf0 --- /dev/null +++ b/core/java/android/preference/PreferenceFrameLayout.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.preference; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +/** + * @hide + */ +public class PreferenceFrameLayout extends FrameLayout { + private static final int DEFAULT_TOP_PADDING = 0; + private static final int DEFAULT_BOTTOM_PADDING = 0; + private final int mTopPadding; + private final int mBottomPadding; + private boolean mPaddingApplied = false; + + public PreferenceFrameLayout(Context context) { + this(context, null); + } + + public PreferenceFrameLayout(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.preferenceFrameLayoutStyle); + } + + public PreferenceFrameLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.PreferenceFrameLayout, defStyle, 0); + + mTopPadding = (int) a.getDimension( + com.android.internal.R.styleable.PreferenceFrameLayout_topPadding, + DEFAULT_TOP_PADDING); + mBottomPadding = (int) a.getDimension( + com.android.internal.R.styleable.PreferenceFrameLayout_bottomPadding, + DEFAULT_BOTTOM_PADDING); + + a.recycle(); + } + + @Override + public void addView(View child) { + int topPadding = getPaddingTop(); + int bottomPadding = getPaddingBottom(); + // Check on the id of the child before adding it. + if (child != null && child.getId() != com.android.internal.R.id.default_preference_layout) { + // Add the padding to the view group after determining if the padding already exists. + if (!mPaddingApplied) { + topPadding += mTopPadding; + bottomPadding += mBottomPadding; + mPaddingApplied = true; + } + } else { + if (mPaddingApplied) { + topPadding -= mTopPadding; + bottomPadding -= mBottomPadding; + mPaddingApplied = false; + } + } + int previousTop = getPaddingTop(); + int previousBottom = getPaddingBottom(); + if (previousTop != topPadding || previousBottom != bottomPadding) { + setPadding(getPaddingLeft(), topPadding, getPaddingRight(), bottomPadding); + } + super.addView(child); + } +}
\ No newline at end of file diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 2d2f205..d1ca0c9 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -5669,6 +5669,13 @@ public final class ContactsContract { * Type: INTEGER (boolean) */ public static final String FAVORITES = "favorites"; + + /** + * The "read-only" flag: "0" by default, "1" if the row cannot be modified or + * deleted except by a sync adapter. See {@link ContactsContract#CALLER_IS_SYNCADAPTER}. + * <P>Type: INTEGER</P> + */ + public static final String GROUP_IS_READ_ONLY = "group_is_read_only"; } /** diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java index ff769ad..683e603 100644 --- a/core/java/android/provider/Downloads.java +++ b/core/java/android/provider/Downloads.java @@ -382,6 +382,27 @@ public final class Downloads { */ public static final String COLUMN_ERROR_MSG = "errorMsg"; + /** + * This column stores the source of the last update to this row. + * This column is only for internal use. + * Valid values are indicated by LAST_UPDATESRC_* constants. + * <P>Type: INT</P> + */ + public static final String COLUMN_LAST_UPDATESRC = "lastUpdateSrc"; + + /** + * default value for {@link #COLUMN_LAST_UPDATESRC}. + * This value is used when this column's value is not relevant. + */ + public static final int LAST_UPDATESRC_NOT_RELEVANT = 0; + + /** + * One of the values taken by {@link #COLUMN_LAST_UPDATESRC}. + * This value is used when the update is NOT to be relayed to the DownloadService + * (and thus spare DownloadService from scanning the database when this change occurs) + */ + public static final int LAST_UPDATESRC_DONT_NOTIFY_DOWNLOADSVC = 1; + /* * Lists the destinations that an application can specify for a download. */ diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index f111ef2..fb4bed7 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -53,6 +53,13 @@ public final class MediaStore { private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/"; + /** + * Broadcast Action: A broadcast to indicate the end of an MTP session with the host. + * This broadcast is only sent if MTP activity has modified the media database during the + * most recent MTP session. + */ + public static final String ACTION_MTP_SESSION_END = "android.provider.action.MTP_SESSION_END"; + /** * Activity Action: Launch a music player. * The activity should be able to play, browse, or manipulate music files stored on the device. diff --git a/core/java/android/provider/Mtp.java b/core/java/android/provider/Ptp.java index 78110ef..2c54370 100644 --- a/core/java/android/provider/Mtp.java +++ b/core/java/android/provider/Ptp.java @@ -22,28 +22,20 @@ import android.util.Log; /** - * The MTP provider supports accessing content on MTP and PTP devices. + * The PTP provider supports accessing content on PTP devices. * @hide */ -public final class Mtp +public final class Ptp { - private final static String TAG = "Mtp"; + private final static String TAG = "Ptp"; - public static final String AUTHORITY = "mtp"; + public static final String AUTHORITY = "ptp"; private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/"; private static final String CONTENT_AUTHORITY_DEVICE_SLASH = "content://" + AUTHORITY + "/device/"; - - /** - * Broadcast Action: A broadcast to indicate the end of an MTP session with the host. - * This broadcast is only sent if MTP activity has modified the media database during the - * most recent MTP session - */ - public static final String ACTION_MTP_SESSION_END = "android.provider.action.MTP_SESSION_END"; - /** - * Contains list of all MTP/PTP devices + * Contains list of all PTP devices */ public static final class Device implements BaseColumns { @@ -67,7 +59,7 @@ public final class Mtp } /** - * Contains list of storage units for an MTP/PTP device + * Contains list of storage units for an PTP device */ public static final class Storage implements BaseColumns { @@ -93,7 +85,7 @@ public final class Mtp } /** - * Contains list of objects on an MTP/PTP device + * Contains list of objects on an PTP device */ public static final class Object implements BaseColumns { @@ -133,7 +125,7 @@ public final class Mtp /** * The following columns correspond to the fields in the ObjectInfo dataset - * as described in the MTP specification. + * as described in the PTP specification. */ /** @@ -144,7 +136,7 @@ public final class Mtp /** * The object's format. Can be one of the FORMAT_* symbols below, - * or any of the valid MTP object formats as defined in the MTP specification. + * or any of the valid PTP object formats as defined in the PTP specification. * <P>Type: INTEGER</P> */ public static final String FORMAT = "format"; @@ -163,7 +155,7 @@ public final class Mtp /** * The object's thumbnail format. Can be one of the FORMAT_* symbols below, - * or any of the valid MTP object formats as defined in the MTP specification. + * or any of the valid PTP object formats as defined in the PTP specification. * <P>Type: INTEGER</P> */ public static final String THUMB_FORMAT = "thumb_format"; diff --git a/core/java/android/util/PrefixPrinter.java b/core/java/android/util/PrefixPrinter.java new file mode 100644 index 0000000..62f7da1 --- /dev/null +++ b/core/java/android/util/PrefixPrinter.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +/** + * PrefixPrinter is a Printer which prefixes all lines with a given + * prefix. + * + * @hide + */ +public class PrefixPrinter implements Printer { + private final Printer mPrinter; + private final String mPrefix; + + /** + * Creates a new PrefixPrinter. + * + * <p>If prefix is null or empty, the provided printer is returned, rather + * than making a prefixing printer. + */ + public static Printer create(Printer printer, String prefix) { + if (prefix == null || prefix.equals("")) { + return printer; + } + return new PrefixPrinter(printer, prefix); + } + + private PrefixPrinter(Printer printer, String prefix) { + mPrinter = printer; + mPrefix = prefix; + } + + public void println(String str) { + mPrinter.println(mPrefix + str); + } +} diff --git a/core/java/android/util/Singleton.java b/core/java/android/util/Singleton.java new file mode 100644 index 0000000..8a38bdb --- /dev/null +++ b/core/java/android/util/Singleton.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +/** + * Singleton helper class for lazily initialization. + * + * Modeled after frameworks/base/include/utils/Singleton.h + * + * @hide + */ +public abstract class Singleton<T> { + private T mInstance; + + protected abstract T create(); + + public final T get() { + synchronized (this) { + if (mInstance == null) { + mInstance = create(); + } + return mInstance; + } + } +} diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 60ca384..85ce5e1 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -158,18 +158,17 @@ public class TimeUtils { static private int printField(char[] formatStr, int amt, char suffix, int pos, boolean always, int zeropad) { if (always || amt > 0) { + final int startPos = pos; if ((always && zeropad >= 3) || amt > 99) { int dig = amt/100; formatStr[pos] = (char)(dig + '0'); pos++; - always = true; amt -= (dig*100); } - if ((always && zeropad >= 2) || amt > 9) { + if ((always && zeropad >= 2) || amt > 9 || startPos != pos) { int dig = amt/10; formatStr[pos] = (char)(dig + '0'); pos++; - always = true; amt -= (dig*10); } formatStr[pos] = (char)(amt + '0'); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index dd0f477..d964e2f 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -122,7 +122,7 @@ interface IWindowManager int getTrackballKeycodeState(int sw); int getDPadKeycodeState(int sw); InputChannel monitorInput(String inputChannelName); - + // Report whether the hardware supports the given keys; returns true if successful boolean hasKeys(in int[] keycodes, inout boolean[] keyExists); @@ -132,7 +132,14 @@ interface IWindowManager // For testing void setInTouchMode(boolean showFocus); - + + // For StrictMode flashing a red border on violations from the UI + // thread. The uid/pid is implicit from the Binder call, and the Window + // Manager uses that to determine whether or not the red border should + // actually be shown. (it will be ignored that pid doesn't have windows + // on screen) + void showStrictModeViolation(boolean on); + // These can only be called with the SET_ORIENTATION permission. /** * Change the current screen rotation, constants as per diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index e6eb46e..011ad77 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1627,6 +1627,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility static final int ACTIVATED = 0x40000000; /** + * Always allow a user to over-scroll this view, provided it is a + * view that can scroll. + * + * @see #getOverScrollMode() + * @see #setOverScrollMode(int) + */ + public static final int OVER_SCROLL_ALWAYS = 0; + + /** + * Allow a user to over-scroll this view only if the content is large + * enough to meaningfully scroll, provided it is a view that can scroll. + * + * @see #getOverScrollMode() + * @see #setOverScrollMode(int) + */ + public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; + + /** + * Never allow a user to over-scroll this view. + * + * @see #getOverScrollMode() + * @see #setOverScrollMode(int) + */ + public static final int OVER_SCROLL_NEVER = 2; + + /** + * Controls the over-scroll mode for this view. + * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)}, + * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}, + * and {@link #OVER_SCROLL_NEVER}. + */ + private int mOverScrollMode; + + /** * The parent this view is attached to. * {@hide} * @@ -2057,6 +2091,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility mResources = context != null ? context.getResources() : null; mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS); } /** @@ -2122,6 +2157,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY; + int overScrollMode = mOverScrollMode; final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); @@ -2327,9 +2363,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility }); } break; + case R.styleable.View_overScrollMode: + overScrollMode = a.getInt(attr, OVER_SCROLL_IF_CONTENT_SCROLLS); + break; } } + setOverScrollMode(overScrollMode); + if (background != null) { setBackgroundDrawable(background); } @@ -10131,6 +10172,128 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Scroll the view with standard behavior for scrolling beyond the normal + * content boundaries. Views that call this method should override + * {@link #onOverScrolled(int, int, boolean, boolean)} to respond to the + * results of an over-scroll operation. + * + * Views can use this method to handle any touch or fling-based scrolling. + * + * @param deltaX Change in X in pixels + * @param deltaY Change in Y in pixels + * @param scrollX Current X scroll value in pixels before applying deltaX + * @param scrollY Current Y scroll value in pixels before applying deltaY + * @param scrollRangeX Maximum content scroll range along the X axis + * @param scrollRangeY Maximum content scroll range along the Y axis + * @param maxOverScrollX Number of pixels to overscroll by in either direction + * along the X axis. + * @param maxOverScrollY Number of pixels to overscroll by in either direction + * along the Y axis. + * @param isTouchEvent true if this scroll operation is the result of a touch event. + * @return true if scrolling was clamped to an over-scroll boundary along either + * axis, false otherwise. + */ + protected boolean overScrollBy(int deltaX, int deltaY, + int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY, + int maxOverScrollX, int maxOverScrollY, + boolean isTouchEvent) { + final int overScrollMode = mOverScrollMode; + final boolean canScrollHorizontal = + computeHorizontalScrollRange() > computeHorizontalScrollExtent(); + final boolean canScrollVertical = + computeVerticalScrollRange() > computeVerticalScrollExtent(); + final boolean overScrollHorizontal = overScrollMode == OVER_SCROLL_ALWAYS || + (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal); + final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS || + (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical); + + int newScrollX = scrollX + deltaX; + if (!overScrollHorizontal) { + maxOverScrollX = 0; + } + + int newScrollY = scrollY + deltaY; + if (!overScrollVertical) { + maxOverScrollY = 0; + } + + // Clamp values if at the limits and record + final int left = -maxOverScrollX; + final int right = maxOverScrollX + scrollRangeX; + final int top = -maxOverScrollY; + final int bottom = maxOverScrollY + scrollRangeY; + + boolean clampedX = false; + if (newScrollX > right) { + newScrollX = right; + clampedX = true; + } else if (newScrollX < left) { + newScrollX = left; + clampedX = true; + } + + boolean clampedY = false; + if (newScrollY > bottom) { + newScrollY = bottom; + clampedY = true; + } else if (newScrollY < top) { + newScrollY = top; + clampedY = true; + } + + onOverScrolled(newScrollX, newScrollY, clampedX, clampedY); + + return clampedX || clampedY; + } + + /** + * Called by {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)} to + * respond to the results of an over-scroll operation. + * + * @param scrollX New X scroll value in pixels + * @param scrollY New Y scroll value in pixels + * @param clampedX True if scrollX was clamped to an over-scroll boundary + * @param clampedY True if scrollY was clamped to an over-scroll boundary + */ + protected void onOverScrolled(int scrollX, int scrollY, + boolean clampedX, boolean clampedY) { + // Intentionally empty. + } + + /** + * Returns the over-scroll mode for this view. The result will be + * one of {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS} + * (allow over-scrolling only if the view content is larger than the container), + * or {@link #OVER_SCROLL_NEVER}. + * + * @return This view's over-scroll mode. + */ + public int getOverScrollMode() { + return mOverScrollMode; + } + + /** + * Set the over-scroll mode for this view. Valid over-scroll modes are + * {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS} + * (allow over-scrolling only if the view content is larger than the container), + * or {@link #OVER_SCROLL_NEVER}. + * + * Setting the over-scroll mode of a view will have an effect only if the + * view is capable of scrolling. + * + * @param overScrollMode The new over-scroll mode for this view. + */ + public void setOverScrollMode(int overScrollMode) { + if (overScrollMode != OVER_SCROLL_ALWAYS && + overScrollMode != OVER_SCROLL_IF_CONTENT_SCROLLS && + overScrollMode != OVER_SCROLL_NEVER) { + throw new IllegalArgumentException("Invalid overscroll mode " + overScrollMode); + } + mOverScrollMode = overScrollMode; + } + + /** * A MeasureSpec encapsulates the layout requirements passed from parent to child. * Each MeasureSpec represents a requirement for either the width or the height. * A MeasureSpec is comprised of a size and a mode. There are three possible diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 85981d2..bb85894 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -144,7 +144,7 @@ public class ViewConfiguration { /** * Maximum velocity to initiate a fling, as measured in pixels per second */ - private static final int MAXIMUM_FLING_VELOCITY = 4000; + private static final int MAXIMUM_FLING_VELOCITY = 8000; /** * The maximum size of View's drawing cache, expressed in bytes. This size @@ -158,6 +158,16 @@ public class ViewConfiguration { */ private static float SCROLL_FRICTION = 0.015f; + /** + * Max distance to overscroll for edge effects + */ + private static final int OVERSCROLL_DISTANCE = 0; + + /** + * Max distance to overfling for edge effects + */ + private static final int OVERFLING_DISTANCE = 4; + private final int mEdgeSlop; private final int mFadingEdgeLength; private final int mMinimumFlingVelocity; @@ -168,6 +178,8 @@ public class ViewConfiguration { private final int mDoubleTapSlop; private final int mWindowTouchSlop; private final int mMaximumDrawingCacheSize; + private final int mOverscrollDistance; + private final int mOverflingDistance; private static final SparseArray<ViewConfiguration> sConfigurations = new SparseArray<ViewConfiguration>(2); @@ -188,6 +200,8 @@ public class ViewConfiguration { mWindowTouchSlop = WINDOW_TOUCH_SLOP; //noinspection deprecation mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE; + mOverscrollDistance = OVERSCROLL_DISTANCE; + mOverflingDistance = OVERFLING_DISTANCE; } /** @@ -216,6 +230,9 @@ public class ViewConfiguration { // Size of the screen in bytes, in ARGB_8888 format mMaximumDrawingCacheSize = 4 * metrics.widthPixels * metrics.heightPixels; + + mOverscrollDistance = (int) (density * OVERSCROLL_DISTANCE + 0.5f); + mOverflingDistance = (int) (density * OVERFLING_DISTANCE + 0.5f); } /** @@ -473,6 +490,20 @@ public class ViewConfiguration { } /** + * @return The maximum distance a View should overscroll by when showing edge effects. + */ + public int getScaledOverscrollDistance() { + return mOverscrollDistance; + } + + /** + * @return The maximum distance a View should overfling by when showing edge effects. + */ + public int getScaledOverflingDistance() { + return mOverflingDistance; + } + + /** * The amount of time that the zoom controls should be * displayed on the screen expressed in milliseconds. * diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index fe55f34..9bc1c22 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -47,7 +47,10 @@ import com.android.internal.view.InputBindResult; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -1275,10 +1278,10 @@ public final class InputMethodManager { } } } - + /** - * Force switch to a new input method component. This can only be called - * from the currently active input method, as validated by the given token. + * Force switch to a new input method component. This can only be called + * from an application or a service which has a token of the currently active input method. * @param token Supplies the identifying token given to an input method * when it was started, which allows it to perform this operation on * itself. @@ -1291,7 +1294,24 @@ public final class InputMethodManager { throw new RuntimeException(e); } } - + + /** + * Force switch to a new input method and subtype. This can only be called + * from an application or a service which has a token of the currently active input method. + * @param token Supplies the identifying token given to an input method + * when it was started, which allows it to perform this operation on + * itself. + * @param id The unique identifier for the new input method to be switched to. + * @param subtype The new subtype of the new input method to be switched to. + */ + public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { + try { + mService.setInputMethodAndSubtype(token, id, subtype); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + /** * Close/hide the input method's soft input area, so the user no longer * sees it or can interact with it. This can only be called @@ -1452,6 +1472,38 @@ public final class InputMethodManager { } } + public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() { + synchronized (mH) { + HashMap<InputMethodInfo, List<InputMethodSubtype>> ret = + new HashMap<InputMethodInfo, List<InputMethodSubtype>>(); + try { + // TODO: We should change the return type from List<Object> to List<Parcelable> + List<Object> info = mService.getShortcutInputMethodsAndSubtypes(); + // "info" has imi1, subtype1, subtype2, imi2, subtype2, imi3, subtype3..in the list + ArrayList<InputMethodSubtype> subtypes = null; + final int N = info.size(); + if (info != null && N > 0) { + for (int i = 0; i < N; ++i) { + Object o = info.get(i); + if (o instanceof InputMethodInfo) { + if (ret.containsKey(o)) { + Log.e(TAG, "IMI list already contains the same InputMethod."); + break; + } + subtypes = new ArrayList<InputMethodSubtype>(); + ret.put((InputMethodInfo)o, subtypes); + } else if (subtypes != null && o instanceof InputMethodSubtype) { + subtypes.add((InputMethodSubtype)o); + } + } + } + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + } + return ret; + } + } + public boolean switchToLastInputMethod(IBinder imeToken) { synchronized (mH) { try { diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index 61a30ab..9568e4f 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -780,50 +780,11 @@ class BrowserFrame extends Handler { if (cacheMode == WebSettings.LOAD_NORMAL) { cacheMode = WebSettings.LOAD_NO_CACHE; } - if (mSettings.getSavePassword() && hasPasswordField()) { - try { - if (DebugFlags.BROWSER_FRAME) { - Assert.assertNotNull(mCallbackProxy.getBackForwardList() - .getCurrentItem()); - } - WebAddress uri = new WebAddress(mCallbackProxy - .getBackForwardList().getCurrentItem().getUrl()); - String schemePlusHost = uri.getScheme() + uri.getHost(); - String[] ret = getUsernamePassword(); - // Has the user entered a username/password pair and is - // there some POST data - if (ret != null && postData != null && - ret[0].length() > 0 && ret[1].length() > 0) { - // Check to see if the username & password appear in - // the post data (there could be another form on the - // page and that was posted instead. - String postString = new String(postData); - if (postString.contains(URLEncoder.encode(ret[0])) && - postString.contains(URLEncoder.encode(ret[1]))) { - String[] saved = mDatabase.getUsernamePassword( - schemePlusHost); - if (saved != null) { - // null username implies that user has chosen not to - // save password - if (saved[0] != null) { - // non-null username implies that user has - // chosen to save password, so update the - // recorded password - mDatabase.setUsernamePassword( - schemePlusHost, ret[0], ret[1]); - } - } else { - // CallbackProxy will handle creating the resume - // message - mCallbackProxy.onSavePassword(schemePlusHost, ret[0], - ret[1], null); - } - } - } - } catch (ParseException ex) { - // if it is bad uri, don't save its password - } - + String[] ret = getUsernamePassword(); + if (ret != null) { + String domUsername = ret[0]; + String domPassword = ret[1]; + maybeSavePassword(postData, domUsername, domPassword); } } @@ -874,6 +835,68 @@ class BrowserFrame extends Handler { return !synchronous ? loadListener : null; } + /** + * If this looks like a POST request (form submission) containing a username + * and password, give the user the option of saving them. Will either do + * nothing, or block until the UI interaction is complete. + * + * Called by startLoadingResource when using the Apache HTTP stack. + * Called directly by WebKit when using the Chrome HTTP stack. + * + * @param postData The data about to be sent as the body of a POST request. + * @param username The username entered by the user (sniffed from the DOM). + * @param password The password entered by the user (sniffed from the DOM). + */ + private void maybeSavePassword( + byte[] postData, String username, String password) { + if (postData == null + || username == null || username.isEmpty() + || password == null || password.isEmpty()) { + return; // No password to save. + } + + if (!mSettings.getSavePassword()) { + return; // User doesn't want to save passwords. + } + + try { + if (DebugFlags.BROWSER_FRAME) { + Assert.assertNotNull(mCallbackProxy.getBackForwardList() + .getCurrentItem()); + } + WebAddress uri = new WebAddress(mCallbackProxy + .getBackForwardList().getCurrentItem().getUrl()); + String schemePlusHost = uri.getScheme() + uri.getHost(); + // Check to see if the username & password appear in + // the post data (there could be another form on the + // page and that was posted instead. + String postString = new String(postData); + if (postString.contains(URLEncoder.encode(username)) && + postString.contains(URLEncoder.encode(password))) { + String[] saved = mDatabase.getUsernamePassword( + schemePlusHost); + if (saved != null) { + // null username implies that user has chosen not to + // save password + if (saved[0] != null) { + // non-null username implies that user has + // chosen to save password, so update the + // recorded password + mDatabase.setUsernamePassword( + schemePlusHost, username, password); + } + } else { + // CallbackProxy will handle creating the resume + // message + mCallbackProxy.onSavePassword(schemePlusHost, username, + password, null); + } + } + } catch (ParseException ex) { + // if it is bad uri, don't save its password + } + } + // Called by jni from the chrome network stack. private WebResourceResponse shouldInterceptRequest(String url) { InputStream androidResource = inputStreamForAndroidResource(url); diff --git a/core/java/android/webkit/OverScrollGlow.java b/core/java/android/webkit/OverScrollGlow.java new file mode 100644 index 0000000..53600f6 --- /dev/null +++ b/core/java/android/webkit/OverScrollGlow.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.webkit; + +import com.android.internal.R; + +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.EdgeGlow; + +/** + * This class manages the edge glow effect when a WebView is flung or pulled beyond the edges. + * @hide + */ +public class OverScrollGlow { + private WebView mHostView; + + private EdgeGlow mEdgeGlowTop; + private EdgeGlow mEdgeGlowBottom; + private EdgeGlow mEdgeGlowLeft; + private EdgeGlow mEdgeGlowRight; + + private int mOverScrollDeltaX; + private int mOverScrollDeltaY; + + public OverScrollGlow(WebView host) { + mHostView = host; + final Resources res = host.getContext().getResources(); + final Drawable edge = res.getDrawable(R.drawable.overscroll_edge); + final Drawable glow = res.getDrawable(R.drawable.overscroll_glow); + mEdgeGlowTop = new EdgeGlow(edge, glow); + mEdgeGlowBottom = new EdgeGlow(edge, glow); + mEdgeGlowLeft = new EdgeGlow(edge, glow); + mEdgeGlowRight = new EdgeGlow(edge, glow); + } + + /** + * Pull leftover touch scroll distance into one of the edge glows as appropriate. + * + * @param x Current X scroll offset + * @param y Current Y scroll offset + * @param oldX Old X scroll offset + * @param oldY Old Y scroll offset + * @param maxX Maximum range for horizontal scrolling + * @param maxY Maximum range for vertical scrolling + */ + public void pullGlow(int x, int y, int oldX, int oldY, int maxX, int maxY) { + // Only show overscroll bars if there was no movement in any direction + // as a result of scrolling. + if (oldX == mHostView.getScrollX() && oldY == mHostView.getScrollY()) { + // Don't show left/right glows if we fit the whole content. + // Also don't show if there was vertical movement. + if (maxX > 0) { + final int pulledToX = oldX + mOverScrollDeltaX; + if (pulledToX < 0) { + mEdgeGlowLeft.onPull((float) mOverScrollDeltaX / mHostView.getWidth()); + if (!mEdgeGlowRight.isFinished()) { + mEdgeGlowRight.onRelease(); + } + } else if (pulledToX > maxX) { + mEdgeGlowRight.onPull((float) mOverScrollDeltaX / mHostView.getWidth()); + if (!mEdgeGlowLeft.isFinished()) { + mEdgeGlowLeft.onRelease(); + } + } + mOverScrollDeltaX = 0; + } + + if (maxY > 0 || mHostView.getOverScrollMode() == View.OVER_SCROLL_ALWAYS) { + final int pulledToY = oldY + mOverScrollDeltaY; + if (pulledToY < 0) { + mEdgeGlowTop.onPull((float) mOverScrollDeltaY / mHostView.getHeight()); + if (!mEdgeGlowBottom.isFinished()) { + mEdgeGlowBottom.onRelease(); + } + } else if (pulledToY > maxY) { + mEdgeGlowBottom.onPull((float) mOverScrollDeltaY / mHostView.getHeight()); + if (!mEdgeGlowTop.isFinished()) { + mEdgeGlowTop.onRelease(); + } + } + mOverScrollDeltaY = 0; + } + } + } + + /** + * Set touch delta values indicating the current amount of overscroll. + * + * @param deltaX + * @param deltaY + */ + public void setOverScrollDeltas(int deltaX, int deltaY) { + mOverScrollDeltaX = deltaX; + mOverScrollDeltaY = deltaY; + } + + /** + * Absorb leftover fling velocity into one of the edge glows as appropriate. + * + * @param x Current X scroll offset + * @param y Current Y scroll offset + * @param oldX Old X scroll offset + * @param oldY Old Y scroll offset + * @param rangeX Maximum range for horizontal scrolling + * @param rangeY Maximum range for vertical scrolling + */ + public void absorbGlow(int x, int y, int oldX, int oldY, int rangeX, int rangeY) { + if (rangeY > 0 || mHostView.getOverScrollMode() == View.OVER_SCROLL_ALWAYS) { + if (y < 0 && oldY >= 0) { + mEdgeGlowTop.onAbsorb((int) mHostView.mScroller.getCurrVelocity()); + if (!mEdgeGlowBottom.isFinished()) { + mEdgeGlowBottom.onRelease(); + } + } else if (y > rangeY && oldY <= rangeY) { + mEdgeGlowBottom.onAbsorb((int) mHostView.mScroller.getCurrVelocity()); + if (!mEdgeGlowTop.isFinished()) { + mEdgeGlowTop.onRelease(); + } + } + } + + if (rangeX > 0) { + if (x < 0 && oldX >= 0) { + mEdgeGlowLeft.onAbsorb((int) mHostView.mScroller.getCurrVelocity()); + if (!mEdgeGlowRight.isFinished()) { + mEdgeGlowRight.onRelease(); + } + } else if (x > rangeX && oldX <= rangeX) { + mEdgeGlowRight.onAbsorb((int) mHostView.mScroller.getCurrVelocity()); + if (!mEdgeGlowLeft.isFinished()) { + mEdgeGlowLeft.onRelease(); + } + } + } + } + + /** + * Draw the glow effect along the sides of the widget. mEdgeGlow* must be non-null. + * + * @param canvas Canvas to draw into, transformed into view coordinates. + * @return true if glow effects are still animating and the view should invalidate again. + */ + public boolean drawEdgeGlows(Canvas canvas) { + final int scrollX = mHostView.getScrollX(); + final int scrollY = mHostView.getScrollY(); + final int width = mHostView.getWidth(); + int height = mHostView.getHeight(); + + boolean invalidateForGlow = false; + if (!mEdgeGlowTop.isFinished()) { + final int restoreCount = canvas.save(); + + canvas.translate(-width / 2 + scrollX, Math.min(0, scrollY)); + mEdgeGlowTop.setSize(width * 2, height); + invalidateForGlow |= mEdgeGlowTop.draw(canvas); + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowBottom.isFinished()) { + final int restoreCount = canvas.save(); + + canvas.translate(-width / 2 + scrollX, + Math.max(mHostView.computeMaxScrollY(), scrollY) + height); + canvas.rotate(180, width, 0); + mEdgeGlowBottom.setSize(width * 2, height); + invalidateForGlow |= mEdgeGlowBottom.draw(canvas); + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowLeft.isFinished()) { + final int restoreCount = canvas.save(); + + canvas.rotate(270); + canvas.translate(-height * 1.5f - scrollY, Math.min(0, scrollX)); + mEdgeGlowLeft.setSize(height * 2, width); + invalidateForGlow |= mEdgeGlowLeft.draw(canvas); + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowRight.isFinished()) { + final int restoreCount = canvas.save(); + + canvas.rotate(90); + canvas.translate(-height / 2 + scrollY, + -(Math.max(mHostView.computeMaxScrollX(), scrollX) + width)); + mEdgeGlowRight.setSize(height * 2, width); + invalidateForGlow |= mEdgeGlowRight.draw(canvas); + canvas.restoreToCount(restoreCount); + } + return invalidateForGlow; + } + + /** + * @return True if any glow is still animating + */ + public boolean isAnimating() { + return (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished() || + !mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished()); + } + + /** + * Release all glows from any touch pulls in progress. + */ + public void releaseAll() { + mEdgeGlowTop.onRelease(); + mEdgeGlowBottom.onRelease(); + mEdgeGlowLeft.onRelease(); + mEdgeGlowRight.onRelease(); + } +} diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index b2ba7e2..755366c 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -330,4 +330,14 @@ public class WebChromeClient { */ public void setInstallableWebApp() { } + /** + * Tell the client that the page being viewed has an autofillable + * form and the user would like to set a profile up. + * @param msg A Message to send once the user has successfully + * set up a profile and to inform the WebTextView it should + * now autofill using that new profile. + * @hide + */ + public void setupAutoFill(Message msg) { } + } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index f4caa74..2e69d99 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -268,6 +268,8 @@ public class WebSettings { private AutoFillProfile mAutoFillProfile; + private boolean mUseWebViewBackgroundForOverscroll = true; + // private WebSettings, not accessible by the host activity static private int mDoubleTapToastCount = 3; @@ -631,6 +633,23 @@ public class WebSettings { } /** + * Set whether the WebView uses its background for over scroll background. + * If true, it will use the WebView's background. If false, it will use an + * internal pattern. Default is true. + */ + public void setUseWebViewBackgroundForOverscrollBackground(boolean view) { + mUseWebViewBackgroundForOverscroll = view; + } + + /** + * Returns true if this WebView uses WebView's background instead of + * internal pattern for over scroll background. + */ + public boolean getUseWebViewBackgroundForOverscrollBackground() { + return mUseWebViewBackgroundForOverscroll; + } + + /** * Store whether the WebView is saving form data. */ public void setSaveFormData(boolean save) { @@ -1626,6 +1645,13 @@ public class WebSettings { } } + /** + * @hide + */ + public synchronized AutoFillProfile getAutoFillProfile() { + return mAutoFillProfile; + } + int getDoubleTapToastCount() { return mDoubleTapToastCount; } diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java index 1caa707..e1a5c2d 100644 --- a/core/java/android/webkit/WebTextView.java +++ b/core/java/android/webkit/WebTextView.java @@ -26,9 +26,14 @@ import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Message; +import android.text.BoringLayout.Metrics; +import android.text.DynamicLayout; import android.text.Editable; import android.text.InputFilter; import android.text.Layout; +import android.text.Layout.Alignment; import android.text.Selection; import android.text.Spannable; import android.text.TextPaint; @@ -126,6 +131,7 @@ import junit.framework.Assert; private boolean mAutoFillable; // Is this textview part of an autofillable form? private int mQueryId; + private boolean mAutoFillProfileIsSet; // Types used with setType. Keep in sync with CachedInput.h private static final int NORMAL_TEXT_FIELD = 0; @@ -137,6 +143,9 @@ import junit.framework.Assert; private static final int TELEPHONE = 6; private static final int URL = 7; + private static final int AUTOFILL_FORM = 100; + private Handler mHandler; + /** * Create a new WebTextView. * @param context The Context for this WebTextView. @@ -160,6 +169,18 @@ import junit.framework.Assert; setTextColor(DebugFlags.DRAW_WEBTEXTVIEW ? Color.RED : Color.BLACK); // This helps to align the text better with the text in the web page. setIncludeFontPadding(false); + + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AUTOFILL_FORM: + mWebView.autoFillForm(mQueryId); + break; + } + } + }; + } public void setAutoFillable(int queryId) { @@ -359,6 +380,68 @@ import junit.framework.Assert; } } + @Override + protected void makeNewLayout(int w, int hintWidth, Metrics boring, + Metrics hintBoring, int ellipsisWidth, boolean bringIntoView) { + // Necessary to get a Layout to work with, and to do the other work that + // makeNewLayout does. + super.makeNewLayout(w, hintWidth, boring, hintBoring, ellipsisWidth, + bringIntoView); + + // For fields that do not draw, create a layout which is altered so that + // the text lines up. + if (DebugFlags.DRAW_WEBTEXTVIEW || willNotDraw()) { + float lineHeight = -1; + if (mWebView != null) { + float height = mWebView.nativeFocusCandidateLineHeight(); + if (height != -1) { + lineHeight = height * mWebView.getScale(); + } + } + CharSequence text = getText(); + // Copy from the existing Layout. + mLayout = new WebTextViewLayout(text, text, getPaint(), w, + mLayout.getAlignment(), mLayout.getSpacingMultiplier(), + mLayout.getSpacingAdd(), false, null, ellipsisWidth, + lineHeight); + } + } + + /** + * Custom layout which figures out its line spacing. If -1 is passed in for + * the height, it will use the ascent and descent from the paint to + * determine the line spacing. Otherwise it will use the spacing provided. + */ + private static class WebTextViewLayout extends DynamicLayout { + private float mLineHeight; + private float mDifference; + public WebTextViewLayout(CharSequence base, CharSequence display, + TextPaint paint, + int width, Alignment align, + float spacingMult, float spacingAdd, + boolean includepad, + TextUtils.TruncateAt ellipsize, int ellipsizedWidth, + float lineHeight) { + super(base, display, paint, width, align, spacingMult, spacingAdd, + includepad, ellipsize, ellipsizedWidth); + float paintLineHeight = paint.descent() - paint.ascent(); + if (lineHeight == -1f) { + mLineHeight = paintLineHeight; + mDifference = 0f; + } else { + mLineHeight = lineHeight; + // Through trial and error, I found this calculation to improve + // the accuracy of line placement. + mDifference = (lineHeight - paintLineHeight) / 2; + } + } + + @Override + public int getLineTop(int line) { + return Math.round(mLineHeight * line - mDifference); + } + } + @Override public InputConnection onCreateInputConnection( EditorInfo outAttrs) { InputConnection connection = super.onCreateInputConnection(outAttrs); @@ -736,8 +819,17 @@ import junit.framework.Assert; if (id == 0 && position == 0) { // Blank out the text box while we wait for WebCore to fill the form. replaceText(""); - // Call a webview method to tell WebCore to autofill the form. - mWebView.autoFillForm(mQueryId); + WebSettings settings = mWebView.getSettings(); + if (mAutoFillProfileIsSet) { + // Call a webview method to tell WebCore to autofill the form. + mWebView.autoFillForm(mQueryId); + } else { + // There is no autofill profile setup yet and the user has + // elected to try and set one up. Call through to the + // embedder to action that. + mWebView.getWebChromeClient().setupAutoFill( + mHandler.obtainMessage(AUTOFILL_FORM)); + } } } }); @@ -1059,4 +1151,8 @@ import junit.framework.Assert; /* package */ void updateCachedTextfield() { mWebView.updateCachedTextfield(getText().toString()); } + + /* package */ void setAutoFillProfileIsSet(boolean autoFillProfileIsSet) { + mAutoFillProfileIsSet = autoFillProfileIsSet; + } } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 5bf2ad4..a0ee765 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -16,19 +16,25 @@ package android.webkit; +import com.android.internal.R; + import android.annotation.Widget; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.ClipboardManager; import android.content.Context; import android.content.DialogInterface; -import android.content.IntentFilter; import android.content.DialogInterface.OnCancelListener; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources; import android.database.DataSetObserver; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.CornerPathEffect; @@ -40,6 +46,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; +import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.net.Uri; import android.net.http.SslCertificate; @@ -81,13 +88,12 @@ import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.CheckedTextView; +import android.widget.EdgeGlow; import android.widget.LinearLayout; import android.widget.ListView; -import android.widget.Scroller; +import android.widget.OverScroller; import android.widget.Toast; -import junit.framework.Assert; - import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -102,6 +108,8 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import junit.framework.Assert; + /** * <p>A View that displays web pages. This class is the basis upon which you * can roll your own web browser or simply display some online content within your Activity. @@ -529,7 +537,13 @@ public class WebView extends AbsoluteLayout // time for the longest scroll animation private static final int MAX_DURATION = 750; // milliseconds private static final int SLIDE_TITLE_DURATION = 500; // milliseconds - private Scroller mScroller; + + // Used by OverScrollGlow + OverScroller mScroller; + + private boolean mInOverScrollMode = false; + private static Paint mOverScrollBackground; + private static Paint mOverScrollBorder; private boolean mWrapContent; private static final int MOTIONLESS_FALSE = 0; @@ -734,6 +748,20 @@ public class WebView extends AbsoluteLayout // variable to cache the above pattern in case accessibility is enabled. private Pattern mMatchAxsUrlParameterPattern; + /** + * Max distance to overscroll by in pixels. + * This how far content can be pulled beyond its normal bounds by the user. + */ + private int mOverscrollDistance; + + /** + * Max distance to overfling by in pixels. + * This is how far flinged content can move beyond the end of its normal bounds. + */ + private int mOverflingDistance; + + private OverScrollGlow mOverScrollGlow; + // Used to match key downs and key ups private boolean mGotKeyDown; @@ -906,9 +934,10 @@ public class WebView extends AbsoluteLayout mCallbackProxy = new CallbackProxy(context, this); mViewManager = new ViewManager(this); + L10nUtils.loadStrings(context); mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces); mDatabase = WebViewDatabase.getInstance(context); - mScroller = new Scroller(context); + mScroller = new OverScroller(context, null, 0, 0, false); //TODO Use OverScroller's flywheel mZoomManager = new ZoomManager(this, mCallbackProxy); /* The init method must follow the creation of certain member variables, @@ -922,7 +951,6 @@ public class WebView extends AbsoluteLayout startPrivateBrowsing(); } - L10nUtils.loadStrings(context); mAutoFillData = new WebViewCore.AutoFillData(); } @@ -1044,6 +1072,9 @@ public class WebView extends AbsoluteLayout // Compute the inverse of the density squared. DRAG_LAYER_INVERSE_DENSITY_SQUARED = 1 / (density * density); + + mOverscrollDistance = configuration.getScaledOverscrollDistance(); + mOverflingDistance = configuration.getScaledOverflingDistance(); } /** @@ -1066,6 +1097,18 @@ public class WebView extends AbsoluteLayout new TextToSpeech(getContext(), null)); } + @Override + public void setOverScrollMode(int mode) { + super.setOverScrollMode(mode); + if (mode != OVER_SCROLL_NEVER) { + if (mOverScrollGlow == null) { + mOverScrollGlow = new OverScrollGlow(this); + } + } else { + mOverScrollGlow = null; + } + } + /* package */void updateDefaultZoomDensity(int zoomDensity) { final float density = mContext.getResources().getDisplayMetrics().density * 100 / zoomDensity; @@ -1197,7 +1240,8 @@ public class WebView extends AbsoluteLayout * @hide */ public int getVisibleTitleHeight() { - return Math.max(getTitleHeight() - mScrollY, 0); + // need to restrict mScrollY due to over scroll + return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0); } /* @@ -1946,7 +1990,7 @@ public class WebView extends AbsoluteLayout } nativeClearCursor(); // start next trackball movement from page edge if (bottom) { - return pinScrollTo(mScrollX, computeVerticalScrollRange(), true, 0); + return pinScrollTo(mScrollX, computeRealVerticalScrollRange(), true, 0); } // Page down. int h = getHeight(); @@ -2171,13 +2215,15 @@ public class WebView extends AbsoluteLayout // Expects x in view coordinates int pinLocX(int x) { - return pinLoc(x, getViewWidth(), computeHorizontalScrollRange()); + if (mInOverScrollMode) return x; + return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange()); } // Expects y in view coordinates int pinLocY(int y) { + if (mInOverScrollMode) return y; return pinLoc(y, getViewHeightWithTitle(), - computeVerticalScrollRange() + getTitleHeight()); + computeRealVerticalScrollRange() + getTitleHeight()); } /** @@ -2412,7 +2458,7 @@ public class WebView extends AbsoluteLayout // Sets r to be our visible rectangle in content coordinates private void calcOurContentVisibleRect(Rect r) { calcOurVisibleRect(r); - // pin the rect to the bounds of the content + // since we might overscroll, pin the rect to the bounds of the content r.left = Math.max(viewToContentX(r.left), 0); // viewToContentY will remove the total height of the title bar. Add // the visible height back in to account for the fact that if the title @@ -2497,8 +2543,7 @@ public class WebView extends AbsoluteLayout return false; } - @Override - protected int computeHorizontalScrollRange() { + private int computeRealHorizontalScrollRange() { if (mDrawHistory) { return mHistoryWidth; } else if (mHorizontalScrollBarMode == SCROLLBAR_ALWAYSOFF @@ -2512,7 +2557,27 @@ public class WebView extends AbsoluteLayout } @Override - protected int computeVerticalScrollRange() { + protected int computeHorizontalScrollRange() { + int range = computeRealHorizontalScrollRange(); + + // Adjust reported range if overscrolled to compress the scroll bars + final int scrollX = mScrollX; + final int overscrollRight = computeMaxScrollX(); + if (scrollX < 0) { + range -= scrollX; + } else if (scrollX > overscrollRight) { + range += scrollX - overscrollRight; + } + + return range; + } + + @Override + protected int computeHorizontalScrollOffset() { + return Math.max(mScrollX, 0); + } + + private int computeRealVerticalScrollRange() { if (mDrawHistory) { return mHistoryHeight; } else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF @@ -2526,6 +2591,22 @@ public class WebView extends AbsoluteLayout } @Override + protected int computeVerticalScrollRange() { + int range = computeRealVerticalScrollRange(); + + // Adjust reported range if overscrolled to compress the scroll bars + final int scrollY = mScrollY; + final int overscrollBottom = computeMaxScrollY(); + if (scrollY < 0) { + range -= scrollY; + } else if (scrollY > overscrollBottom) { + range += scrollY - overscrollBottom; + } + + return range; + } + + @Override protected int computeVerticalScrollOffset() { return Math.max(mScrollY - getTitleHeight(), 0); } @@ -2540,10 +2621,39 @@ public class WebView extends AbsoluteLayout protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) { + if (mScrollY < 0) { + t -= mScrollY; + } scrollBar.setBounds(l, t + getVisibleTitleHeight(), r, b); scrollBar.draw(canvas); } + @Override + protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, + boolean clampedY) { + mInOverScrollMode = false; + int maxX = computeMaxScrollX(); + int maxY = computeMaxScrollY(); + if (maxX == 0) { + // do not over scroll x if the page just fits the screen + scrollX = pinLocX(scrollX); + } else if (scrollX < 0 || scrollX > maxX) { + mInOverScrollMode = true; + } + if (scrollY < 0 || scrollY > maxY) { + mInOverScrollMode = true; + } + + int oldX = mScrollX; + int oldY = mScrollY; + + super.scrollTo(scrollX, scrollY); + + if (mOverScrollGlow != null) { + mOverScrollGlow.pullGlow(mScrollX, mScrollY, oldX, oldY, maxX, maxY); + } + } + /** * Get the url for the current page. This is not always the same as the url * passed to WebViewClient.onPageStarted because although the load for @@ -2923,13 +3033,23 @@ public class WebView extends AbsoluteLayout if (mScroller.computeScrollOffset()) { int oldX = mScrollX; int oldY = mScrollY; - mScrollX = mScroller.getCurrX(); - mScrollY = mScroller.getCurrY(); - postInvalidate(); // So we draw again - if (oldX != mScrollX || oldY != mScrollY) { - onScrollChanged(mScrollX, mScrollY, oldX, oldY); - } else if (mScroller.getStartX() != mScrollX - || mScroller.getStartY() != mScrollY) { + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + invalidate(); // So we draw again + + if (!mScroller.isFinished()) { + final int rangeX = computeMaxScrollX(); + final int rangeY = computeMaxScrollY(); + overScrollBy(x - oldX, y - oldY, oldX, oldY, + rangeX, rangeY, + mOverflingDistance, mOverflingDistance, false); + + if (mOverScrollGlow != null) { + mOverScrollGlow.absorbGlow(x, y, oldX, oldY, rangeX, rangeY); + } + } else { + mScrollX = x; + mScrollY = y; abortAnimation(); mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); WebViewCore.resumePriority(); @@ -3436,6 +3556,40 @@ public class WebView extends AbsoluteLayout drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing); } + /** + * Draw the background when beyond bounds + * @param canvas Canvas to draw into + */ + private void drawOverScrollBackground(Canvas canvas) { + if (mOverScrollBackground == null) { + mOverScrollBackground = new Paint(); + Bitmap bm = BitmapFactory.decodeResource( + mContext.getResources(), + com.android.internal.R.drawable.status_bar_background); + mOverScrollBackground.setShader(new BitmapShader(bm, + Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)); + mOverScrollBorder = new Paint(); + mOverScrollBorder.setStyle(Paint.Style.STROKE); + mOverScrollBorder.setStrokeWidth(0); + mOverScrollBorder.setColor(0xffbbbbbb); + } + + int top = 0; + int right = computeRealHorizontalScrollRange(); + int bottom = top + computeRealVerticalScrollRange(); + // first draw the background and anchor to the top of the view + canvas.save(); + canvas.translate(mScrollX, mScrollY); + canvas.clipRect(-mScrollX, top - mScrollY, right - mScrollX, bottom + - mScrollY, Region.Op.DIFFERENCE); + canvas.drawPaint(mOverScrollBackground); + canvas.restore(); + // then draw the border + canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder); + // next clip the region for the content + canvas.clipRect(0, top, right, bottom); + } + @Override protected void onDraw(Canvas canvas) { // if mNativeClass is 0, the WebView has been destroyed. Do nothing. @@ -3452,6 +3606,10 @@ public class WebView extends AbsoluteLayout } int saveCount = canvas.save(); + if (mInOverScrollMode && !getSettings() + .getUseWebViewBackgroundForOverscrollBackground()) { + drawOverScrollBackground(canvas); + } if (mTitleBar != null) { canvas.translate(0, (int) mTitleBar.getHeight()); } @@ -3466,6 +3624,10 @@ public class WebView extends AbsoluteLayout } mWebViewCore.signalRepaintDone(); + if (mOverScrollGlow != null && mOverScrollGlow.drawEdgeGlows(canvas)) { + invalidate(); + } + // paint the highlight in the end if (!mTouchHighlightRegion.isEmpty()) { if (mTouchHightlightPaint == null) { @@ -3549,6 +3711,15 @@ public class WebView extends AbsoluteLayout * click action, look for a word under the click. If one is found, * animate the text selection into view. * FIXME: no animation code yet */ + return selectText(); + } + + /** + * Select the word at the last click point. + * + * @hide pending API council approval + */ + public boolean selectText() { int x = viewToContentX((int) mLastTouchX + mScrollX); int y = viewToContentY((int) mLastTouchY + mScrollY); setUpSelect(); @@ -3561,6 +3732,16 @@ public class WebView extends AbsoluteLayout return false; } + private int mOrientation = Configuration.ORIENTATION_UNDEFINED; + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + if (mSelectingText && mOrientation != newConfig.orientation) { + selectionDone(); + } + mOrientation = newConfig.orientation; + } + /** * Keep track of the Callback so we can end its ActionMode or remove its * titlebar. @@ -4042,10 +4223,20 @@ public class WebView extends AbsoluteLayout // Note that code inside the adapter click handler in WebTextView depends // on the AutoFill item being at the top of the drop down list. If you change // the order, make sure to do it there too! - pastEntries.add(getResources().getText( - com.android.internal.R.string.autofill_this_form).toString() + - " " + - mAutoFillData.getPreviewString()); + WebSettings settings = getSettings(); + if (settings != null && settings.getAutoFillProfile() != null) { + pastEntries.add(getResources().getText( + com.android.internal.R.string.autofill_this_form).toString() + + " " + + mAutoFillData.getPreviewString()); + mWebTextView.setAutoFillProfileIsSet(true); + } else { + // There is no autofill profile set up yet, so add an option that + // will invite the user to set their profile up. + pastEntries.add(getResources().getText( + com.android.internal.R.string.setup_autofill).toString()); + mWebTextView.setAutoFillProfileIsSet(false); + } } pastEntries.addAll(mDatabase.getFormData(mUrl, mName)); @@ -4688,12 +4879,14 @@ public class WebView extends AbsoluteLayout @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); - sendOurVisibleRect(); - // update WebKit if visible title bar height changed. The logic is same - // as getVisibleTitleHeight. - int titleHeight = getTitleHeight(); - if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) { - sendViewSizeZoom(false); + if (!mInOverScrollMode) { + sendOurVisibleRect(); + // update WebKit if visible title bar height changed. The logic is same + // as getVisibleTitleHeight. + int titleHeight = getTitleHeight(); + if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) { + sendViewSizeZoom(false); + } } } @@ -4820,7 +5013,7 @@ public class WebView extends AbsoluteLayout final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector(); - boolean skipScaleGesture = false; + boolean isScrollGesture = false; // Set to the mid-point of a two-finger gesture used to detect if the // user has touched a layer. float gestureX = x; @@ -4848,7 +5041,7 @@ public class WebView extends AbsoluteLayout } action = ev.getActionMasked(); if (dist < DRAG_LAYER_FINGER_DISTANCE) { - skipScaleGesture = true; + isScrollGesture = true; } else if (mTouchMode == TOUCH_DRAG_LAYER_MODE) { // Fingers moved too far apart while dragging, the user // might be trying to zoom. @@ -4857,9 +5050,13 @@ public class WebView extends AbsoluteLayout } } - // If the page disallows zoom, pass multi-pointer events to webkit. - if (!skipScaleGesture && ev.getPointerCount() > 1 - && (mZoomManager.isZoomScaleFixed() || mDeferMultitouch)) { + // If the page disallows zoom, pass multi-touch events to webkit. + // mDeferMultitouch is a hack for layout tests, where it is used to + // force passing multi-touch events to webkit. + // FIXME: always pass multi-touch events to webkit and remove everything + // related to mDeferMultitouch. + if (ev.getPointerCount() > 1 && + (mDeferMultitouch || (!isScrollGesture && mZoomManager.isZoomScaleFixed()))) { if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "passing " + ev.getPointerCount() + " points to webkit"); } @@ -4868,7 +5065,7 @@ public class WebView extends AbsoluteLayout } if (mZoomManager.supportsMultiTouchZoom() && ev.getPointerCount() > 1 && - mTouchMode != TOUCH_DRAG_LAYER_MODE && !skipScaleGesture) { + mTouchMode != TOUCH_DRAG_LAYER_MODE && !isScrollGesture) { if (!detector.isInProgress() && ev.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN) { // Insert a fake pointer down event in order to start @@ -5141,27 +5338,13 @@ public class WebView extends AbsoluteLayout deltaX = 0; deltaY = 0; - if (skipScaleGesture) { + if (isScrollGesture) { startScrollingLayer(gestureX, gestureY); } startDrag(); } // do pan - if (mTouchMode != TOUCH_DRAG_LAYER_MODE) { - int newScrollX = pinLocX(mScrollX + deltaX); - int newDeltaX = newScrollX - mScrollX; - if (deltaX != newDeltaX) { - deltaX = newDeltaX; - fDeltaX = (float) newDeltaX; - } - int newScrollY = pinLocY(mScrollY + deltaY); - int newDeltaY = newScrollY - mScrollY; - if (deltaY != newDeltaY) { - deltaY = newDeltaY; - fDeltaY = (float) newDeltaY; - } - } boolean done = false; boolean keepScrollBarsVisible = false; if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) { @@ -5351,6 +5534,12 @@ public class WebView extends AbsoluteLayout mHeldMotionless = MOTIONLESS_IGNORE; doFling(); break; + } else { + if (mScroller.springBack(mScrollX, mScrollY, 0, + computeMaxScrollX(), 0, + computeMaxScrollY())) { + invalidate(); + } } // redraw in high-quality, as we're done dragging mHeldMotionless = MOTIONLESS_TRUE; @@ -5371,6 +5560,8 @@ public class WebView extends AbsoluteLayout } case MotionEvent.ACTION_CANCEL: { if (mTouchMode == TOUCH_DRAG_MODE) { + mScroller.springBack(mScrollX, mScrollY, 0, + computeMaxScrollX(), 0, computeMaxScrollY()); invalidate(); } cancelWebCoreTouchEvent(contentX, contentY, false); @@ -5446,7 +5637,22 @@ public class WebView extends AbsoluteLayout } return; } - scrollBy(deltaX, deltaY); + + final int oldX = mScrollX; + final int oldY = mScrollY; + final int rangeX = computeMaxScrollX(); + final int rangeY = computeMaxScrollY(); + + if (mOverScrollGlow != null) { + mOverScrollGlow.setOverScrollDeltas(deltaX, deltaY); + } + + overScrollBy(deltaX, deltaY, oldX, oldY, + rangeX, rangeY, + mOverscrollDistance, mOverscrollDistance, true); + if (mOverScrollGlow != null && mOverScrollGlow.isAnimating()) { + invalidate(); + } } mZoomManager.keepZoomPickerVisible(); } @@ -5459,6 +5665,11 @@ public class WebView extends AbsoluteLayout mVelocityTracker.recycle(); mVelocityTracker = null; } + + // Release any pulled glows + if (mOverScrollGlow != null) { + mOverScrollGlow.releaseAll(); + } } private void cancelTouch() { @@ -5469,6 +5680,7 @@ public class WebView extends AbsoluteLayout mVelocityTracker.recycle(); mVelocityTracker = null; } + if (mTouchMode == TOUCH_DRAG_MODE || mTouchMode == TOUCH_DRAG_LAYER_MODE) { WebViewCore.resumePriority(); @@ -5780,12 +5992,20 @@ public class WebView extends AbsoluteLayout } } - private int computeMaxScrollX() { - return Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); + /** + * Compute the maximum horizontal scroll position. Used by {@link OverScrollGlow}. + * @return Maximum horizontal scroll position within real content + */ + int computeMaxScrollX() { + return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0); } - private int computeMaxScrollY() { - return Math.max(computeVerticalScrollRange() + getTitleHeight() + /** + * Compute the maximum vertical scroll position. Used by {@link OverScrollGlow}. + * @return Maximum vertical scroll position within real content + */ + int computeMaxScrollY() { + return Math.max(computeRealVerticalScrollRange() + getTitleHeight() - getViewHeightWithTitle(), 0); } @@ -5804,7 +6024,7 @@ public class WebView extends AbsoluteLayout public void flingScroll(int vx, int vy) { mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0, - computeMaxScrollY()); + computeMaxScrollY(), mOverflingDistance, mOverflingDistance); invalidate(); } @@ -5834,6 +6054,10 @@ public class WebView extends AbsoluteLayout if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) { WebViewCore.resumePriority(); WebViewCore.resumeUpdatePicture(mWebViewCore); + if (mScroller.springBack(mScrollX, mScrollY, 0, computeMaxScrollX(), + 0, computeMaxScrollY())) { + invalidate(); + } return; } float currentVelocity = mScroller.getCurrVelocity(); @@ -5860,13 +6084,37 @@ public class WebView extends AbsoluteLayout + " maxX=" + maxX + " maxY=" + maxY + " mScrollX=" + mScrollX + " mScrollY=" + mScrollY); } + + // Allow sloppy flings without overscrolling at the edges. + if ((mScrollX == 0 || mScrollX == maxX) && Math.abs(vx) < Math.abs(vy)) { + vx = 0; + } + if ((mScrollY == 0 || mScrollY == maxY) && Math.abs(vy) < Math.abs(vx)) { + vy = 0; + } + + if (mOverscrollDistance < mOverflingDistance) { + if (mScrollX == -mOverscrollDistance || mScrollX == maxX + mOverscrollDistance) { + vx = 0; + } + if (mScrollY == -mOverscrollDistance || mScrollY == maxY + mOverscrollDistance) { + vy = 0; + } + } + mLastVelX = vx; mLastVelY = vy; mLastVelocity = velocity; - mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY); + // no horizontal overscroll if the content just fits + mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY, + maxX == 0 ? 0 : mOverflingDistance, mOverflingDistance); + // Duration is calculated based on velocity. With range boundaries and overscroll + // we may not know how long the final animation will take. (Hence the deprecation + // warning on the call below.) It's not a big deal for scroll bars but if webcore + // resumes during this effect we will take a performance hit. See computeScroll; + // we resume webcore there when the animation is finished. final int time = mScroller.getDuration(); - mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_PRIORITY, time); awakenScrollBars(time); invalidate(); } @@ -6665,6 +6913,10 @@ public class WebView extends AbsoluteLayout case MotionEvent.ACTION_CANCEL: if (mDeferTouchMode == TOUCH_DRAG_MODE) { // no fling in defer process + mScroller.springBack(mScrollX, mScrollY, 0, + computeMaxScrollX(), 0, + computeMaxScrollY()); + invalidate(); WebViewCore.resumePriority(); WebViewCore.resumeUpdatePicture(mWebViewCore); } @@ -7461,6 +7713,7 @@ public class WebView extends AbsoluteLayout /* package */ native int nativeFocusCandidatePointer(); private native String nativeFocusCandidateText(); /* package */ native float nativeFocusCandidateTextSize(); + /* package */ native int nativeFocusCandidateLineHeight(); /** * Returns an integer corresponding to WebView.cpp::type. * See WebTextView.setType() diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 70cfee9..423a788 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -20,6 +20,7 @@ import com.android.internal.R; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; @@ -39,6 +40,7 @@ import android.util.LongSparseArray; import android.util.SparseBooleanArray; import android.util.StateSet; import android.view.ActionMode; +import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; @@ -52,7 +54,6 @@ import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.view.ContextMenu.ContextMenuInfo; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -138,6 +139,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te static final int TOUCH_MODE_FLING = 4; /** + * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end. + */ + static final int TOUCH_MODE_OVERSCROLL = 5; + + /** + * Indicates the view is being flung outside of normal content bounds + * and will spring back. + */ + static final int TOUCH_MODE_OVERFLING = 6; + + /** * Regular layout - usually an unsolicited layout from the view system */ static final int LAYOUT_NORMAL = 0; @@ -446,6 +458,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private ContextMenuInfo mContextMenuInfo = null; /** + * Maximum distance to record overscroll + */ + int mOverscrollMax; + + /** + * Content height divided by this is the overscroll limit. + */ + static final int OVERSCROLL_LIMIT_DIVISOR = 3; + + /** * Used to request a layout when we changed touch mode */ private static final int TOUCH_MODE_UNKNOWN = -1; @@ -548,6 +570,48 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private static final int INVALID_POINTER = -1; /** + * Maximum distance to overscroll by during edge effects + */ + int mOverscrollDistance; + + /** + * Maximum distance to overfling during edge effects + */ + int mOverflingDistance; + + // These two EdgeGlows are always set and used together. + // Checking one for null is as good as checking both. + + /** + * Tracks the state of the top edge glow. + */ + private EdgeGlow mEdgeGlowTop; + + /** + * Tracks the state of the bottom edge glow. + */ + private EdgeGlow mEdgeGlowBottom; + + /** + * An estimate of how many pixels are between the top of the list and + * the top of the first position in the adapter, based on the last time + * we saw it. Used to hint where to draw edge glows. + */ + private int mFirstPositionDistanceGuess; + + /** + * An estimate of how many pixels are between the bottom of the list and + * the bottom of the last position in the adapter, based on the last time + * we saw it. Used to hint where to draw edge glows. + */ + private int mLastPositionDistanceGuess; + + /** + * Used for determining when to cancel out of overscroll. + */ + private int mDirection = 0; + + /** * Interface definition for a callback to be invoked when the list or grid * has been scrolled. */ @@ -690,9 +754,29 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mOverscrollDistance = configuration.getScaledOverscrollDistance(); + mOverflingDistance = configuration.getScaledOverflingDistance(); + mDensityScale = getContext().getResources().getDisplayMetrics().density; } + @Override + public void setOverScrollMode(int mode) { + if (mode != OVER_SCROLL_NEVER) { + if (mEdgeGlowTop == null) { + final Resources res = getContext().getResources(); + final Drawable edge = res.getDrawable(R.drawable.overscroll_edge); + final Drawable glow = res.getDrawable(R.drawable.overscroll_glow); + mEdgeGlowTop = new EdgeGlow(edge, glow); + mEdgeGlowBottom = new EdgeGlow(edge, glow); + } + } else { + mEdgeGlowTop = null; + mEdgeGlowBottom = null; + } + super.setOverScrollMode(mode); + } + /** * {@inheritDoc} */ @@ -1003,6 +1087,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * @return true if all list content currently fits within the view boundaries + */ + private boolean contentFits() { + final int childCount = getChildCount(); + if (childCount != mItemCount) { + return false; + } + + return getChildAt(0).getTop() >= 0 && getChildAt(childCount - 1).getBottom() <= mBottom; + } + + /** * Enables fast scrolling by letting the user quickly scroll through lists by * dragging the fast scroll thumb. The adapter attached to the list may want * to implement {@link SectionIndexer} if it wishes to display alphabet preview and @@ -1540,6 +1636,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te int result; if (mSmoothScrollbarEnabled) { result = Math.max(mItemCount * 100, 0); + if (mScrollY != 0) { + // Compensate for overscroll + result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100)); + } } else { result = mItemCount; } @@ -1612,6 +1712,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te layoutChildren(); mInLayout = false; + + mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; } /** @@ -2126,6 +2228,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mFlingRunnable.endFling(); if (mScrollY != 0) { mScrollY = 0; + finishGlows(); invalidate(); } } @@ -2445,9 +2548,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // Check if we have moved far enough that it looks more like a // scroll than a tap final int distance = Math.abs(deltaY); - if (distance > mTouchSlop) { + final boolean overscroll = mScrollY != 0; + if (overscroll || distance > mTouchSlop) { createScrollingCache(); - mTouchMode = TOUCH_MODE_SCROLL; + mTouchMode = overscroll ? TOUCH_MODE_OVERSCROLL : TOUCH_MODE_SCROLL; mMotionCorrection = deltaY; final Handler handler = getHandler(); // Handler should not be null unless the AbsListView is not attached to a @@ -2483,6 +2587,19 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // touch mode). Force an initial layout to get rid of the selection. layoutChildren(); } + } else { + int touchMode = mTouchMode; + if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) { + if (mFlingRunnable != null) { + mFlingRunnable.endFling(); + } + + if (mScrollY != 0) { + mScrollY = 0; + finishGlows(); + invalidate(); + } + } } } @@ -2513,49 +2630,63 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { - mActivePointerId = ev.getPointerId(0); - final int x = (int) ev.getX(); - final int y = (int) ev.getY(); - int motionPosition = pointToPosition(x, y); - if (!mDataChanged) { - if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0) - && (getAdapter().isEnabled(motionPosition))) { - // User clicked on an actual view (and was not stopping a fling). It might be a - // click or a scroll. Assume it is a click until proven otherwise - mTouchMode = TOUCH_MODE_DOWN; - // FIXME Debounce - if (mPendingCheckForTap == null) { - mPendingCheckForTap = new CheckForTap(); - } - postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); - } else { - if (ev.getEdgeFlags() != 0 && motionPosition < 0) { - // If we couldn't find a view to click on, but the down event was touching - // the edge, we will bail out and try again. This allows the edge correcting - // code in ViewRoot to try to find a nearby view to select - return false; - } + switch (mTouchMode) { + case TOUCH_MODE_OVERFLING: { + mFlingRunnable.endFling(); + mTouchMode = TOUCH_MODE_OVERSCROLL; + mMotionY = mLastY = (int) ev.getY(); + mMotionCorrection = 0; + mActivePointerId = ev.getPointerId(0); + break; + } - if (mTouchMode == TOUCH_MODE_FLING) { - // Stopped a fling. It is a scroll. - createScrollingCache(); - mTouchMode = TOUCH_MODE_SCROLL; - mMotionCorrection = 0; - motionPosition = findMotionRow(y); - mFlingRunnable.flywheelTouch(); + default: { + mActivePointerId = ev.getPointerId(0); + final int x = (int) ev.getX(); + final int y = (int) ev.getY(); + int motionPosition = pointToPosition(x, y); + if (!mDataChanged) { + if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0) + && (getAdapter().isEnabled(motionPosition))) { + // User clicked on an actual view (and was not stopping a fling). It might be a + // click or a scroll. Assume it is a click until proven otherwise + mTouchMode = TOUCH_MODE_DOWN; + // FIXME Debounce + if (mPendingCheckForTap == null) { + mPendingCheckForTap = new CheckForTap(); + } + postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); + } else { + if (ev.getEdgeFlags() != 0 && motionPosition < 0) { + // If we couldn't find a view to click on, but the down event was touching + // the edge, we will bail out and try again. This allows the edge correcting + // code in ViewRoot to try to find a nearby view to select + return false; + } + + if (mTouchMode == TOUCH_MODE_FLING) { + // Stopped a fling. It is a scroll. + createScrollingCache(); + mTouchMode = TOUCH_MODE_SCROLL; + mMotionCorrection = 0; + motionPosition = findMotionRow(y); + mFlingRunnable.flywheelTouch(); + } } } - } - if (motionPosition >= 0) { - // Remember where the motion event started - v = getChildAt(motionPosition - mFirstPosition); - mMotionViewOriginalTop = v.getTop(); + if (motionPosition >= 0) { + // Remember where the motion event started + v = getChildAt(motionPosition - mFirstPosition); + mMotionViewOriginalTop = v.getTop(); + } + mMotionX = x; + mMotionY = y; + mMotionPosition = motionPosition; + mLastY = Integer.MIN_VALUE; + break; + } } - mMotionX = x; - mMotionY = y; - mMotionPosition = motionPosition; - mLastY = Integer.MIN_VALUE; break; } @@ -2593,9 +2724,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te requestDisallowInterceptTouchEvent(true); } + final int rawDeltaY = deltaY; deltaY -= mMotionCorrection; int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; + final int motionIndex; + if (mMotionPosition >= 0) { + motionIndex = mMotionPosition - mFirstPosition; + } else { + // If we don't have a motion position that we can reliably track, + // pick something in the middle to make a best guess at things below. + motionIndex = getChildCount() / 2; + } + + int motionViewPrevTop = 0; + View motionView = this.getChildAt(motionIndex); + if (motionView != null) { + motionViewPrevTop = motionView.getTop(); + } + // No need to do all this work if we're not going to move anyway boolean atEdge = false; if (incrementalDeltaY != 0) { @@ -2603,23 +2750,117 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } // Check to see if we have bumped into the scroll limit - if (atEdge && getChildCount() > 0) { - // Treat this like we're starting a new scroll from the current - // position. This will let the user start scrolling back into - // content immediately rather than needing to scroll back to the - // point where they hit the limit first. - int motionPosition = findMotionRow(y); - if (motionPosition >= 0) { - final View motionView = getChildAt(motionPosition - mFirstPosition); - mMotionViewOriginalTop = motionView.getTop(); + motionView = this.getChildAt(motionIndex); + if (motionView != null) { + // Check if the top of the motion view is where it is + // supposed to be + final int motionViewRealTop = motionView.getTop(); + if (atEdge) { + // Apply overscroll + + int overscroll = -incrementalDeltaY - + (motionViewRealTop - motionViewPrevTop); + overScrollBy(0, overscroll, 0, mScrollY, 0, 0, + 0, mOverscrollDistance, true); + if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) { + // Don't allow overfling if we're at the edge. + mVelocityTracker.clear(); + } + + final int overscrollMode = getOverScrollMode(); + if (overscrollMode == OVER_SCROLL_ALWAYS || + (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && + !contentFits())) { + mDirection = 0; // Reset when entering overscroll. + mTouchMode = TOUCH_MODE_OVERSCROLL; + if (rawDeltaY > 0) { + mEdgeGlowTop.onPull((float) overscroll / getHeight()); + if (!mEdgeGlowBottom.isFinished()) { + mEdgeGlowBottom.onRelease(); + } + } else if (rawDeltaY < 0) { + mEdgeGlowBottom.onPull((float) overscroll / getHeight()); + if (!mEdgeGlowTop.isFinished()) { + mEdgeGlowTop.onRelease(); + } + } + } } mMotionY = y; - mMotionPosition = motionPosition; invalidate(); } mLastY = y; } break; + + case TOUCH_MODE_OVERSCROLL: + if (y != mLastY) { + final int rawDeltaY = deltaY; + deltaY -= mMotionCorrection; + int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; + + final int oldScroll = mScrollY; + final int newScroll = oldScroll - incrementalDeltaY; + int newDirection = y > mLastY ? 1 : -1; + + if (mDirection == 0) { + mDirection = newDirection; + } + + if (mDirection != newDirection) { + // Coming back to 'real' list scrolling + incrementalDeltaY = -newScroll; + mScrollY = 0; + + // No need to do all this work if we're not going to move anyway + if (incrementalDeltaY != 0) { + trackMotionScroll(incrementalDeltaY, incrementalDeltaY); + } + + // Check to see if we are back in + View motionView = this.getChildAt(mMotionPosition - mFirstPosition); + if (motionView != null) { + mTouchMode = TOUCH_MODE_SCROLL; + + // We did not scroll the full amount. Treat this essentially like the + // start of a new touch scroll + final int motionPosition = findClosestMotionRow(y); + + mMotionCorrection = 0; + motionView = getChildAt(motionPosition - mFirstPosition); + mMotionViewOriginalTop = motionView.getTop(); + mMotionY = y; + mMotionPosition = motionPosition; + } + } else { + overScrollBy(0, -incrementalDeltaY, 0, mScrollY, 0, 0, + 0, mOverscrollDistance, true); + final int overscrollMode = getOverScrollMode(); + if (overscrollMode == OVER_SCROLL_ALWAYS || + (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && + !contentFits())) { + if (rawDeltaY > 0) { + mEdgeGlowTop.onPull((float) -incrementalDeltaY / getHeight()); + if (!mEdgeGlowBottom.isFinished()) { + mEdgeGlowBottom.onRelease(); + } + } else if (rawDeltaY < 0) { + mEdgeGlowBottom.onPull((float) -incrementalDeltaY / getHeight()); + if (!mEdgeGlowTop.isFinished()) { + mEdgeGlowTop.onRelease(); + } + } + invalidate(); + } + if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) { + // Don't allow overfling if we're at the edge. + mVelocityTracker.clear(); + } + } + mLastY = y; + mDirection = newDirection; + } + break; } break; @@ -2693,19 +2934,30 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te case TOUCH_MODE_SCROLL: final int childCount = getChildCount(); if (childCount > 0) { - if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top && + final int firstChildTop = getChildAt(0).getTop(); + final int lastChildBottom = getChildAt(childCount - 1).getBottom(); + final int contentTop = mListPadding.top; + final int contentBottom = getHeight() - mListPadding.bottom; + if (mFirstPosition == 0 && firstChildTop >= contentTop && mFirstPosition + childCount < mItemCount && - getChildAt(childCount - 1).getBottom() <= - getHeight() - mListPadding.bottom) { + lastChildBottom <= getHeight() - contentBottom) { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } else { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + final int initialVelocity = (int) (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale); - - if (Math.abs(initialVelocity) > mMinimumVelocity) { + // Fling if we have enough velocity and we aren't at a boundary. + // Since we can potentially overfling more than we can overscroll, don't + // allow the weird behavior where you can scroll to a boundary then + // fling further. + if (Math.abs(initialVelocity) > mMinimumVelocity && + !((mFirstPosition == 0 && + firstChildTop == contentTop - mOverscrollDistance) || + (mFirstPosition + childCount == mItemCount && + lastChildBottom == contentBottom + mOverscrollDistance))) { if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } @@ -2725,10 +2977,32 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } break; + + case TOUCH_MODE_OVERSCROLL: + if (mFlingRunnable == null) { + mFlingRunnable = new FlingRunnable(); + } + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); + + reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); + if (Math.abs(initialVelocity) > mMinimumVelocity) { + mFlingRunnable.startOverfling(-initialVelocity); + } else { + mFlingRunnable.startSpringback(); + } + + break; } setPressed(false); + if (mEdgeGlowTop != null) { + mEdgeGlowTop.onRelease(); + mEdgeGlowBottom.onRelease(); + } + // Need to redraw since we probably aren't drawing the selector anymore invalidate(); @@ -2759,24 +3033,42 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } case MotionEvent.ACTION_CANCEL: { - mTouchMode = TOUCH_MODE_REST; - setPressed(false); - View motionView = getChildAt(mMotionPosition - mFirstPosition); - if (motionView != null) { - motionView.setPressed(false); - } - clearScrollingCache(); + switch (mTouchMode) { + case TOUCH_MODE_OVERSCROLL: + if (mFlingRunnable == null) { + mFlingRunnable = new FlingRunnable(); + } + mFlingRunnable.startSpringback(); + break; - final Handler handler = getHandler(); - if (handler != null) { - handler.removeCallbacks(mPendingCheckForLongPress); - } + case TOUCH_MODE_OVERFLING: + // Do nothing - let it play out. + break; - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; + default: + mTouchMode = TOUCH_MODE_REST; + setPressed(false); + View motionView = this.getChildAt(mMotionPosition - mFirstPosition); + if (motionView != null) { + motionView.setPressed(false); + } + clearScrollingCache(); + + final Handler handler = getHandler(); + if (handler != null) { + handler.removeCallbacks(mPendingCheckForLongPress); + } + + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } } + if (mEdgeGlowTop != null) { + mEdgeGlowTop.onRelease(); + mEdgeGlowBottom.onRelease(); + } mActivePointerId = INVALID_POINTER; break; } @@ -2801,10 +3093,61 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } @Override + protected void onOverScrolled(int scrollX, int scrollY, + boolean clampedX, boolean clampedY) { + mScrollY = scrollY; + + if (clampedY) { + // Velocity is broken by hitting the limit; don't start a fling off of this. + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + awakenScrollBars(); + } + + @Override public void draw(Canvas canvas) { super.draw(canvas); + if (mEdgeGlowTop != null) { + final int scrollY = mScrollY; + if (!mEdgeGlowTop.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + + canvas.translate(0, Math.min(0, scrollY + mFirstPositionDistanceGuess)); + mEdgeGlowTop.setSize(width, getHeight()); + if (mEdgeGlowTop.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowBottom.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + final int height = getHeight(); + + canvas.translate(-width, 0); + canvas.rotate(-180, width, 0); + canvas.translate(0, -height); + mEdgeGlowBottom.setSize(width, height); + if (mEdgeGlowBottom.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + } if (mFastScroller != null) { - mFastScroller.draw(canvas); + final int scrollY = mScrollY; + if (scrollY != 0) { + // Pin to the top/bottom during overscroll + int restoreCount = canvas.save(); + canvas.translate(0, (float) scrollY); + mFastScroller.draw(canvas); + canvas.restoreToCount(restoreCount); + } else { + mFastScroller.draw(canvas); + } } } @@ -2823,6 +3166,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { int touchMode = mTouchMode; + if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) { + mMotionCorrection = 0; + return true; + } final int x = (int) ev.getX(); final int y = (int) ev.getY(); @@ -2887,6 +3234,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mMotionX = (int) ev.getX(newPointerIndex); mMotionY = (int) ev.getY(newPointerIndex); + mMotionCorrection = 0; mActivePointerId = ev.getPointerId(newPointerIndex); if (mVelocityTracker != null) { mVelocityTracker.clear(); @@ -2942,7 +3290,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te /** * Tracks the decay of a fling scroll */ - private final Scroller mScroller; + private final OverScroller mScroller; /** * Y value reported by mScroller on the previous fling @@ -2953,7 +3301,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te public void run() { final int activeId = mActivePointerId; final VelocityTracker vt = mVelocityTracker; - final Scroller scroller = mScroller; + final OverScroller scroller = mScroller; if (vt == null || activeId == INVALID_POINTER) { return; } @@ -2975,7 +3323,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds FlingRunnable() { - mScroller = new Scroller(getContext()); + mScroller = new OverScroller(getContext()); } void start(int initialVelocity) { @@ -2998,6 +3346,42 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + void startSpringback() { + if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) { + mTouchMode = TOUCH_MODE_OVERFLING; + invalidate(); + post(this); + } else { + mTouchMode = TOUCH_MODE_REST; + } + } + + void startOverfling(int initialVelocity) { + final int min = mScrollY > 0 ? Integer.MIN_VALUE : 0; + final int max = mScrollY > 0 ? 0 : Integer.MAX_VALUE; + mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, min, max, 0, getHeight()); + mTouchMode = TOUCH_MODE_OVERFLING; + invalidate(); + post(this); + } + + void edgeReached(int delta) { + mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance); + final int overscrollMode = getOverScrollMode(); + if (overscrollMode == OVER_SCROLL_ALWAYS || + (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) { + mTouchMode = TOUCH_MODE_OVERFLING; + final int vel = (int) mScroller.getCurrVelocity(); + if (delta > 0) { + mEdgeGlowTop.onAbsorb(vel); + } else { + mEdgeGlowBottom.onAbsorb(vel); + } + } + invalidate(); + post(this); + } + void startScroll(int distance, int duration) { int initialY = distance < 0 ? Integer.MAX_VALUE : 0; mLastFlingY = initialY; @@ -3040,58 +3424,100 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return; } // Fall through - case TOUCH_MODE_FLING: + case TOUCH_MODE_FLING: { if (mItemCount == 0 || getChildCount() == 0) { endFling(); return; } - break; - } - final Scroller scroller = mScroller; - boolean more = scroller.computeScrollOffset(); - final int y = scroller.getCurrY(); - // Flip sign to convert finger direction to list items direction - // (e.g. finger moving down means list is moving towards the top) - int delta = mLastFlingY - y; + final OverScroller scroller = mScroller; + boolean more = scroller.computeScrollOffset(); + final int y = scroller.getCurrY(); - // Pretend that each frame of a fling scroll is a touch scroll - if (delta > 0) { - // List is moving towards the top. Use first view as mMotionPosition - mMotionPosition = mFirstPosition; - final View firstView = getChildAt(0); - mMotionViewOriginalTop = firstView.getTop(); + // Flip sign to convert finger direction to list items direction + // (e.g. finger moving down means list is moving towards the top) + int delta = mLastFlingY - y; - // Don't fling more than 1 screen - delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta); - } else { - // List is moving towards the bottom. Use last view as mMotionPosition - int offsetToLast = getChildCount() - 1; - mMotionPosition = mFirstPosition + offsetToLast; + // Pretend that each frame of a fling scroll is a touch scroll + if (delta > 0) { + // List is moving towards the top. Use first view as mMotionPosition + mMotionPosition = mFirstPosition; + final View firstView = getChildAt(0); + mMotionViewOriginalTop = firstView.getTop(); - final View lastView = getChildAt(offsetToLast); - mMotionViewOriginalTop = lastView.getTop(); + // Don't fling more than 1 screen + delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta); + } else { + // List is moving towards the bottom. Use last view as mMotionPosition + int offsetToLast = getChildCount() - 1; + mMotionPosition = mFirstPosition + offsetToLast; - // Don't fling more than 1 screen - delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta); - } + final View lastView = getChildAt(offsetToLast); + mMotionViewOriginalTop = lastView.getTop(); - // Don't stop just because delta is zero (it could have been rounded) - final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0); + // Don't fling more than 1 screen + delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta); + } - if (more && !atEnd) { - invalidate(); - mLastFlingY = y; - post(this); - } else { - endFling(); + // Check to see if we have bumped into the scroll limit + View motionView = getChildAt(mMotionPosition - mFirstPosition); + int oldTop = 0; + if (motionView != null) { + oldTop = motionView.getTop(); + } - if (PROFILE_FLINGING) { - if (mFlingProfilingStarted) { - Debug.stopMethodTracing(); - mFlingProfilingStarted = false; + // Don't stop just because delta is zero (it could have been rounded) + final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0); + if (atEnd) { + if (motionView != null) { + // Tweak the scroll for how far we overshot + int overshoot = -(delta - (motionView.getTop() - oldTop)); + overScrollBy(0, overshoot, 0, mScrollY, 0, 0, + 0, mOverflingDistance, false); } + edgeReached(delta); + break; } + + if (more && !atEnd) { + invalidate(); + mLastFlingY = y; + post(this); + } else { + endFling(); + + if (PROFILE_FLINGING) { + if (mFlingProfilingStarted) { + Debug.stopMethodTracing(); + mFlingProfilingStarted = false; + } + + if (mFlingStrictSpan != null) { + mFlingStrictSpan.finish(); + mFlingStrictSpan = null; + } + } + } + break; + } + + case TOUCH_MODE_OVERFLING: { + final OverScroller scroller = mScroller; + if (scroller.computeScrollOffset()) { + final int scrollY = mScrollY; + final int deltaY = scroller.getCurrY() - scrollY; + if (overScrollBy(0, deltaY, 0, scrollY, 0, 0, + 0, mOverflingDistance, false)) { + startSpringback(); + } else { + invalidate(); + post(this); + } + } else { + endFling(); + } + break; + } } } } @@ -3620,16 +4046,29 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final int firstPosition = mFirstPosition; - if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) { + // Update our guesses for where the first and last views are + if (firstPosition == 0) { + mFirstPositionDistanceGuess = firstTop - mListPadding.top; + } else { + mFirstPositionDistanceGuess += incrementalDeltaY; + } + if (firstPosition + childCount == mItemCount) { + mLastPositionDistanceGuess = lastBottom + mListPadding.bottom; + } else { + mLastPositionDistanceGuess += incrementalDeltaY; + } + + if (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0) { // Don't need to move views down if the top of the first position // is already visible - return true; + return incrementalDeltaY != 0; } - if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) { + if (firstPosition + childCount == mItemCount && lastBottom <= end && + incrementalDeltaY <= 0) { // Don't need to move views up if the bottom of the last position // is already visible - return true; + return incrementalDeltaY != 0; } final boolean down = incrementalDeltaY < 0; @@ -3805,6 +4244,22 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te abstract int findMotionRow(int y); /** + * Find the row closest to y. This row will be used as the motion row when scrolling. + * + * @param y Where the user touched + * @return The position of the first (or only) item in the row closest to y + */ + int findClosestMotionRow(int y) { + final int childCount = getChildCount(); + if (childCount == 0) { + return INVALID_POSITION; + } + + final int motionRow = findMotionRow(y); + return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1; + } + + /** * Causes all the views to be rebuilt and redrawn. */ public void invalidateViews() { @@ -4577,6 +5032,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return result; } + private void finishGlows() { + if (mEdgeGlowTop != null) { + mEdgeGlowTop.finish(); + mEdgeGlowBottom.finish(); + } + } + /** * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService * through the specified intent. diff --git a/core/java/android/widget/EdgeGlow.java b/core/java/android/widget/EdgeGlow.java new file mode 100644 index 0000000..9b3a6e6 --- /dev/null +++ b/core/java/android/widget/EdgeGlow.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + +/** + * This class performs the glow effect used at the edges of scrollable widgets. + * @hide + */ +public class EdgeGlow { + private static final String TAG = "EdgeGlow"; + + // Time it will take the effect to fully recede in ms + private static final int RECEDE_TIME = 1000; + + // Time it will take before a pulled glow begins receding + private static final int PULL_TIME = 167; + + // Time it will take for a pulled glow to decay to partial strength before release + private static final int PULL_DECAY_TIME = 1000; + + private static final float MAX_ALPHA = 0.8f; + private static final float HELD_EDGE_ALPHA = 0.7f; + private static final float HELD_EDGE_SCALE_Y = 0.5f; + private static final float HELD_GLOW_ALPHA = 0.5f; + private static final float HELD_GLOW_SCALE_Y = 0.5f; + + private static final float MAX_GLOW_HEIGHT = 3.f; + + private static final float PULL_GLOW_BEGIN = 1.f; + private static final float PULL_EDGE_BEGIN = 0.6f; + + // Minimum velocity that will be absorbed + private static final int MIN_VELOCITY = 100; + + private static final float EPSILON = 0.001f; + + private final Drawable mEdge; + private final Drawable mGlow; + private int mWidth; + private int mHeight; + + private float mEdgeAlpha; + private float mEdgeScaleY; + private float mGlowAlpha; + private float mGlowScaleY; + + private float mEdgeAlphaStart; + private float mEdgeAlphaFinish; + private float mEdgeScaleYStart; + private float mEdgeScaleYFinish; + private float mGlowAlphaStart; + private float mGlowAlphaFinish; + private float mGlowScaleYStart; + private float mGlowScaleYFinish; + + private long mStartTime; + private float mDuration; + + private final Interpolator mInterpolator; + + private static final int STATE_IDLE = 0; + private static final int STATE_PULL = 1; + private static final int STATE_ABSORB = 2; + private static final int STATE_RECEDE = 3; + private static final int STATE_PULL_DECAY = 4; + + // How much dragging should effect the height of the edge image. + // Number determined by user testing. + private static final int PULL_DISTANCE_EDGE_FACTOR = 5; + + // How much dragging should effect the height of the glow image. + // Number determined by user testing. + private static final int PULL_DISTANCE_GLOW_FACTOR = 5; + private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 0.8f; + + private static final int VELOCITY_EDGE_FACTOR = 8; + private static final int VELOCITY_GLOW_FACTOR = 16; + + private int mState = STATE_IDLE; + + private float mPullDistance; + + public EdgeGlow(Drawable edge, Drawable glow) { + mEdge = edge; + mGlow = glow; + + mInterpolator = new DecelerateInterpolator(); + } + + public void setSize(int width, int height) { + mWidth = width; + mHeight = height; + } + + public boolean isFinished() { + return mState == STATE_IDLE; + } + + public void finish() { + mState = STATE_IDLE; + } + + /** + * Call when the object is pulled by the user. + * + * @param deltaDistance Change in distance since the last call + */ + public void onPull(float deltaDistance) { + final long now = AnimationUtils.currentAnimationTimeMillis(); + if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) { + return; + } + if (mState != STATE_PULL) { + mGlowScaleY = PULL_GLOW_BEGIN; + } + mState = STATE_PULL; + + mStartTime = now; + mDuration = PULL_TIME; + + mPullDistance += deltaDistance; + float distance = Math.abs(mPullDistance); + + mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, MAX_ALPHA)); + mEdgeScaleY = mEdgeScaleYStart = Math.max( + HELD_EDGE_SCALE_Y, Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f)); + + mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA, + mGlowAlpha + + (Math.abs(deltaDistance) * PULL_DISTANCE_ALPHA_GLOW_FACTOR)); + + float glowChange = Math.abs(deltaDistance); + if (deltaDistance > 0 && mPullDistance < 0) { + glowChange = -glowChange; + } + if (mPullDistance == 0) { + mGlowScaleY = 0; + } + + // Do not allow glow to get larger than MAX_GLOW_HEIGHT. + mGlowScaleY = mGlowScaleYStart = Math.min(MAX_GLOW_HEIGHT, Math.max( + 0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR)); + + mEdgeAlphaFinish = mEdgeAlpha; + mEdgeScaleYFinish = mEdgeScaleY; + mGlowAlphaFinish = mGlowAlpha; + mGlowScaleYFinish = mGlowScaleY; + } + + /** + * Call when the object is released after being pulled. + */ + public void onRelease() { + mPullDistance = 0; + + if (mState != STATE_PULL && mState != STATE_PULL_DECAY) { + return; + } + + mState = STATE_RECEDE; + mEdgeAlphaStart = mEdgeAlpha; + mEdgeScaleYStart = mEdgeScaleY; + mGlowAlphaStart = mGlowAlpha; + mGlowScaleYStart = mGlowScaleY; + + mEdgeAlphaFinish = 0.f; + mEdgeScaleYFinish = 0.f; + mGlowAlphaFinish = 0.f; + mGlowScaleYFinish = 0.f; + + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = RECEDE_TIME; + } + + /** + * Call when the effect absorbs an impact at the given velocity. + * + * @param velocity Velocity at impact in pixels per second. + */ + public void onAbsorb(int velocity) { + mState = STATE_ABSORB; + velocity = Math.max(MIN_VELOCITY, Math.abs(velocity)); + + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = 0.1f + (velocity * 0.03f); + + // The edge should always be at least partially visible, regardless + // of velocity. + mEdgeAlphaStart = 0.f; + mEdgeScaleY = mEdgeScaleYStart = 0.f; + // The glow depends more on the velocity, and therefore starts out + // nearly invisible. + mGlowAlphaStart = 0.5f; + mGlowScaleYStart = 0.f; + + // Factor the velocity by 8. Testing on device shows this works best to + // reflect the strength of the user's scrolling. + mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1)); + // Edge should never get larger than the size of its asset. + mEdgeScaleYFinish = Math.max( + HELD_EDGE_SCALE_Y, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1.f)); + + // Growth for the size of the glow should be quadratic to properly + // respond + // to a user's scrolling speed. The faster the scrolling speed, the more + // intense the effect should be for both the size and the saturation. + mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f); + // Alpha should change for the glow as well as size. + mGlowAlphaFinish = Math.max( + mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA)); + } + + + /** + * Draw into the provided canvas. Assumes that the canvas has been rotated + * accordingly and the size has been set. The effect will be drawn the full + * width of X=0 to X=width, emitting from Y=0 and extending to some factor < + * 1.f of height. + * + * @param canvas Canvas to draw into + * @return true if drawing should continue beyond this frame to continue the + * animation + */ + public boolean draw(Canvas canvas) { + update(); + + final int edgeHeight = mEdge.getIntrinsicHeight(); + final int edgeWidth = mEdge.getIntrinsicWidth(); + final int glowHeight = mGlow.getIntrinsicHeight(); + final int glowWidth = mGlow.getIntrinsicWidth(); + + mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255)); + + // Center the glow inside the width of the container. + int glowLeft = (mWidth - glowWidth)/2; + mGlow.setBounds(glowLeft, 0, mWidth - glowLeft, (int) Math.min( + glowHeight * mGlowScaleY * glowHeight/ glowWidth * 0.6f, + glowHeight * MAX_GLOW_HEIGHT)); + mGlow.draw(canvas); + + mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255)); + + int edgeLeft = (mWidth - edgeWidth)/2; + mEdge.setBounds(edgeLeft, 0, mWidth - edgeLeft, (int) (edgeHeight * mEdgeScaleY)); + mEdge.draw(canvas); + + return mState != STATE_IDLE; + } + + private void update() { + final long time = AnimationUtils.currentAnimationTimeMillis(); + final float t = Math.min((time - mStartTime) / mDuration, 1.f); + + final float interp = mInterpolator.getInterpolation(t); + + mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp; + mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp; + mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp; + mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp; + + if (t >= 1.f - EPSILON) { + switch (mState) { + case STATE_ABSORB: + mState = STATE_RECEDE; + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = RECEDE_TIME; + + mEdgeAlphaStart = mEdgeAlpha; + mEdgeScaleYStart = mEdgeScaleY; + mGlowAlphaStart = mGlowAlpha; + mGlowScaleYStart = mGlowScaleY; + + // After absorb, the glow and edge should fade to nothing. + mEdgeAlphaFinish = 0.f; + mEdgeScaleYFinish = 0.f; + mGlowAlphaFinish = 0.f; + mGlowScaleYFinish = 0.f; + break; + case STATE_PULL: + mState = STATE_PULL_DECAY; + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = PULL_DECAY_TIME; + + mEdgeAlphaStart = mEdgeAlpha; + mEdgeScaleYStart = mEdgeScaleY; + mGlowAlphaStart = mGlowAlpha; + mGlowScaleYStart = mGlowScaleY; + + // After pull, the glow and edge should fade to nothing. + mEdgeAlphaFinish = 0.f; + mEdgeScaleYFinish = 0.f; + mGlowAlphaFinish = 0.f; + mGlowScaleYFinish = 0.f; + break; + case STATE_PULL_DECAY: + // When receding, we want edge to decrease more slowly + // than the glow. + float factor = mGlowScaleYFinish != 0 ? 1 + / (mGlowScaleYFinish * mGlowScaleYFinish) + : Float.MAX_VALUE; + mEdgeScaleY = mEdgeScaleYStart + + (mEdgeScaleYFinish - mEdgeScaleYStart) * + interp * factor; + break; + case STATE_RECEDE: + mState = STATE_IDLE; + break; + } + } + } +} diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 114ae81..4146460 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -1954,7 +1954,12 @@ public class GridView extends AbsListView { // TODO: Account for vertical spacing too final int numColumns = mNumColumns; final int rowCount = (mItemCount + numColumns - 1) / numColumns; - return Math.max(rowCount * 100, 0); + int result = Math.max(rowCount * 100, 0); + if (mScrollY != 0) { + // Compensate for overscroll + result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100)); + } + return result; } } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index e30d4c8..9fc91da 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -16,19 +16,24 @@ package android.widget; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Rect; +import com.android.internal.R; + import android.util.AttributeSet; -import android.view.FocusFinder; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.VelocityTracker; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.view.View; +import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.KeyEvent; +import android.view.FocusFinder; +import android.view.MotionEvent; import android.view.ViewParent; import android.view.animation.AnimationUtils; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; import java.util.List; @@ -65,7 +70,9 @@ public class HorizontalScrollView extends FrameLayout { private long mLastScroll; private final Rect mTempRect = new Rect(); - private Scroller mScroller; + private OverScroller mScroller; + private EdgeGlow mEdgeGlowLeft; + private EdgeGlow mEdgeGlowRight; /** * Flag to indicate that we are moving focus ourselves. This is so the @@ -119,6 +126,9 @@ public class HorizontalScrollView extends FrameLayout { private int mMinimumVelocity; private int mMaximumVelocity; + private int mOverscrollDistance; + private int mOverflingDistance; + /** * ID of the active pointer. This is used to retain consistency during * drags/flings if multiple pointers are used. @@ -191,7 +201,7 @@ public class HorizontalScrollView extends FrameLayout { private void initScrollView() { - mScroller = new Scroller(getContext()); + mScroller = new OverScroller(getContext()); setFocusable(true); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setWillNotDraw(false); @@ -199,6 +209,8 @@ public class HorizontalScrollView extends FrameLayout { mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mOverscrollDistance = configuration.getScaledOverscrollDistance(); + mOverflingDistance = configuration.getScaledOverflingDistance(); } @Override @@ -463,6 +475,9 @@ public class HorizontalScrollView extends FrameLayout { /* Release the drag */ mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; + if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) { + invalidate(); + } break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); @@ -495,9 +510,7 @@ public class HorizontalScrollView extends FrameLayout { switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { final float x = ev.getX(); - if (!(mIsBeingDragged = inChild((int) x, (int) ev.getY()))) { - return false; - } + mIsBeingDragged = true; /* * If being flinged and user touches, stop the fling. isFinished @@ -520,7 +533,36 @@ public class HorizontalScrollView extends FrameLayout { final int deltaX = (int) (mLastMotionX - x); mLastMotionX = x; - scrollBy(deltaX, 0); + final int oldX = mScrollX; + final int oldY = mScrollY; + final int range = getScrollRange(); + if (overScrollBy(deltaX, 0, mScrollX, 0, range, 0, + mOverscrollDistance, 0, true)) { + // Break our velocity if we hit a scroll barrier. + mVelocityTracker.clear(); + } + onScrollChanged(mScrollX, mScrollY, oldX, oldY); + + final int overscrollMode = getOverScrollMode(); + if (overscrollMode == OVER_SCROLL_ALWAYS || + (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0)) { + final int pulledToX = oldX + deltaX; + if (pulledToX < 0) { + mEdgeGlowLeft.onPull((float) deltaX / getWidth()); + if (!mEdgeGlowRight.isFinished()) { + mEdgeGlowRight.onRelease(); + } + } else if (pulledToX > range) { + mEdgeGlowRight.onPull((float) deltaX / getWidth()); + if (!mEdgeGlowLeft.isFinished()) { + mEdgeGlowLeft.onRelease(); + } + } + if (mEdgeGlowLeft != null + && (!mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished())) { + invalidate(); + } + } } break; case MotionEvent.ACTION_UP: @@ -529,8 +571,15 @@ public class HorizontalScrollView extends FrameLayout { velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId); - if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) { - fling(-initialVelocity); + if (getChildCount() > 0) { + if ((Math.abs(initialVelocity) > mMinimumVelocity)) { + fling(-initialVelocity); + } else { + final int right = getScrollRange(); + if (mScroller.springBack(mScrollX, mScrollY, 0, right, 0, 0)) { + invalidate(); + } + } } mActivePointerId = INVALID_POINTER; @@ -540,16 +589,27 @@ public class HorizontalScrollView extends FrameLayout { mVelocityTracker.recycle(); mVelocityTracker = null; } + if (mEdgeGlowLeft != null) { + mEdgeGlowLeft.onRelease(); + mEdgeGlowRight.onRelease(); + } } break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged && getChildCount() > 0) { + if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) { + invalidate(); + } mActivePointerId = INVALID_POINTER; mIsBeingDragged = false; if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } + if (mEdgeGlowLeft != null) { + mEdgeGlowLeft.onRelease(); + mEdgeGlowRight.onRelease(); + } } break; case MotionEvent.ACTION_POINTER_UP: @@ -576,12 +636,28 @@ public class HorizontalScrollView extends FrameLayout { } } + @Override + protected void onOverScrolled(int scrollX, int scrollY, + boolean clampedX, boolean clampedY) { + // Treat animating scrolls differently; see #computeScroll() for why. + if (!mScroller.isFinished()) { + mScrollX = scrollX; + mScrollY = scrollY; + if (clampedX) { + mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0); + } + } else { + super.scrollTo(scrollX, scrollY); + } + awakenScrollBars(); + } + private int getScrollRange() { int scrollRange = 0; if (getChildCount() > 0) { View child = getChildAt(0); scrollRange = Math.max(0, - child.getWidth() - getWidth() - mPaddingLeft - mPaddingRight); + child.getWidth() - (getWidth() - mPaddingLeft - mPaddingRight)); } return scrollRange; } @@ -958,7 +1034,16 @@ public class HorizontalScrollView extends FrameLayout { return contentWidth; } - return getChildAt(0).getRight(); + int scrollRange = getChildAt(0).getRight(); + final int scrollX = mScrollX; + final int overscrollRight = Math.max(0, scrollRange - contentWidth); + if (scrollX < 0) { + scrollRange -= scrollX; + } else if (scrollX > overscrollRight) { + scrollRange += scrollX - overscrollRight; + } + + return scrollRange; } @Override @@ -1019,14 +1104,20 @@ public class HorizontalScrollView extends FrameLayout { int x = mScroller.getCurrX(); int y = mScroller.getCurrY(); - if (getChildCount() > 0) { - View child = getChildAt(0); - x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth()); - y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight()); - if (x != oldX || y != oldY) { - mScrollX = x; - mScrollY = y; - onScrollChanged(x, y, oldX, oldY); + if (oldX != x || oldY != y) { + overScrollBy(x - oldX, y - oldY, oldX, oldY, getScrollRange(), 0, + mOverflingDistance, 0, false); + onScrollChanged(mScrollX, mScrollY, oldX, oldY); + + final int range = getScrollRange(); + final int overscrollMode = getOverScrollMode(); + if (overscrollMode == OVER_SCROLL_ALWAYS || + (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0)) { + if (x < 0 && oldX >= 0) { + mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity()); + } else if (x > range && oldX <= range) { + mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity()); + } } } awakenScrollBars(); @@ -1263,7 +1354,7 @@ public class HorizontalScrollView extends FrameLayout { int right = getChildAt(0).getWidth(); mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, - Math.max(0, right - width), 0, 0); + Math.max(0, right - width), 0, 0, width/2, 0); final boolean movingRight = velocityX > 0; @@ -1301,6 +1392,56 @@ public class HorizontalScrollView extends FrameLayout { } } + @Override + public void setOverScrollMode(int mode) { + if (mode != OVER_SCROLL_NEVER) { + if (mEdgeGlowLeft == null) { + final Resources res = getContext().getResources(); + final Drawable edge = res.getDrawable(R.drawable.overscroll_edge); + final Drawable glow = res.getDrawable(R.drawable.overscroll_glow); + mEdgeGlowLeft = new EdgeGlow(edge, glow); + mEdgeGlowRight = new EdgeGlow(edge, glow); + } + } else { + mEdgeGlowLeft = null; + mEdgeGlowRight = null; + } + super.setOverScrollMode(mode); + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + if (mEdgeGlowLeft != null) { + final int scrollX = mScrollX; + if (!mEdgeGlowLeft.isFinished()) { + final int restoreCount = canvas.save(); + final int height = getHeight(); + + canvas.rotate(270); + canvas.translate(-height * 1.5f, Math.min(0, scrollX)); + mEdgeGlowLeft.setSize(getHeight() * 2, getWidth()); + if (mEdgeGlowLeft.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowRight.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + final int height = getHeight(); + + canvas.rotate(90); + canvas.translate(-height / 2, -(Math.max(getScrollRange(), scrollX) + width)); + mEdgeGlowRight.setSize(height * 2, width); + if (mEdgeGlowRight.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + } + } + private int clamp(int n, int my, int child) { if (my >= child || n < 0) { return 0; diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 502cc38..fd4f950 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -104,6 +104,9 @@ public class ListView extends AbsListView { Drawable mDivider; int mDividerHeight; + Drawable mOverScrollHeader; + Drawable mOverScrollFooter; + private boolean mIsCacheColorOpaque; private boolean mDividerIsOpaque; @@ -152,6 +155,18 @@ public class ListView extends AbsListView { setDivider(d); } + final Drawable osHeader = a.getDrawable( + com.android.internal.R.styleable.ListView_overScrollHeader); + if (osHeader != null) { + setOverscrollHeader(osHeader); + } + + final Drawable osFooter = a.getDrawable( + com.android.internal.R.styleable.ListView_overScrollFooter); + if (osFooter != null) { + setOverscrollFooter(osFooter); + } + // Use the height specified, zero being the default final int dividerHeight = a.getDimensionPixelSize( com.android.internal.R.styleable.ListView_dividerHeight, 0); @@ -2962,14 +2977,52 @@ public class ListView extends AbsListView { } super.setCacheColorHint(color); } - + + void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) { + final int height = drawable.getMinimumHeight(); + + canvas.save(); + canvas.clipRect(bounds); + + final int span = bounds.bottom - bounds.top; + if (span < height) { + bounds.top = bounds.bottom - height; + } + + drawable.setBounds(bounds); + drawable.draw(canvas); + + canvas.restore(); + } + + void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) { + final int height = drawable.getMinimumHeight(); + + canvas.save(); + canvas.clipRect(bounds); + + final int span = bounds.bottom - bounds.top; + if (span < height) { + bounds.bottom = bounds.top + height; + } + + drawable.setBounds(bounds); + drawable.draw(canvas); + + canvas.restore(); + } + @Override protected void dispatchDraw(Canvas canvas) { // Draw the dividers final int dividerHeight = mDividerHeight; + final Drawable overscrollHeader = mOverScrollHeader; + final Drawable overscrollFooter = mOverScrollFooter; + final boolean drawOverscrollHeader = overscrollHeader != null; + final boolean drawOverscrollFooter = overscrollFooter != null; final boolean drawDividers = dividerHeight > 0 && mDivider != null; - if (drawDividers) { + if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) { // Only modify the top and bottom in the loop, we set the left and right here final Rect bounds = mTempRect; bounds.left = mPaddingLeft; @@ -2998,34 +3051,67 @@ public class ListView extends AbsListView { final int listBottom = mBottom - mTop - mListPadding.bottom + mScrollY; if (!mStackFromBottom) { - int bottom; + int bottom = 0; + // Draw top divider or header for overscroll + final int scrollY = mScrollY; + if (count > 0 && scrollY < 0) { + if (drawOverscrollHeader) { + bounds.bottom = 0; + bounds.top = scrollY; + drawOverscrollHeader(canvas, overscrollHeader, bounds); + } else if (drawDividers) { + bounds.bottom = 0; + bounds.top = -dividerHeight; + drawDivider(canvas, bounds, -1); + } + } + for (int i = 0; i < count; i++) { if ((headerDividers || first + i >= headerCount) && (footerDividers || first + i < footerLimit)) { View child = getChildAt(i); bottom = child.getBottom(); // Don't draw dividers next to items that are not enabled - if ((areAllItemsSelectable || - (adapter.isEnabled(first + i) && (i == count - 1 || - adapter.isEnabled(first + i + 1))))) { - bounds.top = bottom; - bounds.bottom = bottom + dividerHeight; - drawDivider(canvas, bounds, i); - } else if (fillForMissingDividers) { - bounds.top = bottom; - bounds.bottom = bottom + dividerHeight; - canvas.drawRect(bounds, paint); + + if (drawDividers && + (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) { + if ((areAllItemsSelectable || + (adapter.isEnabled(first + i) && (i == count - 1 || + adapter.isEnabled(first + i + 1))))) { + bounds.top = bottom; + bounds.bottom = bottom + dividerHeight; + drawDivider(canvas, bounds, i); + } else if (fillForMissingDividers) { + bounds.top = bottom; + bounds.bottom = bottom + dividerHeight; + canvas.drawRect(bounds, paint); + } } } } + + final int overFooterBottom = mBottom + mScrollY; + if (drawOverscrollFooter && first + count == itemCount && + overFooterBottom > bottom) { + bounds.top = bottom; + bounds.bottom = overFooterBottom; + drawOverscrollFooter(canvas, overscrollFooter, bounds); + } } else { int top; int listTop = mListPadding.top; final int scrollY = mScrollY; - for (int i = 0; i < count; i++) { + if (count > 0 && drawOverscrollHeader) { + bounds.top = scrollY; + bounds.bottom = getChildAt(0).getTop(); + drawOverscrollHeader(canvas, overscrollHeader, bounds); + } + + final int start = drawOverscrollHeader ? 1 : 0; + for (int i = start; i < count; i++) { if ((headerDividers || first + i >= headerCount) && (footerDividers || first + i < footerLimit)) { View child = getChildAt(i); @@ -3052,9 +3138,16 @@ public class ListView extends AbsListView { } if (count > 0 && scrollY > 0) { - bounds.top = listBottom; - bounds.bottom = listBottom + dividerHeight; - drawDivider(canvas, bounds, -1); + if (drawOverscrollFooter) { + final int absListBottom = mBottom; + bounds.top = absListBottom; + bounds.bottom = absListBottom + scrollY; + drawOverscrollFooter(canvas, overscrollFooter, bounds); + } else if (drawDividers) { + bounds.top = listBottom; + bounds.bottom = listBottom + dividerHeight; + drawDivider(canvas, bounds, -1); + } } } } @@ -3150,6 +3243,45 @@ public class ListView extends AbsListView { invalidate(); } + /** + * Sets the drawable that will be drawn above all other list content. + * This area can become visible when the user overscrolls the list. + * + * @param header The drawable to use + */ + public void setOverscrollHeader(Drawable header) { + mOverScrollHeader = header; + if (mScrollY < 0) { + invalidate(); + } + } + + /** + * @return The drawable that will be drawn above all other list content + */ + public Drawable getOverscrollHeader() { + return mOverScrollHeader; + } + + /** + * Sets the drawable that will be drawn below all other list content. + * This area can become visible when the user overscrolls the list, + * or when the list's content does not fully fill the container area. + * + * @param footer The drawable to use + */ + public void setOverscrollFooter(Drawable footer) { + mOverScrollFooter = footer; + invalidate(); + } + + /** + * @return The drawable that will be drawn below all other list content + */ + public Drawable getOverscrollFooter() { + return mOverScrollFooter; + } + @Override protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java new file mode 100644 index 0000000..cd81e31 --- /dev/null +++ b/core/java/android/widget/OverScroller.java @@ -0,0 +1,888 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.content.Context; +import android.graphics.Interpolator; +import android.view.ViewConfiguration; +import android.view.animation.AnimationUtils; + +/** + * This class encapsulates scrolling with the ability to overshoot the bounds + * of a scrolling operation. This class is a drop-in replacement for + * {@link android.widget.Scroller} in most cases. + */ +public class OverScroller { + int mMode; + + private final MagneticOverScroller mScrollerX; + private final MagneticOverScroller mScrollerY; + + private float mDeceleration; + private final float mPpi; + private final boolean mFlywheel; + + private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9)); + private static float ALPHA = 800; // pixels / seconds + private static float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance) + private static float END_TENSION = 1.0f - START_TENSION; + private static final int NB_SAMPLES = 100; + private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1]; + private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1]; + + private static final int DEFAULT_DURATION = 250; + private static final int SCROLL_MODE = 0; + private static final int FLING_MODE = 1; + + static { + float x_min = 0.0f; + float y_min = 0.0f; + for (int i = 0; i < NB_SAMPLES; i++) { + final float alpha = (float) i / NB_SAMPLES; + { + float x_max = 1.0f; + float x, tx, coef; + while (true) { + x = x_min + (x_max - x_min) / 2.0f; + coef = 3.0f * x * (1.0f - x); + tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x; + if (Math.abs(tx - alpha) < 1E-5) break; + if (tx > alpha) x_max = x; + else x_min = x; + } + SPLINE_POSITION[i] = coef + x * x * x; + } + + { + float y_max = 1.0f; + float y, dy, coef; + while (true) { + y = y_min + (y_max - y_min) / 2.0f; + coef = 3.0f * y * (1.0f - y); + dy = coef + y * y * y; + if (Math.abs(dy - alpha) < 1E-5) break; + if (dy > alpha) y_max = y; + else y_min = y; + } + SPLINE_TIME[i] = coef * ((1.0f - y) * START_TENSION + y * END_TENSION) + y * y * y; + } + } + SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f; + } + + public OverScroller(Context context) { + this(context, null, 0.f, 0.f, true); + } + + /** + * Creates an OverScroller. + * @param context The context of this application. + * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will + * be used. + * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the + * velocity which is preserved in the bounce when the horizontal edge is reached. A null value + * means no bounce. + * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. + */ + public OverScroller(Context context, Interpolator interpolator, + float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) { + mFlywheel = flywheel; + mPpi = context.getResources().getDisplayMetrics().density * 160.0f; + mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction()); + mScrollerX = new MagneticOverScroller(); + mScrollerY = new MagneticOverScroller(); + + mScrollerX.setBounceCoefficient(bounceCoefficientX); + mScrollerY.setBounceCoefficient(bounceCoefficientY); + } + + + /** + * The amount of friction applied to flings. The default value + * is {@link ViewConfiguration#getScrollFriction}. + * + * @param friction A scalar dimension-less value representing the coefficient of + * friction. + */ + public final void setFriction(float friction) { + mDeceleration = computeDeceleration(friction); + } + + private float computeDeceleration(float friction) { + return 9.81f // g (m/s^2) + * 39.37f // inch/meter + * mPpi // pixels per inch + * friction; + } + + /** + * + * Returns whether the scroller has finished scrolling. + * + * @return True if the scroller has finished scrolling, false otherwise. + */ + public final boolean isFinished() { + return mScrollerX.mFinished && mScrollerY.mFinished; + } + + /** + * Force the finished field to a particular value. Contrary to + * {@link #abortAnimation()}, forcing the animation to finished + * does NOT cause the scroller to move to the final x and y + * position. + * + * @param finished The new finished value. + */ + public final void forceFinished(boolean finished) { + mScrollerX.mFinished = mScrollerY.mFinished = finished; + } + + /** + * Returns the current X offset in the scroll. + * + * @return The new X offset as an absolute distance from the origin. + */ + public final int getCurrX() { + return mScrollerX.mCurrentPosition; + } + + /** + * Returns the current Y offset in the scroll. + * + * @return The new Y offset as an absolute distance from the origin. + */ + public final int getCurrY() { + return mScrollerY.mCurrentPosition; + } + + /** + * @hide + * Returns the current velocity. + * + * @return The original velocity less the deceleration, norm of the X and Y velocity vector. + */ + public float getCurrVelocity() { + float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity; + squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity; + return (float) Math.sqrt(squaredNorm); + } + + /** + * Returns the start X offset in the scroll. + * + * @return The start X offset as an absolute distance from the origin. + */ + public final int getStartX() { + return mScrollerX.mStart; + } + + /** + * Returns the start Y offset in the scroll. + * + * @return The start Y offset as an absolute distance from the origin. + */ + public final int getStartY() { + return mScrollerY.mStart; + } + + /** + * Returns where the scroll will end. Valid only for "fling" scrolls. + * + * @return The final X offset as an absolute distance from the origin. + */ + public final int getFinalX() { + return mScrollerX.mFinal; + } + + /** + * Returns where the scroll will end. Valid only for "fling" scrolls. + * + * @return The final Y offset as an absolute distance from the origin. + */ + public final int getFinalY() { + return mScrollerY.mFinal; + } + + /** + * Returns how long the scroll event will take, in milliseconds. + * + * @return The duration of the scroll in milliseconds. + * + * @hide Pending removal once nothing depends on it + * @deprecated OverScrollers don't necessarily have a fixed duration. + * This function will lie to the best of its ability. + */ + @Deprecated + public final int getDuration() { + return Math.max(mScrollerX.mDuration, mScrollerY.mDuration); + } + + /** + * Extend the scroll animation. This allows a running animation to scroll + * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}. + * + * @param extend Additional time to scroll in milliseconds. + * @see #setFinalX(int) + * @see #setFinalY(int) + * + * @hide Pending removal once nothing depends on it + * @deprecated OverScrollers don't necessarily have a fixed duration. + * Instead of setting a new final position and extending + * the duration of an existing scroll, use startScroll + * to begin a new animation. + */ + @Deprecated + public void extendDuration(int extend) { + mScrollerX.extendDuration(extend); + mScrollerY.extendDuration(extend); + } + + /** + * Sets the final position (X) for this scroller. + * + * @param newX The new X offset as an absolute distance from the origin. + * @see #extendDuration(int) + * @see #setFinalY(int) + * + * @hide Pending removal once nothing depends on it + * @deprecated OverScroller's final position may change during an animation. + * Instead of setting a new final position and extending + * the duration of an existing scroll, use startScroll + * to begin a new animation. + */ + @Deprecated + public void setFinalX(int newX) { + mScrollerX.setFinalPosition(newX); + } + + /** + * Sets the final position (Y) for this scroller. + * + * @param newY The new Y offset as an absolute distance from the origin. + * @see #extendDuration(int) + * @see #setFinalX(int) + * + * @hide Pending removal once nothing depends on it + * @deprecated OverScroller's final position may change during an animation. + * Instead of setting a new final position and extending + * the duration of an existing scroll, use startScroll + * to begin a new animation. + */ + @Deprecated + public void setFinalY(int newY) { + mScrollerY.setFinalPosition(newY); + } + + /** + * Call this when you want to know the new location. If it returns true, the + * animation is not yet finished. + */ + public boolean computeScrollOffset() { + if (isFinished()) { + return false; + } + + switch (mMode) { + case SCROLL_MODE: + long time = AnimationUtils.currentAnimationTimeMillis(); + // Any scroller can be used for time, since they were started + // together in scroll mode. We use X here. + final long elapsedTime = time - mScrollerX.mStartTime; + + final int duration = mScrollerX.mDuration; + if (elapsedTime < duration) { + float q = (float) (elapsedTime) / duration; + + q = Scroller.viscousFluid(q); + + mScrollerX.updateScroll(q); + mScrollerY.updateScroll(q); + } else { + abortAnimation(); + } + break; + + case FLING_MODE: + if (!mScrollerX.mFinished) { + if (!mScrollerX.update()) { + if (!mScrollerX.continueWhenFinished()) { + mScrollerX.finish(); + } + } + } + + if (!mScrollerY.mFinished) { + if (!mScrollerY.update()) { + if (!mScrollerY.continueWhenFinished()) { + mScrollerY.finish(); + } + } + } + + break; + } + + return true; + } + + /** + * Start scrolling by providing a starting point and the distance to travel. + * The scroll will use the default value of 250 milliseconds for the + * duration. + * + * @param startX Starting horizontal scroll offset in pixels. Positive + * numbers will scroll the content to the left. + * @param startY Starting vertical scroll offset in pixels. Positive numbers + * will scroll the content up. + * @param dx Horizontal distance to travel. Positive numbers will scroll the + * content to the left. + * @param dy Vertical distance to travel. Positive numbers will scroll the + * content up. + */ + public void startScroll(int startX, int startY, int dx, int dy) { + startScroll(startX, startY, dx, dy, DEFAULT_DURATION); + } + + /** + * Start scrolling by providing a starting point and the distance to travel. + * + * @param startX Starting horizontal scroll offset in pixels. Positive + * numbers will scroll the content to the left. + * @param startY Starting vertical scroll offset in pixels. Positive numbers + * will scroll the content up. + * @param dx Horizontal distance to travel. Positive numbers will scroll the + * content to the left. + * @param dy Vertical distance to travel. Positive numbers will scroll the + * content up. + * @param duration Duration of the scroll in milliseconds. + */ + public void startScroll(int startX, int startY, int dx, int dy, int duration) { + mMode = SCROLL_MODE; + mScrollerX.startScroll(startX, dx, duration); + mScrollerY.startScroll(startY, dy, duration); + } + + /** + * Call this when you want to 'spring back' into a valid coordinate range. + * + * @param startX Starting X coordinate + * @param startY Starting Y coordinate + * @param minX Minimum valid X value + * @param maxX Maximum valid X value + * @param minY Minimum valid Y value + * @param maxY Minimum valid Y value + * @return true if a springback was initiated, false if startX and startY were + * already within the valid range. + */ + public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) { + mMode = FLING_MODE; + + // Make sure both methods are called. + final boolean spingbackX = mScrollerX.springback(startX, minX, maxX); + final boolean spingbackY = mScrollerY.springback(startY, minY, maxY); + return spingbackX || spingbackY; + } + + public void fling(int startX, int startY, int velocityX, int velocityY, + int minX, int maxX, int minY, int maxY) { + fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0); + } + + /** + * Start scrolling based on a fling gesture. The distance traveled will + * depend on the initial velocity of the fling. + * + * @param startX Starting point of the scroll (X) + * @param startY Starting point of the scroll (Y) + * @param velocityX Initial velocity of the fling (X) measured in pixels per + * second. + * @param velocityY Initial velocity of the fling (Y) measured in pixels per + * second + * @param minX Minimum X value. The scroller will not scroll past this point + * unless overX > 0. If overfling is allowed, it will use minX as + * a springback boundary. + * @param maxX Maximum X value. The scroller will not scroll past this point + * unless overX > 0. If overfling is allowed, it will use maxX as + * a springback boundary. + * @param minY Minimum Y value. The scroller will not scroll past this point + * unless overY > 0. If overfling is allowed, it will use minY as + * a springback boundary. + * @param maxY Maximum Y value. The scroller will not scroll past this point + * unless overY > 0. If overfling is allowed, it will use maxY as + * a springback boundary. + * @param overX Overfling range. If > 0, horizontal overfling in either + * direction will be possible. + * @param overY Overfling range. If > 0, vertical overfling in either + * direction will be possible. + */ + public void fling(int startX, int startY, int velocityX, int velocityY, + int minX, int maxX, int minY, int maxY, int overX, int overY) { + // Continue a scroll or fling in progress + if (mFlywheel && !isFinished()) { + float oldVelocityX = mScrollerX.mCurrVelocity; + float oldVelocityY = mScrollerY.mCurrVelocity; + if (Math.signum(velocityX) == Math.signum(oldVelocityX) && + Math.signum(velocityY) == Math.signum(oldVelocityY)) { + velocityX += oldVelocityX; + velocityY += oldVelocityY; + } + } + + mMode = FLING_MODE; + mScrollerX.fling(startX, velocityX, minX, maxX, overX); + mScrollerY.fling(startY, velocityY, minY, maxY, overY); + } + + /** + * Notify the scroller that we've reached a horizontal boundary. + * Normally the information to handle this will already be known + * when the animation is started, such as in a call to one of the + * fling functions. However there are cases where this cannot be known + * in advance. This function will transition the current motion and + * animate from startX to finalX as appropriate. + * + * @param startX Starting/current X position + * @param finalX Desired final X position + * @param overX Magnitude of overscroll allowed. This should be the maximum + * desired distance from finalX. Absolute value - must be positive. + */ + public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) { + mScrollerX.notifyEdgeReached(startX, finalX, overX); + } + + /** + * Notify the scroller that we've reached a vertical boundary. + * Normally the information to handle this will already be known + * when the animation is started, such as in a call to one of the + * fling functions. However there are cases where this cannot be known + * in advance. This function will animate a parabolic motion from + * startY to finalY. + * + * @param startY Starting/current Y position + * @param finalY Desired final Y position + * @param overY Magnitude of overscroll allowed. This should be the maximum + * desired distance from finalY. Absolute value - must be positive. + */ + public void notifyVerticalEdgeReached(int startY, int finalY, int overY) { + mScrollerY.notifyEdgeReached(startY, finalY, overY); + } + + /** + * Returns whether the current Scroller is currently returning to a valid position. + * Valid bounds were provided by the + * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method. + * + * One should check this value before calling + * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress + * to restore a valid position will then be stopped. The caller has to take into account + * the fact that the started scroll will start from an overscrolled position. + * + * @return true when the current position is overscrolled and in the process of + * interpolating back to a valid value. + */ + public boolean isOverScrolled() { + return ((!mScrollerX.mFinished && + mScrollerX.mState != MagneticOverScroller.TO_EDGE) || + (!mScrollerY.mFinished && + mScrollerY.mState != MagneticOverScroller.TO_EDGE)); + } + + /** + * Stops the animation. Contrary to {@link #forceFinished(boolean)}, + * aborting the animating causes the scroller to move to the final x and y + * positions. + * + * @see #forceFinished(boolean) + */ + public void abortAnimation() { + mScrollerX.finish(); + mScrollerY.finish(); + } + + /** + * Returns the time elapsed since the beginning of the scrolling. + * + * @return The elapsed time in milliseconds. + * + * @hide + */ + public int timePassed() { + final long time = AnimationUtils.currentAnimationTimeMillis(); + final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime); + return (int) (time - startTime); + } + + /** + * @hide + */ + public boolean isScrollingInDirection(float xvel, float yvel) { + final int dx = mScrollerX.mFinal - mScrollerX.mStart; + final int dy = mScrollerY.mFinal - mScrollerY.mStart; + return !isFinished() && Math.signum(xvel) == Math.signum(dx) && + Math.signum(yvel) == Math.signum(dy); + } + + class MagneticOverScroller { + // Initial position + int mStart; + + // Current position + int mCurrentPosition; + + // Final position + int mFinal; + + // Initial velocity + int mVelocity; + + // Current velocity + float mCurrVelocity; + + // Constant current deceleration + float mDeceleration; + + // Animation starting time, in system milliseconds + long mStartTime; + + // Animation duration, in milliseconds + int mDuration; + + // Duration to complete spline component of animation + int mSplineDuration; + + // Distance to travel along spline animation + int mSplineDistance; + + // Whether the animation is currently in progress + boolean mFinished; + + private static final int TO_EDGE = 0; + private static final int TO_BOUNDARY = 1; + private static final int TO_BOUNCE = 2; + + private int mState = TO_EDGE; + + // The allowed overshot distance before boundary is reached. + private int mOver; + + // If the velocity is smaller than this value, no bounce is triggered + // when the edge limits are reached (would result in a zero pixels + // displacement anyway). + private static final float MINIMUM_VELOCITY_FOR_BOUNCE = 140.0f; //Float.MAX_VALUE;//140.0f; + + // Proportion of the velocity that is preserved when the edge is reached. + private static final float DEFAULT_BOUNCE_COEFFICIENT = 0.36f; + + private float mBounceCoefficient = DEFAULT_BOUNCE_COEFFICIENT; + + MagneticOverScroller() { + mFinished = true; + } + + void updateScroll(float q) { + mCurrentPosition = mStart + Math.round(q * (mFinal - mStart)); + } + + /* + * Get a signed deceleration that will reduce the velocity. + */ + float getDeceleration(int velocity) { + return velocity > 0 ? -OverScroller.this.mDeceleration : OverScroller.this.mDeceleration; + } + + /* + * Modifies mDuration to the duration it takes to get from start to newFinal using the + * spline interpolation. The previous duration was needed to get to oldFinal. + */ + void adjustDuration(int start, int oldFinal, int newFinal) { + final int oldDistance = oldFinal - start; + final int newDistance = newFinal - start; + final float x = (float) Math.abs((float) newDistance / oldDistance); + final int index = (int) (NB_SAMPLES * x); + if (index < NB_SAMPLES) { + final float x_inf = (float) index / NB_SAMPLES; + final float x_sup = (float) (index + 1) / NB_SAMPLES; + final float t_inf = SPLINE_TIME[index]; + final float t_sup = SPLINE_TIME[index + 1]; + final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf); + + mDuration *= timeCoef; + } + } + + void startScroll(int start, int distance, int duration) { + mFinished = false; + + mStart = start; + mFinal = start + distance; + + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = duration; + + // Unused + mDeceleration = 0.0f; + mVelocity = 0; + } + + void finish() { + mCurrentPosition = mFinal; + // Not reset since WebView relies on this value for fast fling. + // TODO: restore when WebView uses the fast fling implemented in this class. + // mCurrVelocity = 0.0f; + mFinished = true; + } + + void setFinalPosition(int position) { + mFinal = position; + mFinished = false; + } + + void extendDuration(int extend) { + final long time = AnimationUtils.currentAnimationTimeMillis(); + final int elapsedTime = (int) (time - mStartTime); + mDuration = elapsedTime + extend; + mFinished = false; + } + + void setBounceCoefficient(float coefficient) { + mBounceCoefficient = coefficient; + } + + boolean springback(int start, int min, int max) { + mFinished = true; + + mStart = mFinal = start; + mVelocity = 0; + + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = 0; + + if (start < min) { + startSpringback(start, min, 0); + } else if (start > max) { + startSpringback(start, max, 0); + } + + return !mFinished; + } + + private void startSpringback(int start, int end, int velocity) { + mFinished = false; + mState = TO_BOUNCE; + mStart = mFinal = end; + final float velocitySign = Math.signum(start - end); + mDeceleration = getDeceleration((int) velocitySign); + fitOnBounceCurve(start, end, velocity); + mDuration = - (int) (2000.0f * mVelocity / mDeceleration); + } + + void fling(int start, int velocity, int min, int max, int over) { + mOver = over; + mFinished = false; + mCurrVelocity = mVelocity = velocity; + mDuration = mSplineDuration = 0; + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mStart = start; + + if (start > max || start < min) { + startAfterEdge(start, min, max, velocity); + return; + } + + mState = TO_EDGE; + double totalDistance = 0.0; + + if (velocity != 0) { + final double l = Math.log(START_TENSION * Math.abs(velocity) / ALPHA); + // Duration are expressed in milliseconds + mDuration = mSplineDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0))); + totalDistance = (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l)); + } + + mSplineDistance = (int) (totalDistance * Math.signum(velocity)); + mFinal = start + mSplineDistance; + + // Clamp to a valid final position + if (mFinal < min) { + adjustDuration(mStart, mFinal, min); + mFinal = min; + } + + if (mFinal > max) { + adjustDuration(mStart, mFinal, max); + mFinal = max; + } + } + + private void fitOnBounceCurve(int start, int end, int velocity) { + // Simulate a bounce that started from edge + final float durationToApex = - velocity / mDeceleration; + final float distanceToApex = velocity * velocity / 2.0f / Math.abs(mDeceleration); + final float distanceToEdge = Math.abs(end - start); + final float totalDuration = (float) Math.sqrt( + 2.0 * (distanceToApex + distanceToEdge) / Math.abs(mDeceleration)); + mStartTime -= (int) (1000.0f * (totalDuration - durationToApex)); + mStart = end; + mVelocity = (int) (- mDeceleration * totalDuration); + } + + private void startBounceAfterEdge(int start, int end, int velocity) { + mDeceleration = getDeceleration(velocity == 0 ? start - end : velocity); + fitOnBounceCurve(start, end, velocity); + onEdgeReached(); + } + + private void startAfterEdge(int start, int min, int max, int velocity) { + if (start > min && start < max) { + mFinished = true; + return; + } + final boolean positive = start > max; + final int edge = positive ? max : min; + final int overDistance = start - edge; + boolean keepIncreasing = overDistance * velocity >= 0; + if (keepIncreasing) { + // Will result in a bounce or a to_boundary depending on velocity. + startBounceAfterEdge(start, edge, velocity); + } else { + final double l = Math.log(START_TENSION * Math.abs(velocity) / ALPHA); + final double totalDistance = + (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l)); + if (totalDistance > Math.abs(overDistance)) { + fling(start, velocity, positive ? min : start, positive ? start : max, mOver); + } else { + startSpringback(start, edge, velocity); + } + } + } + + void notifyEdgeReached(int start, int end, int over) { + mOver = over; + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + // We were in fling/scroll mode before: current velocity is such that distance to edge + // is increasing. Ensures that startAfterEdge will not start a new fling. + startAfterEdge(start, end, end, (int) mCurrVelocity); + } + + private void onEdgeReached() { + // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached. + final float distance = - mVelocity * mVelocity / (2.0f * mDeceleration); + + if (Math.abs(distance) < mOver) { + // Spring force will bring us back to final position + mState = TO_BOUNCE; + mFinal = mStart; + mDuration = - (int) (2000.0f * mVelocity / mDeceleration); + } else { + // Velocity is too high, we will hit the boundary limit + mState = TO_BOUNDARY; + int over = mVelocity > 0 ? mOver : -mOver; + mFinal = mStart + over; + mDuration = (int) (1000.0 * Math.PI * over / 2.0 / mVelocity); + } + } + + boolean continueWhenFinished() { + switch (mState) { + case TO_EDGE: + // Duration from start to null velocity + if (mDuration < mSplineDuration) { + // If the animation was clamped, we reached the edge + mStart = mFinal; + // Speed when edge was reached + mVelocity = (int) mCurrVelocity; + mDeceleration = getDeceleration(mVelocity); + mStartTime += mDuration; + onEdgeReached(); + } else { + // Normal stop, no need to continue + return false; + } + break; + case TO_BOUNDARY: + mStartTime += mDuration; + startSpringback(mFinal, mFinal - (mVelocity > 0 ? mOver:-mOver), 0); + break; + case TO_BOUNCE: + mVelocity = (int) (mVelocity * mBounceCoefficient); + if (Math.abs(mVelocity) < MINIMUM_VELOCITY_FOR_BOUNCE) { + return false; + } + mStartTime += mDuration; + mDuration = - (int) (mVelocity / mDeceleration); + break; + } + + update(); + return true; + } + + /* + * Update the current position and velocity for current time. Returns + * true if update has been done and false if animation duration has been + * reached. + */ + boolean update() { + final long time = AnimationUtils.currentAnimationTimeMillis(); + final long currentTime = time - mStartTime; + + if (currentTime > mDuration) { + return false; + } + + double distance = 0.0; + switch (mState) { + case TO_EDGE: { + final float t = (float) currentTime / mSplineDuration; + final int index = (int) (NB_SAMPLES * t); + float distanceCoef = 1.f; + float velocityCoef = 0.f; + if (index < NB_SAMPLES) { + final float t_inf = (float) index / NB_SAMPLES; + final float t_sup = (float) (index + 1) / NB_SAMPLES; + final float d_inf = SPLINE_POSITION[index]; + final float d_sup = SPLINE_POSITION[index + 1]; + velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); + distanceCoef = d_inf + (t - t_inf) * velocityCoef; + } + + distance = distanceCoef * mSplineDistance; + mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000; + break; + } + + case TO_BOUNCE: { + final float t = currentTime / 1000.0f; + mCurrVelocity = mVelocity + mDeceleration * t; + distance = mVelocity * t + mDeceleration * t * t / 2.0f; + break; + } + + case TO_BOUNDARY: { + final float t = currentTime / 1000.0f; + final float d = t * Math.abs(mVelocity) / mOver; + mCurrVelocity = mVelocity * (float) Math.cos(d); + distance = (mVelocity > 0 ? mOver : -mOver) * Math.sin(d); + break; + } + } + + mCurrentPosition = mStart + (int) Math.round(distance); + return true; + } + } +} diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index eb527eb..900c9ec 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -19,8 +19,12 @@ package android.widget; import com.android.internal.R; import android.content.Context; +import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Canvas; import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.StrictMode; import android.util.AttributeSet; import android.view.FocusFinder; import android.view.KeyEvent; @@ -61,7 +65,9 @@ public class ScrollView extends FrameLayout { private long mLastScroll; private final Rect mTempRect = new Rect(); - private Scroller mScroller; + private OverScroller mScroller; + private EdgeGlow mEdgeGlowTop; + private EdgeGlow mEdgeGlowBottom; /** * Flag to indicate that we are moving focus ourselves. This is so the @@ -115,12 +121,24 @@ public class ScrollView extends FrameLayout { private int mMinimumVelocity; private int mMaximumVelocity; + private int mOverscrollDistance; + private int mOverflingDistance; + /** * ID of the active pointer. This is used to retain consistency during * drags/flings if multiple pointers are used. */ private int mActivePointerId = INVALID_POINTER; - + + /** + * The StrictMode "critical time span" objects to catch animation + * stutters. Non-null when a time-sensitive animation is + * in-flight. Must call finish() on them when done animating. + * These are no-ops on user builds. + */ + private StrictMode.Span mScrollStrictSpan = null; // aka "drag" + private StrictMode.Span mFlingStrictSpan = null; + /** * Sentinel value for no current active pointer. * Used by {@link #mActivePointerId}. @@ -187,7 +205,7 @@ public class ScrollView extends FrameLayout { private void initScrollView() { - mScroller = new Scroller(getContext()); + mScroller = new OverScroller(getContext()); setFocusable(true); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setWillNotDraw(false); @@ -195,6 +213,8 @@ public class ScrollView extends FrameLayout { mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mOverscrollDistance = configuration.getScaledOverscrollDistance(); + mOverflingDistance = configuration.getScaledOverflingDistance(); } @Override @@ -427,6 +447,9 @@ public class ScrollView extends FrameLayout { if (yDiff > mTouchSlop) { mIsBeingDragged = true; mLastMotionY = y; + if (mScrollStrictSpan == null) { + mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll"); + } } break; } @@ -451,6 +474,9 @@ public class ScrollView extends FrameLayout { * being flinged. */ mIsBeingDragged = !mScroller.isFinished(); + if (mIsBeingDragged && mScrollStrictSpan == null) { + mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll"); + } break; } @@ -459,6 +485,9 @@ public class ScrollView extends FrameLayout { /* Release the drag */ mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; + if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { + invalidate(); + } break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); @@ -491,9 +520,7 @@ public class ScrollView extends FrameLayout { switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { final float y = ev.getY(); - if (!(mIsBeingDragged = inChild((int) ev.getX(), (int) y))) { - return false; - } + mIsBeingDragged = true; /* * If being flinged and user touches, stop the fling. isFinished @@ -501,6 +528,10 @@ public class ScrollView extends FrameLayout { */ if (!mScroller.isFinished()) { mScroller.abortAnimation(); + if (mFlingStrictSpan != null) { + mFlingStrictSpan.finish(); + mFlingStrictSpan = null; + } } // Remember where the motion event started @@ -516,7 +547,36 @@ public class ScrollView extends FrameLayout { final int deltaY = (int) (mLastMotionY - y); mLastMotionY = y; - scrollBy(0, deltaY); + final int oldX = mScrollX; + final int oldY = mScrollY; + final int range = getScrollRange(); + if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, + 0, mOverscrollDistance, true)) { + // Break our velocity if we hit a scroll barrier. + mVelocityTracker.clear(); + } + onScrollChanged(mScrollX, mScrollY, oldX, oldY); + + final int overscrollMode = getOverScrollMode(); + if (overscrollMode == OVER_SCROLL_ALWAYS || + (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0)) { + final int pulledToY = oldY + deltaY; + if (pulledToY < 0) { + mEdgeGlowTop.onPull((float) deltaY / getHeight()); + if (!mEdgeGlowBottom.isFinished()) { + mEdgeGlowBottom.onRelease(); + } + } else if (pulledToY > range) { + mEdgeGlowBottom.onPull((float) deltaY / getHeight()); + if (!mEdgeGlowTop.isFinished()) { + mEdgeGlowTop.onRelease(); + } + } + if (mEdgeGlowTop != null + && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) { + invalidate(); + } + } } break; case MotionEvent.ACTION_UP: @@ -525,27 +585,28 @@ public class ScrollView extends FrameLayout { velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); - if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) { - fling(-initialVelocity); + if (getChildCount() > 0) { + if ((Math.abs(initialVelocity) > mMinimumVelocity)) { + fling(-initialVelocity); + } else { + final int bottom = getScrollRange(); + if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, bottom)) { + invalidate(); + } + } } mActivePointerId = INVALID_POINTER; - mIsBeingDragged = false; - - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } + endDrag(); } break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged && getChildCount() > 0) { - mActivePointerId = INVALID_POINTER; - mIsBeingDragged = false; - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; + if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { + invalidate(); } + mActivePointerId = INVALID_POINTER; + endDrag(); } break; case MotionEvent.ACTION_POINTER_UP: @@ -572,6 +633,32 @@ public class ScrollView extends FrameLayout { } } + @Override + protected void onOverScrolled(int scrollX, int scrollY, + boolean clampedX, boolean clampedY) { + // Treat animating scrolls differently; see #computeScroll() for why. + if (!mScroller.isFinished()) { + mScrollX = scrollX; + mScrollY = scrollY; + if (clampedY) { + mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange()); + } + } else { + super.scrollTo(scrollX, scrollY); + } + awakenScrollBars(); + } + + private int getScrollRange() { + int scrollRange = 0; + if (getChildCount() > 0) { + View child = getChildAt(0); + scrollRange = Math.max(0, + child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop)); + } + return scrollRange; + } + /** * <p> * Finds the next focusable component that fits in this View's bounds @@ -920,6 +1007,10 @@ public class ScrollView extends FrameLayout { } else { if (!mScroller.isFinished()) { mScroller.abortAnimation(); + if (mFlingStrictSpan != null) { + mFlingStrictSpan.finish(); + mFlingStrictSpan = null; + } } scrollBy(dx, dy); } @@ -948,7 +1039,16 @@ public class ScrollView extends FrameLayout { return contentHeight; } - return getChildAt(0).getBottom(); + int scrollRange = getChildAt(0).getBottom(); + final int scrollY = mScrollY; + final int overscrollBottom = Math.max(0, scrollRange - contentHeight); + if (scrollY < 0) { + scrollRange -= scrollY; + } else if (scrollY > overscrollBottom) { + scrollRange += scrollY - overscrollBottom; + } + + return scrollRange; } @Override @@ -1009,20 +1109,31 @@ public class ScrollView extends FrameLayout { int x = mScroller.getCurrX(); int y = mScroller.getCurrY(); - if (getChildCount() > 0) { - View child = getChildAt(0); - x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth()); - y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight()); - if (x != oldX || y != oldY) { - mScrollX = x; - mScrollY = y; - onScrollChanged(x, y, oldX, oldY); + if (oldX != x || oldY != y) { + overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, getScrollRange(), + 0, mOverflingDistance, false); + onScrollChanged(mScrollX, mScrollY, oldX, oldY); + + final int range = getScrollRange(); + final int overscrollMode = getOverScrollMode(); + if (overscrollMode == OVER_SCROLL_ALWAYS || + (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0)) { + if (y < 0 && oldY >= 0) { + mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); + } else if (y > range && oldY <= range) { + mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); + } } } awakenScrollBars(); // Keep on drawing until the animation has finished. postInvalidate(); + } else { + if (mFlingStrictSpan != null) { + mFlingStrictSpan.finish(); + mFlingStrictSpan = null; + } } } @@ -1197,6 +1308,20 @@ public class ScrollView extends FrameLayout { } @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mScrollStrictSpan != null) { + mScrollStrictSpan.finish(); + mScrollStrictSpan = null; + } + if (mFlingStrictSpan != null) { + mFlingStrictSpan.finish(); + mFlingStrictSpan = null; + } + } + + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mIsLayoutDirty = false; @@ -1254,7 +1379,7 @@ public class ScrollView extends FrameLayout { int bottom = getChildAt(0).getHeight(); mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, - Math.max(0, bottom - height)); + Math.max(0, bottom - height), 0, height/2); final boolean movingDown = velocityY > 0; @@ -1269,11 +1394,34 @@ public class ScrollView extends FrameLayout { mScrollViewMovedFocus = true; mScrollViewMovedFocus = false; } - + + if (mFlingStrictSpan == null) { + mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling"); + } + invalidate(); } } + private void endDrag() { + mIsBeingDragged = false; + + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + + if (mEdgeGlowTop != null) { + mEdgeGlowTop.onRelease(); + mEdgeGlowBottom.onRelease(); + } + + if (mScrollStrictSpan != null) { + mScrollStrictSpan.finish(); + mScrollStrictSpan = null; + } + } + /** * {@inheritDoc} * @@ -1292,6 +1440,55 @@ public class ScrollView extends FrameLayout { } } + @Override + public void setOverScrollMode(int mode) { + if (mode != OVER_SCROLL_NEVER) { + if (mEdgeGlowTop == null) { + final Resources res = getContext().getResources(); + final Drawable edge = res.getDrawable(R.drawable.overscroll_edge); + final Drawable glow = res.getDrawable(R.drawable.overscroll_glow); + mEdgeGlowTop = new EdgeGlow(edge, glow); + mEdgeGlowBottom = new EdgeGlow(edge, glow); + } + } else { + mEdgeGlowTop = null; + mEdgeGlowBottom = null; + } + super.setOverScrollMode(mode); + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + if (mEdgeGlowTop != null) { + final int scrollY = mScrollY; + if (!mEdgeGlowTop.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + + canvas.translate(-width / 2, Math.min(0, scrollY)); + mEdgeGlowTop.setSize(width * 2, getHeight()); + if (mEdgeGlowTop.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowBottom.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + final int height = getHeight(); + + canvas.translate(-width / 2, Math.max(getScrollRange(), scrollY) + height); + canvas.rotate(180, width, 0); + mEdgeGlowBottom.setSize(width * 2, height); + if (mEdgeGlowBottom.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + } + } + private int clamp(int n, int my, int child) { if (my >= child || n < 0) { /* my >= child is this case: diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java index b1f50ba..f00640e 100644 --- a/core/java/android/widget/Scroller.java +++ b/core/java/android/widget/Scroller.java @@ -52,14 +52,10 @@ public class Scroller { private float mDurationReciprocal; private float mDeltaX; private float mDeltaY; - private float mViscousFluidScale; - private float mViscousFluidNormalize; private boolean mFinished; private Interpolator mInterpolator; private boolean mFlywheel; - private float mCoeffX = 0.0f; - private float mCoeffY = 1.0f; private float mVelocity; private static final int DEFAULT_DURATION = 250; @@ -94,8 +90,17 @@ public class Scroller { SPLINE[i] = d; } SPLINE[NB_SAMPLES] = 1.0f; + + // This controls the viscous fluid effect (how much of it) + sViscousFluidScale = 8.0f; + // must be set to 1.0 (used in viscousFluid()) + sViscousFluidNormalize = 1.0f; + sViscousFluidNormalize = 1.0f / viscousFluid(1.0f); } + private static float sViscousFluidScale; + private static float sViscousFluidNormalize; + /** * Create a Scroller with the default duration and interpolator. */ @@ -103,6 +108,11 @@ public class Scroller { this(context, null); } + /** + * Create a Scroller with the specified interpolator. If the interpolator is + * null, the default (viscous) interpolator will be used. "Flywheel" behavior will + * be in effect for apps targeting Honeycomb or newer. + */ public Scroller(Context context, Interpolator interpolator) { this(context, interpolator, context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB); @@ -110,7 +120,8 @@ public class Scroller { /** * Create a Scroller with the specified interpolator. If the interpolator is - * null, the default (viscous) interpolator will be used. + * null, the default (viscous) interpolator will be used. Specify whether or + * not to support progressive "flywheel" behavior in flinging. */ public Scroller(Context context, Interpolator interpolator, boolean flywheel) { mFinished = true; @@ -332,12 +343,7 @@ public class Scroller { mFinalY = startY + dy; mDeltaX = dx; mDeltaY = dy; - mDurationReciprocal = 1.0f / mDuration; - // This controls the viscous fluid effect (how much of it) - mViscousFluidScale = 8.0f; - // must be set to 1.0 (used in viscousFluid()) - mViscousFluidNormalize = 1.0f; - mViscousFluidNormalize = 1.0f / viscousFluid(1.0f); + mDurationReciprocal = 1.0f / (float) mDuration; } /** @@ -393,8 +399,8 @@ public class Scroller { mStartX = startX; mStartY = startY; - mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity; - mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity; + float coeffX = velocity == 0 ? 1.0f : velocityX / velocity; + float coeffY = velocity == 0 ? 1.0f : velocityY / velocity; int totalDistance = (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l)); @@ -404,22 +410,20 @@ public class Scroller { mMinY = minY; mMaxY = maxY; - mFinalX = startX + Math.round(totalDistance * mCoeffX); + mFinalX = startX + Math.round(totalDistance * coeffX); // Pin to mMinX <= mFinalX <= mMaxX mFinalX = Math.min(mFinalX, mMaxX); mFinalX = Math.max(mFinalX, mMinX); - mFinalY = startY + Math.round(totalDistance * mCoeffY); + mFinalY = startY + Math.round(totalDistance * coeffY); // Pin to mMinY <= mFinalY <= mMaxY mFinalY = Math.min(mFinalY, mMaxY); mFinalY = Math.max(mFinalY, mMinY); } - - - private float viscousFluid(float x) + static float viscousFluid(float x) { - x *= mViscousFluidScale; + x *= sViscousFluidScale; if (x < 1.0f) { x -= (1.0f - (float)Math.exp(-x)); } else { @@ -427,7 +431,7 @@ public class Scroller { x = 1.0f - (float)Math.exp(1.0f - x); x = start + x * (1.0f - start); } - x *= mViscousFluidNormalize; + x *= sViscousFluidNormalize; return x; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 80a6a2f..9f9fb18 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -7565,9 +7565,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void selectCurrentWord() { - // In case selection mode is started after an orientation change or after a select all, - // use the current selection instead of creating one - if (hasSelection()) { + if (hasPasswordTransformationMethod()) { + // selectCurrentWord is not available on a password field and would return an + // arbitrary 10-charater selection around pressed position. Select all instead. + // Note that cut/copy menu entries are not available for passwords. + // This is however useful to delete or paste to replace the entire content. + Selection.setSelection((Spannable) mText, 0, mText.length()); return; } @@ -7835,13 +7838,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; } - if (mSelectionActionMode != null && touchPositionIsInSelection()) { - final int start = getSelectionStart(); - final int end = getSelectionEnd(); - CharSequence selectedText = mTransformed.subSequence(start, end); - ClipData data = ClipData.newPlainText(null, null, selectedText); - startDrag(data, getTextThumbnailBuilder(selectedText), false); - stopSelectionActionMode(); + if (mSelectionActionMode != null) { + if (touchPositionIsInSelection()) { + // Start a drag + final int start = getSelectionStart(); + final int end = getSelectionEnd(); + CharSequence selectedText = mTransformed.subSequence(start, end); + ClipData data = ClipData.newPlainText(null, null, selectedText); + startDrag(data, getTextThumbnailBuilder(selectedText), false); + stopSelectionActionMode(); + } else { + selectCurrentWord(); + getSelectionController().show(); + } performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); mEatTouchRelease = true; return true; @@ -7886,9 +7895,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * mode is not available. */ private ActionMode.Callback getActionModeCallback() { - // Long press in the current selection. - // Should initiate a drag. Return false, to rely on context menu for now. - if (canSelectText() && !touchPositionIsInSelection()) { + if (canSelectText()) { return new SelectionActionModeCallback(); } return null; @@ -7983,15 +7990,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean atLeastOne = false; if (canSelectText()) { - if (hasPasswordTransformationMethod()) { - // selectCurrentWord is not available on a password field and would return an - // arbitrary 10-charater selection around pressed position. Select all instead. - // Note that cut/copy menu entries are not available for passwords. - // This is however useful to delete or paste to replace the entire content. - Selection.setSelection((Spannable) mText, 0, mText.length()); - } else { - selectCurrentWord(); - } + selectCurrentWord(); menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll). setAlphabeticShortcut('a'). @@ -9015,7 +9014,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mUserSetTextScaleX; private final Paint mHighlightPaint; private int mHighlightColor = 0xCC475925; - private Layout mLayout; + /** + * This is temporarily visible to fix bug 3085564 in webView. Do not rely on + * this field being protected. Will be restored as private when lineHeight + * feature request 3215097 is implemented + * @hide + */ + protected Layout mLayout; private long mShowCursor; private Blink mBlink; diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java index ada7f36..1984167 100644 --- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java +++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java @@ -120,7 +120,7 @@ public class HeavyWeightSwitcherActivity extends Activity { private OnClickListener mSwitchOldListener = new OnClickListener() { public void onClick(View v) { try { - ActivityManagerNative.getDefault().moveTaskToFront(mCurTask); + ActivityManagerNative.getDefault().moveTaskToFront(mCurTask, 0); } catch (RemoteException e) { } finish(); diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 63d05ec..a2e9486 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -32,6 +32,9 @@ interface IInputMethodManager { List<InputMethodInfo> getInputMethodList(); List<InputMethodInfo> getEnabledInputMethodList(); List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in InputMethodInfo imi); + // TODO: We should change the return type from List to List<Parcelable> + // Currently there is a bug that aidl doesn't accept List<Parcelable> + List getShortcutInputMethodsAndSubtypes(); void addClient(in IInputMethodClient client, in IInputContext inputContext, int uid, int pid); void removeClient(in IInputMethodClient client); @@ -52,6 +55,7 @@ interface IInputMethodManager { void showInputMethodSubtypePickerFromClient(in IInputMethodClient client); void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId); void setInputMethod(in IBinder token, String id); + void setInputMethodAndSubtype(in IBinder token, String id, in InputMethodSubtype subtype); void hideMySoftInput(in IBinder token, int flags); void showMySoftInput(in IBinder token, int flags); void updateStatusIcon(in IBinder token, String packageName, int iconId); diff --git a/core/res/res/anim/activity_close_enter.xml b/core/res/res/anim/activity_close_enter.xml index 3c971da..82d990c 100644 --- a/core/res/res/anim/activity_close_enter.xml +++ b/core/res/res/anim/activity_close_enter.xml @@ -18,8 +18,14 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="normal" android:shareInterpolator="false"> - <!-- Do nothing. --> - <alpha android:fromAlpha="1.0" android:toAlpha="1.0" - android:duration="@android:integer/config_mediumAnimTime"/> + <scale android:fromXScale="0.98" android:toXScale="1.0" + android:fromYScale="0.98" android:toYScale="1.0" + android:pivotX="50%p" android:pivotY="50%p" + android:interpolator="@anim/decelerate_quint_interpolator" + android:duration="@android:integer/config_activityDefaultDur" /> + <alpha android:fromAlpha=".75" android:toAlpha="1.0" + android:interpolator="@anim/decelerate_quint_interpolator" + android:duration="@android:integer/config_activityDefaultDur"/> </set> diff --git a/core/res/res/anim/activity_close_exit.xml b/core/res/res/anim/activity_close_exit.xml index 2001e20..13768b5 100644 --- a/core/res/res/anim/activity_close_exit.xml +++ b/core/res/res/anim/activity_close_exit.xml @@ -18,13 +18,15 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="top" android:shareInterpolator="false"> - <scale android:fromXScale="1.0" android:toXScale="1.0" - android:fromYScale="1.0" android:toYScale="0.9" + <scale android:fromXScale="1.0" android:toXScale="1.04" + android:fromYScale="1.0" android:toYScale="1.04" android:pivotX="50%p" android:pivotY="50%p" android:interpolator="@anim/decelerate_quint_interpolator" - android:duration="@android:integer/config_mediumAnimTime" /> - <alpha android:fromAlpha="1.0" android:toAlpha="0" - android:interpolator="@anim/decelerate_cubic_interpolator" - android:duration="@android:integer/config_mediumAnimTime"/> + android:duration="@android:integer/config_activityDefaultDur" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@anim/decelerate_quint_interpolator" + android:duration="@android:integer/config_activityDefaultDur"/> + </set> diff --git a/core/res/res/anim/activity_open_enter.xml b/core/res/res/anim/activity_open_enter.xml index 6c62e61..4841910 100644 --- a/core/res/res/anim/activity_open_enter.xml +++ b/core/res/res/anim/activity_open_enter.xml @@ -18,13 +18,15 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="top" android:shareInterpolator="false"> - <scale android:fromXScale="1.0" android:toXScale="1.0" - android:fromYScale="0.9" android:toYScale="1.0" + <scale android:fromXScale="1.04" android:toXScale="1.0" + android:fromYScale="1.04" android:toYScale="1.0" android:pivotX="50%p" android:pivotY="50%p" android:interpolator="@anim/decelerate_quint_interpolator" - android:duration="@android:integer/config_mediumAnimTime" /> - <alpha android:fromAlpha="0" android:toAlpha="1.0" - android:interpolator="@anim/decelerate_cubic_interpolator" - android:duration="@android:integer/config_mediumAnimTime"/> + android:duration="@android:integer/config_activityDefaultDur" /> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:interpolator="@anim/decelerate_quint_interpolator" + android:duration="@android:integer/config_activityDefaultDur"/> + </set> diff --git a/core/res/res/anim/activity_open_exit.xml b/core/res/res/anim/activity_open_exit.xml index 3c971da..81e902f 100644 --- a/core/res/res/anim/activity_open_exit.xml +++ b/core/res/res/anim/activity_open_exit.xml @@ -18,8 +18,15 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="normal" android:shareInterpolator="false"> - <!-- Do nothing. --> - <alpha android:fromAlpha="1.0" android:toAlpha="1.0" - android:duration="@android:integer/config_mediumAnimTime"/> + <scale android:fromXScale="1.0" android:toXScale="0.98" + android:fromYScale="1.0" android:toYScale="0.98" + android:pivotX="50%p" android:pivotY="50%p" + android:interpolator="@anim/decelerate_quint_interpolator" + android:duration="@android:integer/config_activityDefaultDur" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.75" + android:interpolator="@anim/decelerate_quint_interpolator" + android:duration="@android:integer/config_activityDefaultDur"/> + </set> diff --git a/core/res/res/anim/dialog_enter.xml b/core/res/res/anim/dialog_enter.xml index d4983c6..82bfdf6 100644 --- a/core/res/res/anim/dialog_enter.xml +++ b/core/res/res/anim/dialog_enter.xml @@ -18,11 +18,13 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator"> + android:shareInterpolator="false" > <scale android:fromXScale="0.9" android:toXScale="1.0" android:fromYScale="0.9" android:toYScale="1.0" android:pivotX="50%" android:pivotY="50%" - android:duration="@android:integer/config_shortAnimTime" /> + android:interpolator="@anim/decelerate_quint_interpolator" + android:duration="@android:integer/config_activityDefaultDur" /> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="@android:integer/config_shortAnimTime" /> + android:interpolator="@anim/decelerate_cubic_interpolator" + android:duration="@android:integer/config_activityShortDur" /> </set> diff --git a/core/res/res/anim/dialog_exit.xml b/core/res/res/anim/dialog_exit.xml index 2aa629a..829c619 100644 --- a/core/res/res/anim/dialog_exit.xml +++ b/core/res/res/anim/dialog_exit.xml @@ -18,11 +18,13 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/accelerate_interpolator"> + android:shareInterpolator="false" > <scale android:fromXScale="1.0" android:toXScale="0.9" - android:fromYScale="1.0" android:toYScale="0.9" - android:pivotX="50%" android:pivotY="50%" - android:duration="@android:integer/config_shortAnimTime" /> + android:fromYScale="1.0" android:toYScale="0.9" + android:pivotX="50%" android:pivotY="50%" + android:interpolator="@anim/decelerate_quint_interpolator" + android:duration="@android:integer/config_activityDefaultDur" /> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="@android:integer/config_shortAnimTime"/> + android:interpolator="@anim/decelerate_cubic_interpolator" + android:duration="@android:integer/config_activityShortDur"/> </set> diff --git a/core/res/res/anim/fragment_close_enter.xml b/core/res/res/anim/fragment_close_enter.xml index 1c170e3..c1b564b 100644 --- a/core/res/res/anim/fragment_close_enter.xml +++ b/core/res/res/anim/fragment_close_enter.xml @@ -16,12 +16,29 @@ ** limitations under the License. */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="normal"> + + <objectAnimator + android:interpolator="@anim/decelerate_quint_interpolator" + android:valueFrom="0.98" + android:valueTo="1.0" + android:valueType="floatType" + android:propertyName="scaleY" + android:duration="@android:integer/config_activityDefaultDur"/> + <objectAnimator + android:interpolator="@anim/decelerate_quint_interpolator" + android:valueFrom="0.98" + android:valueTo="1.0" + android:valueType="floatType" + android:propertyName="scaleX" + android:duration="@android:integer/config_activityDefaultDur"/> <objectAnimator android:interpolator="@anim/decelerate_cubic_interpolator" - android:valueFrom="0" - android:valueTo="1" + android:valueFrom="0.0" + android:valueTo="1.0" android:valueType="floatType" android:propertyName="alpha" - android:duration="@android:integer/config_mediumAnimTime"/> + android:duration="@android:integer/config_activityDefaultDur"/> + </set>
\ No newline at end of file diff --git a/core/res/res/anim/fragment_close_exit.xml b/core/res/res/anim/fragment_close_exit.xml index a084d63..2ce358f 100644 --- a/core/res/res/anim/fragment_close_exit.xml +++ b/core/res/res/anim/fragment_close_exit.xml @@ -16,19 +16,28 @@ ** limitations under the License. */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="top"> <objectAnimator - android:interpolator="@anim/decelerate_cubic_interpolator" - android:valueFrom="1" - android:valueTo="0" + android:interpolator="@anim/decelerate_quint_interpolator" + android:valueFrom="1.0" + android:valueTo="1.04" android:valueType="floatType" - android:propertyName="alpha" - android:duration="@android:integer/config_mediumAnimTime"/> + android:propertyName="scaleY" + android:duration="@android:integer/config_activityDefaultDur"/> <objectAnimator android:interpolator="@anim/decelerate_quint_interpolator" - android:valueFrom="1" - android:valueTo=".8" + android:valueFrom="1.0" + android:valueTo="1.04" android:valueType="floatType" - android:propertyName="scaleY" - android:duration="@android:integer/config_mediumAnimTime"/> + android:propertyName="scaleX" + android:duration="@android:integer/config_activityDefaultDur"/> + <objectAnimator + android:interpolator="@anim/decelerate_cubic_interpolator" + android:valueFrom="1.0" + android:valueTo="0.0" + android:valueType="floatType" + android:propertyName="alpha" + android:duration="@android:integer/config_activityDefaultDur"/> + </set>
\ No newline at end of file diff --git a/core/res/res/anim/fragment_next_enter.xml b/core/res/res/anim/fragment_next_enter.xml index d3ff7a6..63d3a45 100644 --- a/core/res/res/anim/fragment_next_enter.xml +++ b/core/res/res/anim/fragment_next_enter.xml @@ -16,19 +16,13 @@ ** limitations under the License. */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="top"> <objectAnimator android:interpolator="@anim/decelerate_cubic_interpolator" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" android:propertyName="alpha" - android:duration="@android:integer/config_mediumAnimTime"/> - <objectAnimator - android:interpolator="@anim/decelerate_quint_interpolator" - android:valueFrom="50" - android:valueTo="0" - android:valueType="floatType" - android:propertyName="translationY" - android:duration="@android:integer/config_mediumAnimTime"/> + android:duration="@android:integer/config_activityDefaultDur"/> </set>
\ No newline at end of file diff --git a/core/res/res/anim/fragment_next_exit.xml b/core/res/res/anim/fragment_next_exit.xml index e08804f..91e5786 100644 --- a/core/res/res/anim/fragment_next_exit.xml +++ b/core/res/res/anim/fragment_next_exit.xml @@ -16,19 +16,13 @@ ** limitations under the License. */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="normal"> <objectAnimator android:interpolator="@anim/decelerate_cubic_interpolator" android:valueFrom="1" android:valueTo="0" android:valueType="floatType" android:propertyName="alpha" - android:duration="@android:integer/config_mediumAnimTime"/> - <objectAnimator - android:interpolator="@anim/decelerate_quint_interpolator" - android:valueFrom="0" - android:valueTo="-50" - android:valueType="floatType" - android:propertyName="translationY" - android:duration="@android:integer/config_mediumAnimTime"/> + android:duration="@android:integer/config_activityShortDur"/> </set>
\ No newline at end of file diff --git a/core/res/res/anim/fragment_open_enter.xml b/core/res/res/anim/fragment_open_enter.xml index 9e625a9..e3bde46 100644 --- a/core/res/res/anim/fragment_open_enter.xml +++ b/core/res/res/anim/fragment_open_enter.xml @@ -18,17 +18,25 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator - android:interpolator="@anim/decelerate_cubic_interpolator" - android:valueFrom="0" - android:valueTo="1" + android:interpolator="@anim/decelerate_quint_interpolator" + android:valueFrom="1.04" + android:valueTo="1.0" android:valueType="floatType" - android:propertyName="alpha" - android:duration="@android:integer/config_mediumAnimTime"/> + android:propertyName="scaleY" + android:duration="@android:integer/config_activityDefaultDur"/> <objectAnimator android:interpolator="@anim/decelerate_quint_interpolator" - android:valueFrom=".8" - android:valueTo="1" + android:valueFrom="1.04" + android:valueTo="1.0" android:valueType="floatType" - android:propertyName="scaleY" - android:duration="@android:integer/config_mediumAnimTime"/> + android:propertyName="scaleX" + android:duration="@android:integer/config_activityDefaultDur"/> + <objectAnimator + android:interpolator="@anim/decelerate_cubic_interpolator" + android:valueFrom="0.0" + android:valueTo="1.0" + android:valueType="floatType" + android:propertyName="alpha" + android:duration="@android:integer/config_activityDefaultDur"/> + </set>
\ No newline at end of file diff --git a/core/res/res/anim/fragment_open_exit.xml b/core/res/res/anim/fragment_open_exit.xml index d3278b2..b4c2194 100644 --- a/core/res/res/anim/fragment_open_exit.xml +++ b/core/res/res/anim/fragment_open_exit.xml @@ -18,10 +18,25 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator + android:interpolator="@anim/decelerate_quint_interpolator" + android:valueFrom="1.0" + android:valueTo="0.98" + android:valueType="floatType" + android:propertyName="scaleY" + android:duration="@android:integer/config_activityDefaultDur"/> + <objectAnimator + android:interpolator="@anim/decelerate_quint_interpolator" + android:valueFrom="1.0" + android:valueTo="0.98" + android:valueType="floatType" + android:propertyName="scaleX" + android:duration="@android:integer/config_activityDefaultDur"/> + <objectAnimator android:interpolator="@anim/decelerate_cubic_interpolator" - android:valueFrom="1" - android:valueTo="0" + android:valueFrom="1.0" + android:valueTo="0.0" android:valueType="floatType" android:propertyName="alpha" - android:duration="@android:integer/config_mediumAnimTime"/> + android:duration="@android:integer/config_activityDefaultDur"/> + </set>
\ No newline at end of file diff --git a/core/res/res/anim/fragment_prev_enter.xml b/core/res/res/anim/fragment_prev_enter.xml index 7ae41e6..63d3a45 100644 --- a/core/res/res/anim/fragment_prev_enter.xml +++ b/core/res/res/anim/fragment_prev_enter.xml @@ -16,19 +16,13 @@ ** limitations under the License. */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="top"> <objectAnimator android:interpolator="@anim/decelerate_cubic_interpolator" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" android:propertyName="alpha" - android:duration="@android:integer/config_mediumAnimTime"/> - <objectAnimator - android:interpolator="@anim/decelerate_quint_interpolator" - android:valueFrom="-50" - android:valueTo="0" - android:valueType="floatType" - android:propertyName="translationY" - android:duration="@android:integer/config_mediumAnimTime"/> + android:duration="@android:integer/config_activityDefaultDur"/> </set>
\ No newline at end of file diff --git a/core/res/res/anim/fragment_prev_exit.xml b/core/res/res/anim/fragment_prev_exit.xml index a41f1aa..91e5786 100644 --- a/core/res/res/anim/fragment_prev_exit.xml +++ b/core/res/res/anim/fragment_prev_exit.xml @@ -16,19 +16,13 @@ ** limitations under the License. */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:zAdjustment="normal"> <objectAnimator android:interpolator="@anim/decelerate_cubic_interpolator" android:valueFrom="1" android:valueTo="0" android:valueType="floatType" android:propertyName="alpha" - android:duration="@android:integer/config_mediumAnimTime"/> - <objectAnimator - android:interpolator="@anim/decelerate_quint_interpolator" - android:valueFrom="0" - android:valueTo="50" - android:valueType="floatType" - android:propertyName="translationY" - android:duration="@android:integer/config_mediumAnimTime"/> + android:duration="@android:integer/config_activityShortDur"/> </set>
\ No newline at end of file diff --git a/core/res/res/anim/grow_fade_in.xml b/core/res/res/anim/grow_fade_in.xml index 5dc41cb..9180438 100644 --- a/core/res/res/anim/grow_fade_in.xml +++ b/core/res/res/anim/grow_fade_in.xml @@ -18,12 +18,13 @@ */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - <scale android:fromXScale="1.0" android:toXScale="1.0" - android:fromYScale="0.3" android:toYScale="1.0" - android:pivotX="0%" android:pivotY="0%" - android:duration="@android:integer/config_shortAnimTime" /> - <alpha android:interpolator="@anim/decelerate_interpolator" +<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> + <scale android:interpolator="@anim/decelerate_quint_interpolator" + android:fromXScale="0.9" android:toXScale="1.0" + android:fromYScale="0.9" android:toYScale="1.0" + android:pivotX="50%" android:pivotY="0%" + android:duration="@android:integer/config_activityDefaultDur" /> + <alpha android:interpolator="@anim/decelerate_cubic_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="@android:integer/config_shortAnimTime" /> + android:duration="@android:integer/config_activityShortDur" /> </set> diff --git a/core/res/res/anim/grow_fade_in_center.xml b/core/res/res/anim/grow_fade_in_center.xml index c07a82e..09370e6 100644 --- a/core/res/res/anim/grow_fade_in_center.xml +++ b/core/res/res/anim/grow_fade_in_center.xml @@ -18,12 +18,13 @@ */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - <scale android:fromXScale="0.6" android:toXScale="1.0" - android:fromYScale="0.6" android:toYScale="1.0" - android:pivotX="50%" android:pivotY="50%" - android:duration="@android:integer/config_shortAnimTime" /> - <alpha android:interpolator="@anim/decelerate_interpolator" +<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> + <scale android:interpolator="@anim/decelerate_quint_interpolator" + android:fromXScale="0.9" android:toXScale="1.0" + android:fromYScale="0.9" android:toYScale="1.0" + android:pivotX="50%" android:pivotY="50%" + android:duration="@android:integer/config_activityDefaultDur" /> + <alpha android:interpolator="@anim/decelerate_cubic_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="@android:integer/config_shortAnimTime" /> + android:duration="@android:integer/config_activityShortDur" /> </set> diff --git a/core/res/res/anim/grow_fade_in_from_bottom.xml b/core/res/res/anim/grow_fade_in_from_bottom.xml index 848c677..d1488e9 100644 --- a/core/res/res/anim/grow_fade_in_from_bottom.xml +++ b/core/res/res/anim/grow_fade_in_from_bottom.xml @@ -18,12 +18,13 @@ */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - <scale android:fromXScale="0.5" android:toXScale="1.0" - android:fromYScale="0.5" android:toYScale="1.0" - android:pivotX="0%" android:pivotY="100%" - android:duration="@android:integer/config_shortAnimTime" /> - <alpha android:interpolator="@anim/decelerate_interpolator" +<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> + <scale android:interpolator="@anim/decelerate_quint_interpolator" + android:fromXScale="0.9" android:toXScale="1.0" + android:fromYScale="0.9" android:toYScale="1.0" + android:pivotX="50%" android:pivotY="100%" + android:duration="@android:integer/config_activityDefaultDur" /> + <alpha android:interpolator="@anim/decelerate_cubic_interpolator" android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="@android:integer/config_shortAnimTime" /> + android:duration="@android:integer/config_activityShortDur" /> </set> diff --git a/core/res/res/anim/input_method_enter.xml b/core/res/res/anim/input_method_enter.xml index 3858651..e02b310 100644 --- a/core/res/res/anim/input_method_enter.xml +++ b/core/res/res/anim/input_method_enter.xml @@ -19,9 +19,11 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator"> - <translate android:fromYDelta="20%" android:toYDelta="0" + android:shareInterpolator="false"> + <translate android:fromYDelta="10%" android:toYDelta="0" + android:interpolator="@anim/decelerate_quint_interpolator" android:duration="@android:integer/config_shortAnimTime"/> <alpha android:fromAlpha="0.5" android:toAlpha="1.0" + android:interpolator="@anim/decelerate_cubic_interpolator" android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/input_method_exit.xml b/core/res/res/anim/input_method_exit.xml index 25369ab..e155f97 100644 --- a/core/res/res/anim/input_method_exit.xml +++ b/core/res/res/anim/input_method_exit.xml @@ -18,9 +18,11 @@ */ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/accelerate_interpolator"> - <translate android:fromYDelta="0" android:toYDelta="20%" + android:shareInterpolator="false"> + <translate android:fromYDelta="0" android:toYDelta="10%" + android:interpolator="@anim/accelerate_quint_interpolator" android:duration="@android:integer/config_shortAnimTime"/> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@anim/accelerate_cubic_interpolator" android:duration="@android:integer/config_shortAnimTime"/> </set> diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml index b243223..0bfe806 100644 --- a/core/res/res/anim/lock_screen_behind_enter.xml +++ b/core/res/res/anim/lock_screen_behind_enter.xml @@ -17,7 +17,7 @@ */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator"> +<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_cubic_interpolator"> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="@android:integer/config_longAnimTime" /> + android:duration="@android:integer/config_activityDefaultDur" /> </set> diff --git a/core/res/res/anim/lock_screen_enter.xml b/core/res/res/anim/lock_screen_enter.xml index dd47ff8..0a169fd 100644 --- a/core/res/res/anim/lock_screen_enter.xml +++ b/core/res/res/anim/lock_screen_enter.xml @@ -18,7 +18,7 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator"> + android:interpolator="@anim/accelerate_cubic_interpolator"> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="@android:integer/config_mediumAnimTime" /> + android:duration="@android:integer/config_activityDefaultDur" /> </set> diff --git a/core/res/res/anim/lock_screen_exit.xml b/core/res/res/anim/lock_screen_exit.xml index 077fc6b..f06a6e5 100644 --- a/core/res/res/anim/lock_screen_exit.xml +++ b/core/res/res/anim/lock_screen_exit.xml @@ -18,7 +18,7 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/accelerate_interpolator"> + android:interpolator="@anim/decelerate_cubic_interpolator"> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="@android:integer/config_longAnimTime" /> + android:duration="@android:integer/config_activityDefaultDur" /> </set> diff --git a/core/res/res/anim/shrink_fade_out.xml b/core/res/res/anim/shrink_fade_out.xml index 4000c23..7229a17 100644 --- a/core/res/res/anim/shrink_fade_out.xml +++ b/core/res/res/anim/shrink_fade_out.xml @@ -17,12 +17,14 @@ ** limitations under the License. */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - <scale android:fromXScale="1.0" android:toXScale="1.0" - android:fromYScale="1.0" android:toYScale="0.3" - android:pivotX="0%" android:pivotY="0%" - android:duration="@android:integer/config_shortAnimTime" /> - <alpha android:interpolator="@anim/accelerate_interpolator" + +<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> + <scale android:interpolator="@anim/decelerate_quint_interpolator" + android:fromXScale="1.0" android:toXScale="0.9" + android:fromYScale="1.0" android:toYScale="0.9" + android:pivotX="50%" android:pivotY="0%" + android:duration="@android:integer/config_activityDefaultDur" /> + <alpha android:interpolator="@anim/decelerate_cubic_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="@android:integer/config_shortAnimTime"/> -</set> + android:duration="@android:integer/config_activityShortDur" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/shrink_fade_out_center.xml b/core/res/res/anim/shrink_fade_out_center.xml index a41731e..23fa0b0 100644 --- a/core/res/res/anim/shrink_fade_out_center.xml +++ b/core/res/res/anim/shrink_fade_out_center.xml @@ -17,12 +17,14 @@ ** limitations under the License. */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - <scale android:fromXScale="1.0" android:toXScale="0.5" - android:fromYScale="1.0" android:toYScale="0.5" - android:pivotX="50%" android:pivotY="50%" - android:duration="@android:integer/config_shortAnimTime" /> - <alpha android:interpolator="@anim/accelerate_interpolator" + +<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> + <scale android:interpolator="@anim/decelerate_quint_interpolator" + android:fromXScale="1.0" android:toXScale="0.9" + android:fromYScale="1.0" android:toYScale="0.9" + android:pivotX="50%" android:pivotY="50%" + android:duration="@android:integer/config_activityDefaultDur" /> + <alpha android:interpolator="@anim/decelerate_cubic_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="@android:integer/config_shortAnimTime"/> -</set> + android:duration="@android:integer/config_activityShortDur" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/shrink_fade_out_from_bottom.xml b/core/res/res/anim/shrink_fade_out_from_bottom.xml index 345a2e0..0639425 100644 --- a/core/res/res/anim/shrink_fade_out_from_bottom.xml +++ b/core/res/res/anim/shrink_fade_out_from_bottom.xml @@ -17,12 +17,14 @@ ** limitations under the License. */ --> -<set xmlns:android="http://schemas.android.com/apk/res/android"> - <scale android:fromXScale="1.0" android:toXScale="1.0" - android:fromYScale="1.0" android:toYScale="0.3" - android:pivotX="0%" android:pivotY="100%" - android:duration="@android:integer/config_shortAnimTime" /> - <alpha android:interpolator="@anim/accelerate_interpolator" + +<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> + <scale android:interpolator="@anim/decelerate_quint_interpolator" + android:fromXScale="1.0" android:toXScale="0.9" + android:fromYScale="1.0" android:toYScale="0.9" + android:pivotX="50%" android:pivotY="100%" + android:duration="@android:integer/config_activityDefaultDur" /> + <alpha android:interpolator="@anim/decelerate_cubic_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="@android:integer/config_shortAnimTime"/> -</set> + android:duration="@android:integer/config_activityShortDur" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/task_close_enter.xml b/core/res/res/anim/task_close_enter.xml index 9bec8a3..5057eed 100644 --- a/core/res/res/anim/task_close_enter.xml +++ b/core/res/res/anim/task_close_enter.xml @@ -19,16 +19,16 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:detachWallpaper="true" android:shareInterpolator="false"> - <scale android:fromXScale="1.0" android:toXScale="1.0" - android:fromYScale="0.9" android:toYScale="1.0" + <scale android:fromXScale="1.0" android:toXScale="1.0" + android:fromYScale="0.95" android:toYScale="1.0" android:pivotX="50%p" android:pivotY="50%p" - android:fillEnabled="true" android:fillBefore="true" + android:fillEnabled="true" android:fillBefore="true" android:interpolator="@anim/decelerate_quint_interpolator" - android:startOffset="@android:integer/config_mediumAnimTime" - android:duration="@android:integer/config_mediumAnimTime" /> - <alpha android:fromAlpha="0" android:toAlpha="1.0" - android:fillEnabled="true" android:fillBefore="true" + android:startOffset="300" + android:duration="300" /> + <alpha android:fromAlpha="0" android:toAlpha="1.0" android:interpolator="@anim/decelerate_cubic_interpolator" - android:startOffset="@android:integer/config_mediumAnimTime" - android:duration="@android:integer/config_mediumAnimTime"/> + android:fillEnabled="true" android:fillBefore="true" + android:startOffset="300" + android:duration="300"/> </set> diff --git a/core/res/res/anim/task_close_exit.xml b/core/res/res/anim/task_close_exit.xml index ca60214..169f846 100644 --- a/core/res/res/anim/task_close_exit.xml +++ b/core/res/res/anim/task_close_exit.xml @@ -18,19 +18,15 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:detachWallpaper="true" android:shareInterpolator="false"> <scale android:fromXScale="1.0" android:toXScale="1.0" - android:fromYScale="1.0" android:toYScale="0.9" + android:fillEnabled="true" android:fillAfter="true" + android:fromYScale="1.0" android:toYScale="0.0" android:pivotX="50%p" android:pivotY="50%p" - android:fillEnabled="true" android:fillAfter="true" - android:interpolator="@anim/decelerate_quint_interpolator" - android:duration="@android:integer/config_mediumAnimTime" /> + android:interpolator="@anim/linear_interpolator" + android:duration="300" /> <alpha android:fromAlpha="1.0" android:toAlpha="0" - android:fillEnabled="true" android:fillAfter="true" - android:interpolator="@anim/decelerate_quint_interpolator" - android:duration="@android:integer/config_mediumAnimTime"/> - <!-- This is just to keep the animation running for the entire duration. --> - <alpha android:fromAlpha="1.0" android:toAlpha="1.0" - android:startOffset="@android:integer/config_mediumAnimTime" - android:duration="@android:integer/config_mediumAnimTime"/> + android:fillEnabled="true" android:fillAfter="true" + android:interpolator="@anim/decelerate_cubic_interpolator" + android:duration="150"/> </set> diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml index 4f72ba2..8f8515a 100644 --- a/core/res/res/anim/task_open_enter.xml +++ b/core/res/res/anim/task_open_enter.xml @@ -20,15 +20,15 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> <scale android:fromXScale="1.0" android:toXScale="1.0" - android:fromYScale="0.9" android:toYScale="1.0" + android:fromYScale="0.95" android:toYScale="1.0" android:pivotX="50%p" android:pivotY="50%p" android:fillEnabled="true" android:fillBefore="true" android:interpolator="@anim/decelerate_quint_interpolator" - android:startOffset="@android:integer/config_mediumAnimTime" - android:duration="@android:integer/config_mediumAnimTime" /> + android:startOffset="300" + android:duration="300" /> <alpha android:fromAlpha="0" android:toAlpha="1.0" android:fillEnabled="true" android:fillBefore="true" - android:interpolator="@anim/decelerate_cubic_interpolator" - android:startOffset="@android:integer/config_mediumAnimTime" - android:duration="@android:integer/config_mediumAnimTime"/> + android:interpolator="@anim/decelerate_quint_interpolator" + android:startOffset="300" + android:duration="300"/> </set> diff --git a/core/res/res/anim/task_open_exit.xml b/core/res/res/anim/task_open_exit.xml index 6174151..7d2b1b1 100644 --- a/core/res/res/anim/task_open_exit.xml +++ b/core/res/res/anim/task_open_exit.xml @@ -20,17 +20,13 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:detachWallpaper="true" android:shareInterpolator="false"> <scale android:fromXScale="1.0" android:toXScale="1.0" - android:fromYScale="1.0" android:toYScale="0.9" + android:fromYScale="1.0" android:toYScale="0.0" android:pivotX="50%p" android:pivotY="50%p" android:fillEnabled="true" android:fillAfter="true" - android:interpolator="@anim/decelerate_quint_interpolator" - android:duration="@android:integer/config_mediumAnimTime" /> + android:interpolator="@anim/linear_interpolator" + android:duration="300" /> <alpha android:fromAlpha="1.0" android:toAlpha="0" android:fillEnabled="true" android:fillAfter="true" - android:interpolator="@anim/decelerate_quint_interpolator" - android:duration="@android:integer/config_mediumAnimTime"/> - <!-- This is just to keep the animation running for the entire duration. --> - <alpha android:fromAlpha="1.0" android:toAlpha="1.0" - android:startOffset="@android:integer/config_mediumAnimTime" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> + android:interpolator="@anim/decelerate_cubic_interpolator" + android:duration="150"/> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/wallpaper_close_enter.xml b/core/res/res/anim/wallpaper_close_enter.xml index 760acb3..1ce0738 100644 --- a/core/res/res/anim/wallpaper_close_enter.xml +++ b/core/res/res/anim/wallpaper_close_enter.xml @@ -17,7 +17,6 @@ */ --> -<!-- New holo animation, zooming contents on top of wallpaper down. --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> <scale android:fromXScale="1.0" android:toXScale="1.0" @@ -25,64 +24,11 @@ android:pivotX="50%p" android:pivotY="50%p" android:fillEnabled="true" android:fillBefore="true" android:interpolator="@anim/decelerate_quint_interpolator" - android:startOffset="@android:integer/config_mediumAnimTime" - android:duration="@android:integer/config_mediumAnimTime" /> + android:startOffset="200" + android:duration="300" /> <alpha android:fromAlpha="0" android:toAlpha="1.0" android:fillEnabled="true" android:fillBefore="true" android:interpolator="@anim/decelerate_quint_interpolator" - android:startOffset="@android:integer/config_mediumAnimTime" - android:duration="@android:integer/config_mediumAnimTime"/> + android:startOffset="200" + android:duration="300"/> </set> - -<!-- This version zooms the new non-wallpaper up out of the - wallpaper, without zooming the wallpaper itself. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:zAdjustment="top"> - <scale android:fromXScale=".5" android:toXScale="1.0" - android:fromYScale=".5" android:toYScale="1.0" - android:pivotX="50%p" android:pivotY="50%p" - android:interpolator="@anim/decelerate_interpolator" - android:duration="@android:integer/config_mediumAnimTime" /> - <alpha android:fromAlpha="0" android:toAlpha="1.0" - android:interpolator="@anim/accelerate_decelerate_interpolator" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> ---> - -<!-- This version zooms the new non-wallpaper down on top of the - wallpaper, without zooming the wallpaper itself. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:zAdjustment="top"> - <scale android:fromXScale="2.0" android:toXScale="1.0" - android:fromYScale="2.0" android:toYScale="1.0" - android:pivotX="50%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> - <alpha android:fromAlpha="0" android:toAlpha="1.0" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> ---> - -<!-- This version zooms the new non-wallpaper down on top of the - wallpaper. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator"> - <scale android:fromXScale="2.0" android:toXScale="1.0" - android:fromYScale="2.0" android:toYScale="1.0" - android:pivotX="50%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> -</set> ---> - -<!-- This version is a variation on the inter-activity slide that - also scales the wallpaper. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator"> - <translate android:fromXDelta="33%" android:toXDelta="0" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> ---> diff --git a/core/res/res/anim/wallpaper_close_exit.xml b/core/res/res/anim/wallpaper_close_exit.xml index b804916..39af5f9 100644 --- a/core/res/res/anim/wallpaper_close_exit.xml +++ b/core/res/res/anim/wallpaper_close_exit.xml @@ -17,77 +17,16 @@ */ --> -<!-- New holo animation, zooming contents on top of wallpaper down. --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:detachWallpaper="true" android:shareInterpolator="false"> - <scale android:fromXScale="1.0" android:toXScale="0.8" - android:fromYScale="1.0" android:toYScale="0.8" + <scale android:fromXScale="1.0" android:toXScale="0.9" + android:fromYScale="1.0" android:toYScale="0.9" android:pivotX="50%p" android:pivotY="50%p" android:fillEnabled="true" android:fillAfter="true" android:interpolator="@anim/decelerate_quint_interpolator" - android:duration="@android:integer/config_mediumAnimTime" /> + android:duration="300" /> <alpha android:fromAlpha="1.0" android:toAlpha="0" android:fillEnabled="true" android:fillAfter="true" android:interpolator="@anim/decelerate_cubic_interpolator" - android:duration="@android:integer/config_mediumAnimTime"/> - <!-- This is just to keep the animation running for the entire duration. --> - <alpha android:fromAlpha="1.0" android:toAlpha="1.0" - android:startOffset="@android:integer/config_mediumAnimTime" - android:duration="@android:integer/config_mediumAnimTime"/> + android:duration="150"/> </set> - -<!-- This version zooms the new non-wallpaper up out of the - wallpaper, without zooming the wallpaper itself. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:detachWallpaper="true"> - <scale android:fromXScale="1.0" android:toXScale="2.0" - android:fromYScale="1.0" android:toYScale="2.0" - android:pivotX="50%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> -</set> ---> - -<!-- This version zooms the new non-wallpaper down on top of the - wallpaper, without zooming the wallpaper itself. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:detachWallpaper="true"> - <scale android:fromXScale="1.0" android:toXScale=".5" - android:fromYScale="1.0" android:toYScale=".5" - android:pivotX="50%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> -</set> ---> - -<!-- This version zooms the new non-wallpaper down on top of the - wallpaper. The wallpaper here just stays fixed behind. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:zAdjustment="top"> - <scale android:fromXScale="1.0" android:toXScale=".5" - android:fromYScale="1.0" android:toYScale=".5" - android:pivotX="50%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> - <alpha android:fromAlpha="1.0" android:toAlpha="0" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> ---> - -<!-- This version is a variation on the inter-activity slide that - also scales the wallpaper. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:zAdjustment="top"> - <scale android:fromXScale="1.0" android:toXScale="2.0" - android:fromYScale="1.0" android:toYScale="2.0" - android:pivotX="100%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> - <translate android:fromXDelta="0%" android:toXDelta="-100%" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> ---> diff --git a/core/res/res/anim/wallpaper_intra_close_enter.xml b/core/res/res/anim/wallpaper_intra_close_enter.xml index 73bf9a3..1ce0738 100644 --- a/core/res/res/anim/wallpaper_intra_close_enter.xml +++ b/core/res/res/anim/wallpaper_intra_close_enter.xml @@ -18,8 +18,17 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:zAdjustment="top"> - <alpha android:fromAlpha="0.5" android:toAlpha="1.0" - android:duration="@android:integer/config_shortAnimTime" /> + android:shareInterpolator="false"> + <scale android:fromXScale="1.0" android:toXScale="1.0" + android:fromYScale=".9" android:toYScale="1.0" + android:pivotX="50%p" android:pivotY="50%p" + android:fillEnabled="true" android:fillBefore="true" + android:interpolator="@anim/decelerate_quint_interpolator" + android:startOffset="200" + android:duration="300" /> + <alpha android:fromAlpha="0" android:toAlpha="1.0" + android:fillEnabled="true" android:fillBefore="true" + android:interpolator="@anim/decelerate_quint_interpolator" + android:startOffset="200" + android:duration="300"/> </set> diff --git a/core/res/res/anim/wallpaper_intra_close_exit.xml b/core/res/res/anim/wallpaper_intra_close_exit.xml index d43660e..39af5f9 100644 --- a/core/res/res/anim/wallpaper_intra_close_exit.xml +++ b/core/res/res/anim/wallpaper_intra_close_exit.xml @@ -18,7 +18,15 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator"> - <alpha android:fromAlpha="0.5" android:toAlpha="0.0" - android:duration="@android:integer/config_shortAnimTime"/> + android:detachWallpaper="true" android:shareInterpolator="false"> + <scale android:fromXScale="1.0" android:toXScale="0.9" + android:fromYScale="1.0" android:toYScale="0.9" + android:pivotX="50%p" android:pivotY="50%p" + android:fillEnabled="true" android:fillAfter="true" + android:interpolator="@anim/decelerate_quint_interpolator" + android:duration="300" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0" + android:fillEnabled="true" android:fillAfter="true" + android:interpolator="@anim/decelerate_cubic_interpolator" + android:duration="150"/> </set> diff --git a/core/res/res/anim/wallpaper_intra_open_enter.xml b/core/res/res/anim/wallpaper_intra_open_enter.xml index c85ca4c..0a8b7d2 100644 --- a/core/res/res/anim/wallpaper_intra_open_enter.xml +++ b/core/res/res/anim/wallpaper_intra_open_enter.xml @@ -18,7 +18,15 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator"> - <alpha android:fromAlpha="0.5" android:toAlpha="1.0" - android:duration="@android:integer/config_shortAnimTime" /> + android:detachWallpaper="true" android:shareInterpolator="false"> + <scale android:fromXScale="0.95" android:toXScale="1.0" + android:fromYScale="0.95" android:toYScale="1.0" + android:pivotX="50%p" android:pivotY="50%p" + android:interpolator="@anim/decelerate_quint_interpolator" + android:startOffset="200" + android:duration="300" /> + <alpha android:fromAlpha="0" android:toAlpha="1.0" + android:interpolator="@anim/decelerate_cubic_interpolator" + android:startOffset="200" + android:duration="300"/> </set> diff --git a/core/res/res/anim/wallpaper_intra_open_exit.xml b/core/res/res/anim/wallpaper_intra_open_exit.xml index eaeac22..d55ee6e 100644 --- a/core/res/res/anim/wallpaper_intra_open_exit.xml +++ b/core/res/res/anim/wallpaper_intra_open_exit.xml @@ -18,8 +18,13 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:zAdjustment="top"> - <alpha android:fromAlpha="0.5" android:toAlpha="0.0" - android:duration="@android:integer/config_shortAnimTime"/> -</set> + android:shareInterpolator="false"> + <scale android:fromXScale="1.0" android:toXScale="1.0" + android:fromYScale="1.0" android:toYScale="0.0" + android:pivotX="50%p" android:pivotY="50%p" + android:interpolator="@anim/linear_interpolator" + android:duration="300" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0" + android:interpolator="@anim/decelerate_cubic_interpolator" + android:duration="160"/> +</set> diff --git a/core/res/res/anim/wallpaper_open_enter.xml b/core/res/res/anim/wallpaper_open_enter.xml index 72ac671..0a8b7d2 100644 --- a/core/res/res/anim/wallpaper_open_enter.xml +++ b/core/res/res/anim/wallpaper_open_enter.xml @@ -17,74 +17,16 @@ */ --> -<!-- New holo animation, zooming contents on top of wallpaper back up. --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:detachWallpaper="true" android:shareInterpolator="false"> - <scale android:fromXScale="0.8" android:toXScale="1.0" - android:fromYScale="0.8" android:toYScale="1.0" + <scale android:fromXScale="0.95" android:toXScale="1.0" + android:fromYScale="0.95" android:toYScale="1.0" android:pivotX="50%p" android:pivotY="50%p" android:interpolator="@anim/decelerate_quint_interpolator" - android:startOffset="@android:integer/config_mediumAnimTime" - android:duration="@android:integer/config_mediumAnimTime" /> + android:startOffset="200" + android:duration="300" /> <alpha android:fromAlpha="0" android:toAlpha="1.0" android:interpolator="@anim/decelerate_cubic_interpolator" - android:startOffset="@android:integer/config_mediumAnimTime" - android:duration="@android:integer/config_mediumAnimTime"/> + android:startOffset="200" + android:duration="300"/> </set> - - -<!-- This version zooms the exiting non-wallpaper down in to the - wallpaper, without zooming the wallpaper itself. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:detachWallpaper="true"> - <scale android:fromXScale="2.0" android:toXScale="1.0" - android:fromYScale="2.0" android:toYScale="1.0" - android:pivotX="50%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> -</set> ---> - -<!-- This version zooms the new non-wallpaper down on top of the - wallpaper, without zooming the wallpaper itself. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:detachWallpaper="true"> - <scale android:fromXScale=".5" android:toXScale="1.0" - android:fromYScale=".5" android:toYScale="1.0" - android:pivotX="50%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> -</set> ---> - -<!-- This version zooms the new non-wallpaper up off the wallpaper the - wallpaper. The wallpaper here just stays fixed behind. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:zAdjustment="top"> - <scale android:fromXScale=".5" android:toXScale="1.0" - android:fromYScale=".5" android:toYScale="1.0" - android:pivotX="50%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> ---> - -<!-- This version is a variation on the inter-activity slide that - also scales the wallpaper. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:zAdjustment="top"> - <scale android:fromXScale="2.0" android:toXScale="1.0" - android:fromYScale="2.0" android:toYScale="1.0" - android:pivotX="100%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> - <translate android:fromXDelta="-100%" android:toXDelta="0" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> ---> diff --git a/core/res/res/anim/wallpaper_open_exit.xml b/core/res/res/anim/wallpaper_open_exit.xml index 7df3563..d55ee6e 100644 --- a/core/res/res/anim/wallpaper_open_exit.xml +++ b/core/res/res/anim/wallpaper_open_exit.xml @@ -17,68 +17,14 @@ */ --> -<!-- New holo animation, zooming contents on top of wallpaper back up. --> <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> <scale android:fromXScale="1.0" android:toXScale="1.0" - android:fromYScale="1.0" android:toYScale=".9" + android:fromYScale="1.0" android:toYScale="0.0" android:pivotX="50%p" android:pivotY="50%p" - android:interpolator="@anim/decelerate_quint_interpolator" - android:duration="@android:integer/config_mediumAnimTime" /> + android:interpolator="@anim/linear_interpolator" + android:duration="300" /> <alpha android:fromAlpha="1.0" android:toAlpha="0" - android:interpolator="@anim/decelerate_quint_interpolator" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> - -<!-- This version zooms the exiting non-wallpaper down in to the - wallpaper, without zooming the wallpaper itself. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:zAdjustment="top"> - <scale android:fromXScale="1.0" android:toXScale=".5" - android:fromYScale="1.0" android:toYScale=".5" - android:pivotX="50%p" android:pivotY="50%p" - android:interpolator="@anim/decelerate_interpolator" - android:duration="@android:integer/config_mediumAnimTime" /> - <alpha android:fromAlpha="1.0" android:toAlpha="0" - android:interpolator="@anim/accelerate_decelerate_interpolator" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> ---> - -<!-- This version zooms the new non-wallpaper down on top of the - wallpaper, without zooming the wallpaper itself. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator" - android:zAdjustment="top"> - <scale android:fromXScale="1.0" android:toXScale="2.0" - android:fromYScale="1.0" android:toYScale="2.0" - android:pivotX="50%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> - <alpha android:fromAlpha="1.0" android:toAlpha="0" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> ---> - -<!-- This version zooms the new non-wallpaper down on top of the - wallpaper. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator"> - <scale android:fromXScale="1.0" android:toXScale="2.0" - android:fromYScale="1.0" android:toYScale="2.0" - android:pivotX="50%p" android:pivotY="50%p" - android:duration="@android:integer/config_mediumAnimTime" /> -</set> ---> - -<!-- This version is a variation on the inter-activity slide that - also scales the wallpaper. --> -<!-- -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator"> - <translate android:fromXDelta="0%" android:toXDelta="33%" - android:duration="@android:integer/config_mediumAnimTime"/> -</set> ---> + android:interpolator="@anim/decelerate_cubic_interpolator" + android:duration="160"/> +</set> diff --git a/core/res/res/drawable-mdpi/stat_notify_error.png b/core/res/res/drawable-mdpi/stat_notify_error.png Binary files differindex 6ced2b7..1275738 100644 --- a/core/res/res/drawable-mdpi/stat_notify_error.png +++ b/core/res/res/drawable-mdpi/stat_notify_error.png diff --git a/core/res/res/drawable-mdpi/stat_notify_sdcard.png b/core/res/res/drawable-mdpi/stat_notify_sdcard.png Binary files differindex 23093ac..fc0784d 100644 --- a/core/res/res/drawable-mdpi/stat_notify_sdcard.png +++ b/core/res/res/drawable-mdpi/stat_notify_sdcard.png diff --git a/core/res/res/drawable-mdpi/stat_notify_sdcard_prepare.png b/core/res/res/drawable-mdpi/stat_notify_sdcard_prepare.png Binary files differindex 9abb1c9..93fad38 100644 --- a/core/res/res/drawable-mdpi/stat_notify_sdcard_prepare.png +++ b/core/res/res/drawable-mdpi/stat_notify_sdcard_prepare.png diff --git a/core/res/res/drawable-mdpi/stat_notify_sdcard_usb.png b/core/res/res/drawable-mdpi/stat_notify_sdcard_usb.png Binary files differindex 9880694..cda4546 100644 --- a/core/res/res/drawable-mdpi/stat_notify_sdcard_usb.png +++ b/core/res/res/drawable-mdpi/stat_notify_sdcard_usb.png diff --git a/core/res/res/drawable-mdpi/stat_notify_wifi_in_range.png b/core/res/res/drawable-mdpi/stat_notify_wifi_in_range.png Binary files differindex e9c74b4..517c515 100644 --- a/core/res/res/drawable-mdpi/stat_notify_wifi_in_range.png +++ b/core/res/res/drawable-mdpi/stat_notify_wifi_in_range.png diff --git a/core/res/res/drawable-mdpi/stat_sys_adb.png b/core/res/res/drawable-mdpi/stat_sys_adb.png Binary files differindex 12abeda..f0fad76 100644 --- a/core/res/res/drawable-mdpi/stat_sys_adb.png +++ b/core/res/res/drawable-mdpi/stat_sys_adb.png diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_0.png b/core/res/res/drawable-mdpi/stat_sys_battery_0.png Binary files differindex 750e652..e089120 100644 --- a/core/res/res/drawable-mdpi/stat_sys_battery_0.png +++ b/core/res/res/drawable-mdpi/stat_sys_battery_0.png diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_15.png b/core/res/res/drawable-mdpi/stat_sys_battery_15.png Binary files differindex 0eb58e1..be04321 100644 --- a/core/res/res/drawable-mdpi/stat_sys_battery_15.png +++ b/core/res/res/drawable-mdpi/stat_sys_battery_15.png diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim0.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim0.png Binary files differindex 957dab3..f8011c9 100644 --- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim0.png +++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim0.png diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim100.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim100.png Binary files differindex e6d7da0..499ced9 100644 --- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim100.png +++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim100.png diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim15.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim15.png Binary files differindex 957dab3..c921d6a 100644 --- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim15.png +++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim15.png diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim28.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim28.png Binary files differindex 5aba0bb..f882002 100644 --- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim28.png +++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim28.png diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim43.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim43.png Binary files differindex dc5fac6..e7d1069 100644 --- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim43.png +++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim43.png diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim57.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim57.png Binary files differindex 1233ed8..5e0af3d 100644 --- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim57.png +++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim57.png diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim71.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim71.png Binary files differindex 06d397b..fb99059 100644 --- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim71.png +++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim71.png diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim85.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim85.png Binary files differindex 1056faf..072f907 100644 --- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim85.png +++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim85.png diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_unknown.png b/core/res/res/drawable-mdpi/stat_sys_battery_unknown.png Binary files differindex 44eb313..3984c46 100644 --- a/core/res/res/drawable-mdpi/stat_sys_battery_unknown.png +++ b/core/res/res/drawable-mdpi/stat_sys_battery_unknown.png diff --git a/core/res/res/drawable-mdpi/stat_sys_download_anim0.png b/core/res/res/drawable-mdpi/stat_sys_download_anim0.png Binary files differindex 69b95cd..01b65e0 100755 --- a/core/res/res/drawable-mdpi/stat_sys_download_anim0.png +++ b/core/res/res/drawable-mdpi/stat_sys_download_anim0.png diff --git a/core/res/res/drawable-mdpi/stat_sys_download_anim1.png b/core/res/res/drawable-mdpi/stat_sys_download_anim1.png Binary files differindex 1e18eb5..2b0633f 100755 --- a/core/res/res/drawable-mdpi/stat_sys_download_anim1.png +++ b/core/res/res/drawable-mdpi/stat_sys_download_anim1.png diff --git a/core/res/res/drawable-mdpi/stat_sys_download_anim2.png b/core/res/res/drawable-mdpi/stat_sys_download_anim2.png Binary files differindex d7f2312..2295a6d 100755 --- a/core/res/res/drawable-mdpi/stat_sys_download_anim2.png +++ b/core/res/res/drawable-mdpi/stat_sys_download_anim2.png diff --git a/core/res/res/drawable-mdpi/stat_sys_download_anim3.png b/core/res/res/drawable-mdpi/stat_sys_download_anim3.png Binary files differindex 83f8d0f..99db712 100755 --- a/core/res/res/drawable-mdpi/stat_sys_download_anim3.png +++ b/core/res/res/drawable-mdpi/stat_sys_download_anim3.png diff --git a/core/res/res/drawable-mdpi/stat_sys_download_anim4.png b/core/res/res/drawable-mdpi/stat_sys_download_anim4.png Binary files differindex 9c1bd47..738dc87 100755 --- a/core/res/res/drawable-mdpi/stat_sys_download_anim4.png +++ b/core/res/res/drawable-mdpi/stat_sys_download_anim4.png diff --git a/core/res/res/drawable-mdpi/stat_sys_download_anim5.png b/core/res/res/drawable-mdpi/stat_sys_download_anim5.png Binary files differindex 3a81164..00ff542 100755 --- a/core/res/res/drawable-mdpi/stat_sys_download_anim5.png +++ b/core/res/res/drawable-mdpi/stat_sys_download_anim5.png diff --git a/core/res/res/drawable-mdpi/stat_sys_upload_anim0.png b/core/res/res/drawable-mdpi/stat_sys_upload_anim0.png Binary files differindex b7a5978..f6fcc7a 100755 --- a/core/res/res/drawable-mdpi/stat_sys_upload_anim0.png +++ b/core/res/res/drawable-mdpi/stat_sys_upload_anim0.png diff --git a/core/res/res/drawable-mdpi/stat_sys_upload_anim1.png b/core/res/res/drawable-mdpi/stat_sys_upload_anim1.png Binary files differindex a203e15..5d05381 100755 --- a/core/res/res/drawable-mdpi/stat_sys_upload_anim1.png +++ b/core/res/res/drawable-mdpi/stat_sys_upload_anim1.png diff --git a/core/res/res/drawable-mdpi/stat_sys_upload_anim2.png b/core/res/res/drawable-mdpi/stat_sys_upload_anim2.png Binary files differindex 4af7630..a8699bc 100755 --- a/core/res/res/drawable-mdpi/stat_sys_upload_anim2.png +++ b/core/res/res/drawable-mdpi/stat_sys_upload_anim2.png diff --git a/core/res/res/drawable-mdpi/stat_sys_upload_anim3.png b/core/res/res/drawable-mdpi/stat_sys_upload_anim3.png Binary files differindex 1dd76b1..f5ad44a 100755 --- a/core/res/res/drawable-mdpi/stat_sys_upload_anim3.png +++ b/core/res/res/drawable-mdpi/stat_sys_upload_anim3.png diff --git a/core/res/res/drawable-mdpi/stat_sys_upload_anim4.png b/core/res/res/drawable-mdpi/stat_sys_upload_anim4.png Binary files differindex 36c18bf..b5884f6 100755 --- a/core/res/res/drawable-mdpi/stat_sys_upload_anim4.png +++ b/core/res/res/drawable-mdpi/stat_sys_upload_anim4.png diff --git a/core/res/res/drawable-mdpi/stat_sys_upload_anim5.png b/core/res/res/drawable-mdpi/stat_sys_upload_anim5.png Binary files differindex 748331f..ae6b8a9 100755 --- a/core/res/res/drawable-mdpi/stat_sys_upload_anim5.png +++ b/core/res/res/drawable-mdpi/stat_sys_upload_anim5.png diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml index 3982ed9..e3ba634 100644 --- a/core/res/res/layout/alert_dialog.xml +++ b/core/res/res/layout/alert_dialog.xml @@ -81,7 +81,8 @@ android:paddingTop="2dip" android:paddingBottom="12dip" android:paddingLeft="14dip" - android:paddingRight="10dip"> + android:paddingRight="10dip" + android:overScrollMode="ifContentScrolls"> <TextView android:id="@+id/message" style="?android:attr/textAppearanceMedium" android:layout_width="match_parent" diff --git a/core/res/res/layout/preference_dialog_edittext.xml b/core/res/res/layout/preference_dialog_edittext.xml index 691ee8c..40b9e69 100644 --- a/core/res/res/layout/preference_dialog_edittext.xml +++ b/core/res/res/layout/preference_dialog_edittext.xml @@ -19,7 +19,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="48dp" - android:layout_marginBottom="48dp"> + android:layout_marginBottom="48dp" + android:overScrollMode="ifContentScrolls"> <LinearLayout android:id="@+android:id/edittext_container" diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml index 6c0e773..0f84418 100644 --- a/core/res/res/layout/preference_list_content.xml +++ b/core/res/res/layout/preference_list_content.xml @@ -56,7 +56,7 @@ </LinearLayout> - <FrameLayout android:id="@+id/prefs" + <android.preference.PreferenceFrameLayout android:id="@+id/prefs" android:layout_width="0px" android:layout_height="match_parent" android:layout_weight="20" @@ -66,8 +66,6 @@ android:layout_marginBottom="16dp" android:paddingLeft="32dip" android:paddingRight="32dip" - android:paddingTop="48dip" - android:paddingBottom="48dip" android:background="?attr/preferencePanelBackground" android:visibility="gone" /> </LinearLayout> diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml index c499b8a..dbe0df0 100644 --- a/core/res/res/layout/preference_list_fragment.xml +++ b/core/res/res/layout/preference_list_fragment.xml @@ -18,6 +18,7 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/default_preference_layout" android:orientation="vertical" android:layout_height="match_parent" android:layout_width="match_parent" @@ -27,6 +28,9 @@ android:layout_width="match_parent" android:layout_height="0px" android:layout_weight="1" + android:paddingTop="48dip" + android:paddingBottom="48dip" + android:clipToPadding="false" android:drawSelectorOnTop="false" android:cacheColorHint="@android:color/transparent" android:scrollbarAlwaysDrawVerticalTrack="true" /> diff --git a/core/res/res/layout/select_dialog.xml b/core/res/res/layout/select_dialog.xml index 94dcb6a..80d22f6 100644 --- a/core/res/res/layout/select_dialog.xml +++ b/core/res/res/layout/select_dialog.xml @@ -31,4 +31,5 @@ android:layout_marginTop="5px" android:cacheColorHint="@null" android:divider="?android:attr/listDividerAlertDialog" - android:scrollbars="vertical" /> + android:scrollbars="vertical" + android:overScrollMode="ifContentScrolls" /> diff --git a/core/res/res/values-xlarge/config.xml b/core/res/res/values-xlarge/config.xml index 9504d04..37f59c8 100644 --- a/core/res/res/values-xlarge/config.xml +++ b/core/res/res/values-xlarge/config.xml @@ -30,8 +30,8 @@ <!-- Enable lockscreen rotation --> <bool name="config_enableLockScreenRotation">true</bool> - <!-- Enables 3d task switcher on xlarge device --> - <bool name="config_enableRecentApps3D">true</bool> + <!-- see comment in values/config.xml --> + <integer name="config_longPressOnHomeBehavior">1</integer> </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 651bfea..3f81a89 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -610,6 +610,9 @@ <!-- Specifies a drawable to use for the 'home as up' indicator. --> <attr name="homeAsUpIndicator" format="reference" /> + + <!-- Preference frame layout styles. --> + <attr name="preferenceFrameLayoutStyle" format="reference" /> </declare-styleable> <!-- **************************************************************** --> @@ -1632,6 +1635,20 @@ <code>public void sayHello(View v)</code> method of your context (typically, your Activity). --> <attr name="onClick" format="string" /> + + <!-- Defines over-scrolling behavior. This property is used only if the + View is scrollable. Over-scrolling is the ability for the user to + receive feedback when attempting to scroll beyond meaningful content. --> + <attr name="overScrollMode"> + <!-- Always show over-scroll effects, even if the content fits entirely + within the available space. --> + <enum name="always" value="0" /> + <!-- Only show over-scroll effects if the content is large + enough to meaningfully scroll. --> + <enum name="ifContentScrolls" value="1" /> + <!-- Never show over-scroll effects. --> + <enum name="never" value="2" /> + </attr> </declare-styleable> <!-- Attributes that can be used with a {@link android.view.ViewGroup} or any @@ -2116,6 +2133,16 @@ <!-- When set to false, the ListView will not draw the divider before each footer view. The default value is true. --> <attr name="footerDividersEnabled" format="boolean" /> + <!-- Drawable to draw above list content. --> + <attr name="overScrollHeader" format="reference|color" /> + <!-- Drawable to draw below list content. --> + <attr name="overScrollFooter" format="reference|color" /> + </declare-styleable> + <declare-styleable name="PreferenceFrameLayout"> + <!-- Padding to use at the top of the prefs content. --> + <attr name="topPadding" format="dimension" /> + <!-- Padding to use at the bottom of the prefs content. --> + <attr name="bottomPadding" format="dimension" /> </declare-styleable> <declare-styleable name="MenuView"> <!-- Default appearance of menu item text. --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ac15be6..15b5db4 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -66,6 +66,16 @@ <!-- The duration (in milliseconds) of a long animation. --> <integer name="config_longAnimTime">500</integer> + <!-- The duration (in milliseconds) of the activity open/close and fragment open/close animations. --> + <integer name="config_activityShortDur">150</integer> + <integer name="config_activityDefaultDur">220</integer> + + <!-- Duration for the dim animation behind a dialog. This may be either + a percentage, which is relative to the duration of the enter/open + animation of the window being shown that is dimming behind, or it may + be an integer for a constant duration. --> + <fraction name="config_dimBehindFadeDuration">100%</fraction> + <!-- The duration (in milliseconds) that the radio will scan for a signal when there's no network connection. If the scan doesn't timeout, use zero --> <integer name="config_radioScanningTimeout">0</integer> @@ -336,8 +346,12 @@ <!-- Diable lockscreen rotation by default --> <bool name="config_enableLockScreenRotation">false</bool> - <!-- Enable 3D RecentApplications view --> - <bool name="config_enableRecentApps3D">false</bool> + <!-- Control the behavior when the user long presses the power button. + 0 - Nothing + 1 - Recent apps dialog + 2 - Recent apps activity in SystemUI + --> + <integer name="config_longPressOnHomeBehavior">0</integer> <!-- Array of light sensor LUX values to define our levels for auto backlight brightness support. The N entries of this array define N + 1 zones as follows: diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index f4d1df8..f2ab5cd 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1253,6 +1253,9 @@ <public type="attr" name="logo" id="0x010102be" /> <public type="attr" name="xlargeScreens" id="0x010102bf" /> <public type="attr" name="immersive" id="0x010102c0" /> + <public type="attr" name="overScrollMode" id="0x010102c1" /> + <public type="attr" name="overScrollHeader" id="0x010102c2" /> + <public type="attr" name="overScrollFooter" id="0x010102c3" /> <public type="attr" name="filterTouchesWhenObscured" id="0x010102c4" /> <public type="attr" name="textSelectHandleLeft" id="0x010102c5" /> <public type="attr" name="textSelectHandleRight" id="0x010102c6" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index c8a5de8..0c3361d 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1824,8 +1824,10 @@ <!-- Toast for double-tap --> <string name="double_tap_toast">Tip: double-tap to zoom in and out.</string> - <!-- Text to show in the auto complete drop down list on a text view when the browser can auto fill the entire form --> + <!-- Text to show in the auto complete drop down list on a text view when the WebView can auto fill the entire form, and the user has configured an AutoFill profile [CHAR-LIMIT=8] --> <string name="autofill_this_form">AutoFill</string> + <!-- Text to show in the auto complete drop down list on a text view when the WebView can auto fill the entire form but the user has not configured an AutoFill profile [CHAR-LIMIT=16] --> + <string name="setup_autofill">Setup AutoFill</string> <!-- String used to separate FirstName and LastName when writing out a local name e.g. John<separator>Smith [CHAR-LIMIT=NONE]--> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 7a429b0..6985de6 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -48,10 +48,15 @@ <item name="bottomMedium">@android:drawable/popup_bottom_medium</item> <item name="centerMedium">@android:drawable/popup_center_medium</item> </style> - + + <style name="Widget.PreferenceFrameLayout"> + <item name="android:topPadding">0dip</item> + <item name="android:bottomPadding">0dip</item> + </style> + <!-- Base style for animations. This style specifies no animations. --> <style name="Animation" /> - + <!-- Standard animations for a full-screen window or activity. --> <style name="Animation.Activity"> <item name="activityOpenEnterAnimation">@anim/activity_open_enter</item> @@ -1808,7 +1813,7 @@ <item name="android:background">@null</item> <item name="android:homeAsUpIndicator">@android:drawable/ic_ab_back_holo_light</item> <item name="android:progressBarStyle">@android:style/Widget.Holo.Light.ProgressBar.Horizontal</item> - <item name="android:indeterminateProgressStyle">@android:style/Widget.Holo.Light.ProgressBar.Small</item> + <item name="android:indeterminateProgressStyle">@android:style/Widget.Holo.Light.ProgressBar</item> </style> <!-- Animation Styles --> @@ -1874,4 +1879,8 @@ <item name="android:textAppearance">@style/TextAppearance.Holo.Light.DialogWindowTitle</item> </style> + <style name="Widget.Holo.PreferenceFrameLayout"> + <item name="android:topPadding">48dip</item> + <item name="android:bottomPadding">48dip</item> + </style> </resources> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 9215bf2..1ef99d0 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -262,13 +262,16 @@ <!-- SearchView attributes --> <item name="searchDropdownBackground">@android:drawable/search_dropdown_dark</item> + + <!-- PreferenceFrameLayout attributes --> + <item name="preferenceFrameLayoutStyle">@android:style/Widget.PreferenceFrameLayout</item> </style> - + <!-- Variant of the default (dark) theme with no title bar --> <style name="Theme.NoTitleBar"> <item name="android:windowNoTitle">true</item> </style> - + <!-- Variant of the default (dark) theme that has no title bar and fills the entire screen --> <style name="Theme.NoTitleBar.Fullscreen"> @@ -871,6 +874,9 @@ <!-- SearchView attributes --> <item name="searchDropdownBackground">@android:drawable/search_dropdown_dark</item> + + <!-- PreferenceFrameLayout attributes --> + <item name="preferenceFrameLayoutStyle">@android:style/Widget.Holo.PreferenceFrameLayout</item> </style> <!-- New Honeycomb holographic theme. Light version. The widgets in the diff --git a/core/tests/ConnectivityManagerTest/AndroidManifest.xml b/core/tests/ConnectivityManagerTest/AndroidManifest.xml index d298d40..b116bea 100644 --- a/core/tests/ConnectivityManagerTest/AndroidManifest.xml +++ b/core/tests/ConnectivityManagerTest/AndroidManifest.xml @@ -53,6 +53,15 @@ android.label="Test runner for unit tests" /> + <!-- run stress test suite: + "adb shell am instrument -e stressnum <200> -w + com.android.connectivitymanagertest/.ConnectivityManagerStressTestRunner" + --> + <instrumentation android:name=".ConnectivityManagerStressTestRunner" + android:targetPackage="com.android.connectivitymanagertest" + android:label="Test runner for Connectivity Manager Stress Tests" + /> + <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java new file mode 100644 index 0000000..47f208a --- /dev/null +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.connectivitymanagertest; + +import android.os.Bundle; +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; +import android.util.Log; +import com.android.connectivitymanagertest.stress.WifiApStress; + +import junit.framework.TestSuite; + +/** + * Instrumentation Test Runner for all stress tests + * + * To run the stress tests: + * + * adb shell am instrument -e stressnum <stress times> \ + * -w com.android.connectivitymanagertest/.ConnectivityManagerStressTestRunner + */ + +public class ConnectivityManagerStressTestRunner extends InstrumentationTestRunner { + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(WifiApStress.class); + return suite; + } + + @Override + public ClassLoader getLoader() { + return ConnectivityManagerTestRunner.class.getClassLoader(); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + String stressValue = (String) icicle.get("stressnum"); + if (stressValue != null) { + int iteration = Integer.parseInt(stressValue); + if (iteration > 0) { + numStress = iteration; + } + } + } + + public int numStress = 100; +} diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java index 7c46e7a..37b9f52 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java @@ -55,6 +55,9 @@ public class ConnectivityManagerTestActivity extends Activity { public static final int WIFI_SCAN_TIMEOUT = 20 * 1000; public static final int SHORT_TIMEOUT = 5 * 1000; public static final long LONG_TIMEOUT = 50 * 1000; + public static final int SUCCESS = 0; // for Wifi tethering state change + public static final int FAILURE = 1; + public static final int INIT = -1; private static final String ACCESS_POINT_FILE = "accesspoints.xml"; public ConnectivityReceiver mConnectivityReceiver = null; public WifiReceiver mWifiReceiver = null; @@ -87,6 +90,10 @@ public class ConnectivityManagerTestActivity extends Activity { public static final int NUM_NETWORK_TYPES = ConnectivityManager.MAX_NETWORK_TYPE + 1; NetworkState[] connectivityState = new NetworkState[NUM_NETWORK_TYPES]; + // For wifi tethering tests + private String[] mWifiRegexs; + public int mWifiTetherResult = INIT; // -1 is initialization state + /** * A wrapper of a broadcast receiver which provides network connectivity information * for all kinds of network: wifi, mobile, etc. @@ -150,6 +157,16 @@ public class ConnectivityManagerTestActivity extends Activity { mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN); notifyWifiState(); + } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) { + notifyWifiAPState(); + } else if (action.equals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)) { + ArrayList<String> available = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_AVAILABLE_TETHER); + ArrayList<String> active = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_ACTIVE_TETHER); + ArrayList<String> errored = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_ERRORED_TETHER); + updateTetherState(available.toArray(), active.toArray(), errored.toArray()); } else { return; @@ -184,6 +201,8 @@ public class ConnectivityManagerTestActivity extends Activity { mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); + mIntentFilter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); + mIntentFilter.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); registerReceiver(mWifiReceiver, mIntentFilter); // Get an instance of ConnectivityManager @@ -196,6 +215,7 @@ public class ConnectivityManagerTestActivity extends Activity { Log.v(LOG_TAG, "Clear Wifi before we start the test."); removeConfiguredNetworksAndDisableWifi(); } + mWifiRegexs = mCM.getTetherableWifiRegexs(); } public List<WifiConfiguration> loadNetworkConfigurations() throws Exception { @@ -263,13 +283,57 @@ public class ConnectivityManagerTestActivity extends Activity { } } - public void notifyWifiState() { + private void notifyWifiState() { synchronized (wifiObject) { Log.v(LOG_TAG, "notify wifi state changed"); wifiObject.notify(); } } + private void notifyWifiAPState() { + synchronized (this) { + Log.v(LOG_TAG, "notify wifi AP state changed"); + this.notify(); + } + } + + // Update wifi tethering state + private void updateTetherState(Object[] available, Object[] tethered, Object[] errored) { + boolean wifiTethered = false; + boolean wifiErrored = false; + + synchronized (this) { + for (Object obj: tethered) { + String str = (String)obj; + for (String tethRex: mWifiRegexs) { + Log.v(LOG_TAG, "str: " + str +"tethRex: " + tethRex); + if (str.matches(tethRex)) { + wifiTethered = true; + } + } + } + + for (Object obj: errored) { + String str = (String)obj; + for (String tethRex: mWifiRegexs) { + Log.v(LOG_TAG, "error: str: " + str +"tethRex: " + tethRex); + if (str.matches(tethRex)) { + wifiErrored = true; + } + } + } + + if (wifiTethered) { + mWifiTetherResult = SUCCESS; // wifi tethering is successful + } else if (wifiErrored) { + mWifiTetherResult = FAILURE; // wifi tethering failed + } + Log.v(LOG_TAG, "mWifiTetherResult: " + mWifiTetherResult); + this.notify(); + } + } + + // Wait for network connectivity state: CONNECTING, CONNECTED, SUSPENDED, // DISCONNECTING, DISCONNECTED, UNKNOWN public boolean waitForNetworkState(int networkType, State expectedState, long timeout) { @@ -332,6 +396,62 @@ public class ConnectivityManagerTestActivity extends Activity { } } + // Wait for Wifi AP state: WIFI_AP_STATE_DISABLED, WIFI_AP_STATE_DISABLING, + // WIFI_AP_STATE_ENABLED, WIFI_STATE_ENALBING, WIFI_STATE_UNKNOWN + public boolean waitForWifiAPState(int expectedState, long timeout) { + long startTime = System.currentTimeMillis(); + while (true) { + if ((System.currentTimeMillis() - startTime) > timeout) { + if (mWifiManager.getWifiApState() != expectedState) { + return false; + } else { + return true; + } + } + Log.v(LOG_TAG, "Wait for wifi AP state to be: " + expectedState); + synchronized (wifiObject) { + try { + wifiObject.wait(SHORT_TIMEOUT); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (mWifiManager.getWifiApState() != expectedState) { + Log.v(LOG_TAG, "Wifi state is: " + mWifiManager.getWifiApState()); + continue; + } + return true; + } + } + } + + /** + * Wait for the wifi tethering result: + * @param timeout is the maximum waiting time + * @return SUCCESS if tethering result is successful + * FAILURE if tethering result returns error. + */ + public int waitForTetherStateChange(long timeout) { + long startTime = System.currentTimeMillis(); + while (true) { + if ((System.currentTimeMillis() - startTime) > timeout) { + return mWifiTetherResult; + } + Log.v(LOG_TAG, "Wait for wifi tethering result."); + synchronized (this) { + try { + this.wait(SHORT_TIMEOUT); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (mWifiTetherResult == INIT ) { + continue; + } else { + return mWifiTetherResult; + } + } + } + } + // Return true if device is currently connected to mobile network public boolean isConnectedToMobile() { return (mNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE); diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java new file mode 100644 index 0000000..cc53ddc --- /dev/null +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2010, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.connectivitymanagertest.stress; + + +import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner; +import com.android.connectivitymanagertest.ConnectivityManagerTestActivity; +import com.android.connectivitymanagertest.ConnectivityManagerTestRunner; + +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.net.wifi.WifiConfiguration.AuthAlgorithm; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; + +/** + * Stress the wifi driver as access point. + */ +public class WifiApStress + extends ActivityInstrumentationTestCase2<ConnectivityManagerTestActivity> { + private final static String TAG = "WifiApStress"; + private ConnectivityManagerTestActivity mAct; + private static String NETWORK_ID = "AndroidAPTest"; + private static String PASSWD = "androidwifi"; + private int max_num; + + public WifiApStress() { + super(ConnectivityManagerTestActivity.class); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + mAct = getActivity(); + max_num = ((ConnectivityManagerStressTestRunner)getInstrumentation()).numStress; + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + @LargeTest + public void testWifiHotSpot() { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = NETWORK_ID; + config.allowedKeyManagement.set(KeyMgmt.WPA_PSK); + config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN); + config.preSharedKey = PASSWD; + + // If Wifi is enabled, disable it + if (mAct.mWifiManager.isWifiEnabled()) { + mAct.disableWifi(); + } + for (int i = 0; i < max_num; i++) { + Log.v(TAG, "iteration: " + i); + // enable Wifi tethering + assertTrue(mAct.mWifiManager.setWifiApEnabled(config, true)); + // Wait for wifi ap state to be ENABLED + assertTrue(mAct.waitForWifiAPState(mAct.mWifiManager.WIFI_AP_STATE_ENABLED, + mAct.LONG_TIMEOUT)); + // Wait for wifi tethering result + assertEquals(mAct.SUCCESS, mAct.waitForTetherStateChange(2*mAct.SHORT_TIMEOUT)); + // Allow the wifi tethering to be enabled for 10 seconds + try { + Thread.sleep(2 * ConnectivityManagerTestActivity.SHORT_TIMEOUT); + } catch (Exception e) { + fail("thread in sleep is interrupted"); + } + assertTrue(mAct.mWifiManager.setWifiApEnabled(config, false)); + } + } + +} diff --git a/core/tests/coretests/src/android/content/SyncOperationTest.java b/core/tests/coretests/src/android/content/SyncOperationTest.java new file mode 100644 index 0000000..57435e5 --- /dev/null +++ b/core/tests/coretests/src/android/content/SyncOperationTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.accounts.Account; +import android.os.Bundle; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +/** + * You can run those tests with: + * + * adb shell am instrument + * -e debug false + * -w + * -e class android.content.SyncOperationTest com.android.frameworks.coretests/android.test.InstrumentationTestRunner + */ + +public class SyncOperationTest extends AndroidTestCase { + + @SmallTest + public void testToKey() { + Account account1 = new Account("account1", "type1"); + Account account2 = new Account("account2", "type2"); + + Bundle b1 = new Bundle(); + Bundle b2 = new Bundle(); + b2.putBoolean("b2", true); + + SyncOperation op1 = new SyncOperation(account1, + 1, + "authority1", + b1, + 100, + 1000, + 10000); + + // Same as op1 but different time infos + SyncOperation op2 = new SyncOperation(account1, + 1, + "authority1", + b1, + 200, + 2000, + 20000); + + // Same as op1 but different authority + SyncOperation op3 = new SyncOperation(account1, + 1, + "authority2", + b1, + 100, + 1000, + 10000); + + // Same as op1 but different account + SyncOperation op4 = new SyncOperation(account2, + 1, + "authority1", + b1, + 100, + 1000, + 10000); + + // Same as op1 but different bundle + SyncOperation op5 = new SyncOperation(account1, + 1, + "authority1", + b2, + 100, + 1000, + 10000); + + assertEquals(op1.key, op2.key); + assertNotSame(op1.key, op3.key); + assertNotSame(op1.key, op4.key); + assertNotSame(op1.key, op5.key); + } +} diff --git a/core/tests/coretests/src/android/util/TimeUtilsTest.java b/core/tests/coretests/src/android/util/TimeUtilsTest.java index 65a6078..8d9f8e5 100644 --- a/core/tests/coretests/src/android/util/TimeUtilsTest.java +++ b/core/tests/coretests/src/android/util/TimeUtilsTest.java @@ -429,4 +429,22 @@ public class TimeUtilsTest extends TestCase { c.getTimeInMillis(), country); } + + public void testFormatDuration() { + assertFormatDuration("0", 0); + assertFormatDuration("-1ms", -1); + assertFormatDuration("+1ms", 1); + assertFormatDuration("+10ms", 10); + assertFormatDuration("+100ms", 100); + assertFormatDuration("+101ms", 101); + assertFormatDuration("+330ms", 330); + assertFormatDuration("+1s330ms", 1330); + assertFormatDuration("+10s24ms", 10024); + } + + private void assertFormatDuration(String expected, long duration) { + StringBuilder sb = new StringBuilder(); + TimeUtils.formatDuration(duration, sb); + assertEquals("formatDuration(" + duration + ")", expected, sb.toString()); + } } diff --git a/data/keyboards/Android.mk b/data/keyboards/Android.mk new file mode 100644 index 0000000..8cba52d --- /dev/null +++ b/data/keyboards/Android.mk @@ -0,0 +1,19 @@ +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This makefile performs build time validation of framework keymap files. + +LOCAL_PATH := $(call my-dir) + +include $(LOCAL_PATH)/common.mk diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm index 682584c..24b485d 100644 --- a/data/keyboards/Generic.kcm +++ b/data/keyboards/Generic.kcm @@ -22,6 +22,8 @@ type FULL +### Basic QWERTY keys ### + key A { label: 'A' base: 'a' @@ -369,6 +371,8 @@ key APOSTROPHE { ctrl, alt, meta: none } +### Numeric keypad ### + key NUMPAD_0 { label, number: '0' base: fallback INSERT @@ -499,3 +503,25 @@ key NUMPAD_ENTER { base: '\n' fallback ENTER ctrl, alt, meta: none fallback ENTER } + +### Special keys on phones ### + +key AT { + label, number: '@' + base: '@' +} + +key STAR { + label, number: '*' + base: '*' +} + +key POUND { + label, number: '#' + base: '#' +} + +key PLUS { + label, number: '+' + base: '+' +} diff --git a/data/keyboards/Virtual.kcm b/data/keyboards/Virtual.kcm index 9ada86a..8d3c7ac 100644 --- a/data/keyboards/Virtual.kcm +++ b/data/keyboards/Virtual.kcm @@ -19,6 +19,8 @@ type FULL +### Basic QWERTY keys ### + key A { label: 'A' base: 'a' @@ -366,6 +368,8 @@ key APOSTROPHE { ctrl, alt, meta: none } +### Numeric keypad ### + key NUMPAD_0 { label, number: '0' base: fallback INSERT @@ -496,3 +500,25 @@ key NUMPAD_ENTER { base: '\n' fallback ENTER ctrl, alt, meta: none fallback ENTER } + +### Special keys on phones ### + +key AT { + label, number: '@' + base: '@' +} + +key STAR { + label, number: '*' + base: '*' +} + +key POUND { + label, number: '#' + base: '#' +} + +key PLUS { + label, number: '+' + base: '+' +} diff --git a/data/keyboards/common.mk b/data/keyboards/common.mk new file mode 100644 index 0000000..3f05edb --- /dev/null +++ b/data/keyboards/common.mk @@ -0,0 +1,30 @@ +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is the list of framework provided keylayouts and key character maps to include. +# Used by Android.mk and keyboards.mk. + +keylayouts := \ + AVRCP.kl \ + Generic.kl \ + Motorola_Bluetooth_Wireless_Keyboard.kl \ + qwerty.kl \ + qwerty2.kl + +keycharmaps := \ + Generic.kcm \ + Virtual.kcm \ + Motorola_Bluetooth_Wireless_Keyboard.kcm \ + qwerty.kcm \ + qwerty2.kcm diff --git a/data/keyboards/keyboards.mk b/data/keyboards/keyboards.mk index 3a0a553..b32e436 100644 --- a/data/keyboards/keyboards.mk +++ b/data/keyboards/keyboards.mk @@ -14,15 +14,7 @@ # Warning: this is actually a product definition, to be inherited from -keylayouts := \ - AVRCP.kl \ - Generic.kl \ - Motorola_Bluetooth_Wireless_Keyboard.kl - -keycharmaps := \ - Generic.kcm \ - Virtual.kcm \ - Motorola_Bluetooth_Wireless_Keyboard.kcm +include $(LOCAL_PATH)/common.mk PRODUCT_COPY_FILES := $(foreach file,$(keylayouts),\ frameworks/base/data/keyboards/$(file):system/usr/keylayout/$(file)) @@ -30,4 +22,4 @@ PRODUCT_COPY_FILES := $(foreach file,$(keylayouts),\ PRODUCT_COPY_FILES += $(foreach file,$(keycharmaps),\ frameworks/base/data/keyboards/$(file):system/usr/keychars/$(file)) -PRODUCT_PACKAGES := $(keycharmaps) +PRODUCT_PACKAGES := $(keylayouts) $(keycharmaps) diff --git a/data/keyboards/qwerty.kcm b/data/keyboards/qwerty.kcm new file mode 100644 index 0000000..f31333e --- /dev/null +++ b/data/keyboards/qwerty.kcm @@ -0,0 +1,508 @@ +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Emulator keyboard character map #1. +# +# This file is no longer used as the platform's default keyboard character map. +# Refer to Generic.kcm and Virtual.kcm instead. +# + +type ALPHA + +key A { + label: 'A' + number: '2' + base: 'a' + shift, capslock: 'A' + alt: '#' + shift+alt, capslock+alt: none +} + +key B { + label: 'B' + number: '2' + base: 'b' + shift, capslock: 'B' + alt: '<' + shift+alt, capslock+alt: none +} + +key C { + label: 'C' + number: '2' + base: 'c' + shift, capslock: 'C' + alt: '9' + shift+alt, capslock+alt: '\u00e7' +} + +key D { + label: 'D' + number: '3' + base: 'd' + shift, capslock: 'D' + alt: '5' + shift+alt, capslock+alt: none +} + +key E { + label: 'E' + number: '3' + base: 'e' + shift, capslock: 'E' + alt: '2' + shift+alt, capslock+alt: '\u0301' +} + +key F { + label: 'F' + number: '3' + base: 'f' + shift, capslock: 'F' + alt: '6' + shift+alt, capslock+alt: '\u00a5' +} + +key G { + label: 'G' + number: '4' + base: 'g' + shift, capslock: 'G' + alt: '-' + shift+alt, capslock+alt: '_' +} + +key H { + label: 'H' + number: '4' + base: 'h' + shift, capslock: 'H' + alt: '[' + shift+alt, capslock+alt: '{' +} + +key I { + label: 'I' + number: '4' + base: 'i' + shift, capslock: 'I' + alt: '$' + shift+alt, capslock+alt: '\u0302' +} + +key J { + label: 'J' + number: '5' + base: 'j' + shift, capslock: 'J' + alt: ']' + shift+alt, capslock+alt: '}' +} + +key K { + label: 'K' + number: '5' + base: 'k' + shift, capslock: 'K' + alt: '"' + shift+alt, capslock+alt: '~' +} + +key L { + label: 'L' + number: '5' + base: 'l' + shift, capslock: 'L' + alt: '\'' + shift+alt, capslock+alt: '`' +} + +key M { + label: 'M' + number: '6' + base: 'm' + shift, capslock: 'M' + alt: '!' + shift+alt, capslock+alt: none +} + +key N { + label: 'N' + number: '6' + base: 'n' + shift, capslock: 'N' + alt: '>' + shift+alt, capslock+alt: '\u0303' +} + +key O { + label: 'O' + number: '6' + base: 'o' + shift, capslock: 'O' + alt: '(' + shift+alt, capslock+alt: none +} + +key P { + label: 'P' + number: '7' + base: 'p' + shift, capslock: 'P' + alt: ')' + shift+alt, capslock+alt: none +} + +key Q { + label: 'Q' + number: '7' + base: 'q' + shift, capslock: 'Q' + alt: '*' + shift+alt, capslock+alt: '\u0300' +} + +key R { + label: 'R' + number: '7' + base: 'r' + shift, capslock: 'R' + alt: '3' + shift+alt, capslock+alt: '\u20ac' +} + +key S { + label: 'S' + number: '7' + base: 's' + shift, capslock: 'S' + alt: '4' + shift+alt, capslock+alt: '\u00df' +} + +key T { + label: 'T' + number: '8' + base: 't' + shift, capslock: 'T' + alt: '+' + shift+alt, capslock+alt: '\u00a3' +} + +key U { + label: 'U' + number: '8' + base: 'u' + shift, capslock: 'U' + alt: '&' + shift+alt, capslock+alt: '\u0308' +} + +key V { + label: 'V' + number: '8' + base: 'v' + shift, capslock: 'V' + alt: '=' + shift+alt, capslock+alt: '^' +} + +key W { + label: 'W' + number: '9' + base: 'w' + shift, capslock: 'W' + alt: '1' + shift+alt, capslock+alt: none +} + +key X { + label: 'X' + number: '9' + base: 'x' + shift, capslock: 'X' + alt: '8' + shift+alt, capslock+alt: '\uef00' +} + +key Y { + label: 'Y' + number: '9' + base: 'y' + shift, capslock: 'Y' + alt: '%' + shift+alt, capslock+alt: '\u00a1' +} + +key Z { + label: 'Z' + number: '9' + base: 'z' + shift, capslock: 'Z' + alt: '7' + shift+alt, capslock+alt: none +} + +key COMMA { + label: ',' + number: ',' + base: ',' + shift, capslock: ';' + alt: ';' + shift+alt, capslock+alt: '|' +} + +key PERIOD { + label: '.' + number: '.' + base: '.' + shift: ':' + alt: ':' + shift+alt: '\u2026' +} + +key AT { + label: '@' + number: '0' + base: '@' + shift: '0' + alt: '0' + shift+alt: '\u2022' +} + +key SLASH { + label: '/' + number: '/' + base: '/' + shift: '?' + alt: '?' + shift+alt: '\\' +} + +key SPACE { + label: ' ' + number: ' ' + base: ' ' + shift: ' ' + alt: '\uef01' + shift+alt: '\uef01' +} + +key ENTER { + label: '\n' + number: '\n' + base: '\n' + shift: '\n' + alt: '\n' + shift+alt: '\n' +} + +key TAB { + label: '\t' + number: '\t' + base: '\t' + shift: '\t' + alt: '\t' + shift+alt: '\t' +} + +key 0 { + label: '0' + number: '0' + base: '0' + shift: ')' + alt: ')' + shift+alt: ')' +} + +key 1 { + label: '1' + number: '1' + base: '1' + shift: '!' + alt: '!' + shift+alt: '!' +} + +key 2 { + label: '2' + number: '2' + base: '2' + shift: '@' + alt: '@' + shift+alt: '@' +} + +key 3 { + label: '3' + number: '3' + base: '3' + shift: '#' + alt: '#' + shift+alt: '#' +} + +key 4 { + label: '4' + number: '4' + base: '4' + shift: '$' + alt: '$' + shift+alt: '$' +} + +key 5 { + label: '5' + number: '5' + base: '5' + shift: '%' + alt: '%' + shift+alt: '%' +} + +key 6 { + label: '6' + number: '6' + base: '6' + shift: '^' + alt: '^' + shift+alt: '^' +} + +key 7 { + label: '7' + number: '7' + base: '7' + shift: '&' + alt: '&' + shift+alt: '&' +} + +key 8 { + label: '8' + number: '8' + base: '8' + shift: '*' + alt: '*' + shift+alt: '*' +} + +key 9 { + label: '9' + number: '9' + base: '9' + shift: '(' + alt: '(' + shift+alt: '(' +} + +key GRAVE { + label: '`' + number: '`' + base: '`' + shift: '~' + alt: '`' + shift+alt: '~' +} + +key MINUS { + label: '-' + number: '-' + base: '-' + shift: '_' + alt: '-' + shift+alt: '_' +} + +key EQUALS { + label: '=' + number: '=' + base: '=' + shift: '+' + alt: '=' + shift+alt: '+' +} + +key LEFT_BRACKET { + label: '[' + number: '[' + base: '[' + shift: '{' + alt: '[' + shift+alt: '{' +} + +key RIGHT_BRACKET { + label: ']' + number: ']' + base: ']' + shift: '}' + alt: ']' + shift+alt: '}' +} + +key BACKSLASH { + label: '\\' + number: '\\' + base: '\\' + shift: '|' + alt: '\\' + shift+alt: '|' +} + +key SEMICOLON { + label: ';' + number: ';' + base: ';' + shift: ':' + alt: ';' + shift+alt: ':' +} + +key APOSTROPHE { + label: '\'' + number: '\'' + base: '\'' + shift: '"' + alt: '\'' + shift+alt: '"' +} + +key STAR { + label: '*' + number: '*' + base: '*' + shift: '*' + alt: '*' + shift+alt: '*' +} + +key POUND { + label: '#' + number: '#' + base: '#' + shift: '#' + alt: '#' + shift+alt: '#' +} + +key PLUS { + label: '+' + number: '+' + base: '+' + shift: '+' + alt: '+' + shift+alt: '+' +} diff --git a/data/keyboards/qwerty.kl b/data/keyboards/qwerty.kl new file mode 100644 index 0000000..f1caacd --- /dev/null +++ b/data/keyboards/qwerty.kl @@ -0,0 +1,112 @@ +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Emulator keyboard layout #1. +# +# This file is no longer used as the platform's default keyboard layout. +# Refer to Generic.kl instead. +# + +key 399 GRAVE +key 2 1 +key 3 2 +key 4 3 +key 5 4 +key 6 5 +key 7 6 +key 8 7 +key 9 8 +key 10 9 +key 11 0 +key 158 BACK WAKE_DROPPED +key 230 SOFT_RIGHT WAKE +key 60 SOFT_RIGHT WAKE +key 107 ENDCALL WAKE_DROPPED +key 62 ENDCALL WAKE_DROPPED +key 229 MENU WAKE_DROPPED +key 139 MENU WAKE_DROPPED +key 59 MENU WAKE_DROPPED +key 127 SEARCH WAKE_DROPPED +key 217 SEARCH WAKE_DROPPED +key 228 POUND +key 227 STAR +key 231 CALL WAKE_DROPPED +key 61 CALL WAKE_DROPPED +key 232 DPAD_CENTER WAKE_DROPPED +key 108 DPAD_DOWN WAKE_DROPPED +key 103 DPAD_UP WAKE_DROPPED +key 102 HOME WAKE +key 105 DPAD_LEFT WAKE_DROPPED +key 106 DPAD_RIGHT WAKE_DROPPED +key 115 VOLUME_UP WAKE +key 114 VOLUME_DOWN WAKE +key 116 POWER WAKE +key 212 CAMERA + +key 16 Q +key 17 W +key 18 E +key 19 R +key 20 T +key 21 Y +key 22 U +key 23 I +key 24 O +key 25 P +key 26 LEFT_BRACKET +key 27 RIGHT_BRACKET +key 43 BACKSLASH + +key 30 A +key 31 S +key 32 D +key 33 F +key 34 G +key 35 H +key 36 J +key 37 K +key 38 L +key 39 SEMICOLON +key 40 APOSTROPHE +key 14 DEL + +key 44 Z +key 45 X +key 46 C +key 47 V +key 48 B +key 49 N +key 50 M +key 51 COMMA +key 52 PERIOD +key 53 SLASH +key 28 ENTER + +key 56 ALT_LEFT +key 100 ALT_RIGHT +key 42 SHIFT_LEFT +key 54 SHIFT_RIGHT +key 15 TAB +key 57 SPACE +key 150 EXPLORER +key 155 ENVELOPE + +key 12 MINUS +key 13 EQUALS +key 215 AT + +# On an AT keyboard: ESC, F10 +key 1 BACK WAKE_DROPPED +key 68 MENU WAKE_DROPPED diff --git a/data/keyboards/qwerty2.kcm b/data/keyboards/qwerty2.kcm new file mode 100644 index 0000000..d96914f --- /dev/null +++ b/data/keyboards/qwerty2.kcm @@ -0,0 +1,505 @@ +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Emulator keyboard character map #2. +# + +type ALPHA + +key A { + label: 'A' + number: '2' + base: 'a' + shift, capslock: 'A' + alt: 'a' + shift+alt, capslock+alt: 'A' +} + +key B { + label: 'B' + number: '2' + base: 'b' + shift, capslock: 'B' + alt: 'b' + shift+alt, capslock+alt: 'B' +} + +key C { + label: 'C' + number: '2' + base: 'c' + shift, capslock: 'C' + alt: '\u00e7' + shift+alt, capslock+alt: '\u00e7' +} + +key D { + label: 'D' + number: '3' + base: 'd' + shift, capslock: 'D' + alt: '\'' + shift+alt, capslock+alt: '\'' +} + +key E { + label: 'E' + number: '3' + base: 'e' + shift, capslock: 'E' + alt: '"' + shift+alt, capslock+alt: '\u0301' +} + +key F { + label: 'F' + number: '3' + base: 'f' + shift, capslock: 'F' + alt: '[' + shift+alt, capslock+alt: '[' +} + +key G { + label: 'G' + number: '4' + base: 'g' + shift, capslock: 'G' + alt: ']' + shift+alt, capslock+alt: ']' +} + +key H { + label: 'H' + number: '4' + base: 'h' + shift, capslock: 'H' + alt: '<' + shift+alt, capslock+alt: '<' +} + +key I { + label: 'I' + number: '4' + base: 'i' + shift, capslock: 'I' + alt: '-' + shift+alt, capslock+alt: '\u0302' +} + +key J { + label: 'J' + number: '5' + base: 'j' + shift, capslock: 'J' + alt: '>' + shift+alt, capslock+alt: '>' +} + +key K { + label: 'K' + number: '5' + base: 'k' + shift, capslock: 'K' + alt: ';' + shift+alt, capslock+alt: '~' +} + +key L { + label: 'L' + number: '5' + base: 'l' + shift, capslock: 'L' + alt: ':' + shift+alt, capslock+alt: '`' +} + +key M { + label: 'M' + number: '6' + base: 'm' + shift, capslock: 'M' + alt: '%' + shift+alt, capslock+alt: none +} + +key N { + label: 'N' + number: '6' + base: 'n' + shift, capslock: 'N' + alt: none + shift+alt, capslock+alt: '\u0303' +} + +key O { + label: 'O' + number: '6' + base: 'o' + shift, capslock: 'O' + alt: '+' + shift+alt, capslock+alt: '+' +} + +key P { + label: 'P' + number: '7' + base: 'p' + shift, capslock: 'P' + alt: '=' + shift+alt, capslock+alt: '\u00a5' +} + +key Q { + label: 'Q' + number: '7' + base: 'q' + shift, capslock: 'Q' + alt: '|' + shift+alt, capslock+alt: '\u0300' +} + +key R { + label: 'R' + number: '7' + base: 'r' + shift, capslock: 'R' + alt: '`' + shift+alt, capslock+alt: '\u20ac' +} + +key S { + label: 'S' + number: '7' + base: 's' + shift, capslock: 'S' + alt: '\\' + shift+alt, capslock+alt: '\u00df' +} + +key T { + label: 'T' + number: '8' + base: 't' + shift, capslock: 'T' + alt: '{' + shift+alt, capslock+alt: '\u00a3' +} + +key U { + label: 'U' + number: '8' + base: 'u' + shift, capslock: 'U' + alt: '_' + shift+alt, capslock+alt: '\u0308' +} + +key V { + label: 'V' + number: '8' + base: 'v' + shift, capslock: 'V' + alt: 'v' + shift+alt, capslock+alt: 'V' +} + +key W { + label: 'W' + number: '9' + base: 'w' + shift, capslock: 'W' + alt: '~' + shift+alt, capslock+alt: '~' +} + +key X { + label: 'X' + number: '9' + base: 'x' + shift, capslock: 'X' + alt: 'x' + shift+alt, capslock+alt: '\uef00' +} + +key Y { + label: 'Y' + number: '9' + base: 'y' + shift, capslock: 'Y' + alt: '}' + shift+alt, capslock+alt: '\u00a1' +} + +key Z { + label: 'Z' + number: '9' + base: 'z' + shift, capslock: 'Z' + alt: 'z' + shift+alt, capslock+alt: 'Z' +} + +key COMMA { + label: ',' + number: ',' + base: ',' + shift: '<' + alt: ',' + shift+alt: ',' +} + +key PERIOD { + label: '.' + number: '.' + base: '.' + shift: '>' + alt: '.' + shift+alt: '\u2026' +} + +key AT { + label: '@' + number: '@' + base: '@' + shift: '@' + alt: '@' + shift+alt: '\u2022' +} + +key SLASH { + label: '/' + number: '/' + base: '/' + shift: '?' + alt: '?' + shift+alt: '?' +} + +key SPACE { + label: ' ' + number: ' ' + base: ' ' + shift: ' ' + alt: '\uef01' + shift+alt: '\uef01' +} + +key ENTER { + label: '\n' + number: '\n' + base: '\n' + shift: '\n' + alt: '\n' + shift+alt: '\n' +} + +key TAB { + label: '\t' + number: '\t' + base: '\t' + shift: '\t' + alt: '\t' + shift+alt: '\t' +} + +key 0 { + label: '0' + number: '0' + base: '0' + shift: ')' + alt: ')' + shift+alt: ')' +} + +key 1 { + label: '1' + number: '1' + base: '1' + shift: '!' + alt: '!' + shift+alt: '!' +} + +key 2 { + label: '2' + number: '2' + base: '2' + shift: '@' + alt: '@' + shift+alt: '@' +} + +key 3 { + label: '3' + number: '3' + base: '3' + shift: '#' + alt: '#' + shift+alt: '#' +} + +key 4 { + label: '4' + number: '4' + base: '4' + shift: '$' + alt: '$' + shift+alt: '$' +} + +key 5 { + label: '5' + number: '5' + base: '5' + shift: '%' + alt: '%' + shift+alt: '%' +} + +key 6 { + label: '6' + number: '6' + base: '6' + shift: '^' + alt: '^' + shift+alt: '^' +} + +key 7 { + label: '7' + number: '7' + base: '7' + shift: '&' + alt: '&' + shift+alt: '&' +} + +key 8 { + label: '8' + number: '8' + base: '8' + shift: '*' + alt: '*' + shift+alt: '*' +} + +key 9 { + label: '9' + number: '9' + base: '9' + shift: '(' + alt: '(' + shift+alt: '(' +} + +key GRAVE { + label: '`' + number: '`' + base: '`' + shift: '~' + alt: '`' + shift+alt: '~' +} + +key MINUS { + label: '-' + number: '-' + base: '-' + shift: '_' + alt: '-' + shift+alt: '_' +} + +key EQUALS { + label: '=' + number: '=' + base: '=' + shift: '+' + alt: '=' + shift+alt: '+' +} + +key LEFT_BRACKET { + label: '[' + number: '[' + base: '[' + shift: '{' + alt: '[' + shift+alt: '{' +} + +key RIGHT_BRACKET { + label: ']' + number: ']' + base: ']' + shift: '}' + alt: ']' + shift+alt: '}' +} + +key BACKSLASH { + label: '\\' + number: '\\' + base: '\\' + shift: '|' + alt: '\\' + shift+alt: '|' +} + +key SEMICOLON { + label: ';' + number: ';' + base: ';' + shift: ':' + alt: ';' + shift+alt: ':' +} + +key APOSTROPHE { + label: '\'' + number: '\'' + base: '\'' + shift: '"' + alt: '\'' + shift+alt: '"' +} + +key STAR { + label: '*' + number: '*' + base: '*' + shift: '*' + alt: '*' + shift+alt: '*' +} + +key POUND { + label: '#' + number: '#' + base: '#' + shift: '#' + alt: '#' + shift+alt: '#' +} + +key PLUS { + label: '+' + number: '+' + base: '+' + shift: '+' + alt: '+' + shift+alt: '+' +} diff --git a/data/keyboards/qwerty2.kl b/data/keyboards/qwerty2.kl new file mode 100644 index 0000000..863a258 --- /dev/null +++ b/data/keyboards/qwerty2.kl @@ -0,0 +1,109 @@ +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Emulator keyboard layout #2. +# + +key 399 GRAVE +key 2 1 +key 3 2 +key 4 3 +key 5 4 +key 6 5 +key 7 6 +key 8 7 +key 9 8 +key 10 9 +key 11 0 +key 158 BACK WAKE_DROPPED +key 230 SOFT_RIGHT WAKE +key 60 SOFT_RIGHT WAKE +key 107 ENDCALL WAKE_DROPPED +key 62 ENDCALL WAKE_DROPPED +key 229 MENU WAKE_DROPPED +key 139 MENU WAKE_DROPPED +key 59 MENU WAKE_DROPPED +key 127 SEARCH WAKE_DROPPED +key 217 SEARCH WAKE_DROPPED +key 228 POUND +key 227 STAR +key 231 CALL WAKE_DROPPED +key 61 CALL WAKE_DROPPED +key 232 DPAD_CENTER WAKE_DROPPED +key 108 DPAD_DOWN WAKE_DROPPED +key 103 DPAD_UP WAKE_DROPPED +key 102 HOME WAKE +key 105 DPAD_LEFT WAKE_DROPPED +key 106 DPAD_RIGHT WAKE_DROPPED +key 115 VOLUME_UP WAKE +key 114 VOLUME_DOWN WAKE +key 116 POWER WAKE +key 212 CAMERA + +key 16 Q +key 17 W +key 18 E +key 19 R +key 20 T +key 21 Y +key 22 U +key 23 I +key 24 O +key 25 P +key 26 LEFT_BRACKET +key 27 RIGHT_BRACKET +key 43 BACKSLASH + +key 30 A +key 31 S +key 32 D +key 33 F +key 34 G +key 35 H +key 36 J +key 37 K +key 38 L +key 39 SEMICOLON +key 40 APOSTROPHE +key 14 DEL + +key 44 Z +key 45 X +key 46 C +key 47 V +key 48 B +key 49 N +key 50 M +key 51 COMMA +key 52 PERIOD +key 53 SLASH +key 28 ENTER + +key 56 ALT_LEFT +key 100 ALT_RIGHT +key 42 SHIFT_LEFT +key 54 SHIFT_RIGHT +key 15 TAB +key 57 SPACE +key 150 EXPLORER +key 155 ENVELOPE + +key 12 MINUS +key 13 EQUALS +key 215 AT + +# On an AT keyboard: ESC, F10 +key 1 BACK WAKE_DROPPED +key 68 MENU WAKE_DROPPED diff --git a/data/sounds/AudioPackage5.mk b/data/sounds/AudioPackage5.mk new file mode 100755 index 0000000..550f990 --- /dev/null +++ b/data/sounds/AudioPackage5.mk @@ -0,0 +1,72 @@ +# +# Audio Package 5 - Crespo/Soju +# +# Include this file in a product makefile to include these audio files +# +# + +LOCAL_PATH:= frameworks/base/data/sounds + +PRODUCT_COPY_FILES += \ + $(LOCAL_PATH)/Alarm_Buzzer.ogg:system/media/audio/alarms/Alarm_Buzzer.ogg \ + $(LOCAL_PATH)/Alarm_Beep_01.ogg:system/media/audio/alarms/Alarm_Beep_01.ogg \ + $(LOCAL_PATH)/Alarm_Beep_02.ogg:system/media/audio/alarms/Alarm_Beep_02.ogg \ + $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \ + $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \ + $(LOCAL_PATH)/effects/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \ + $(LOCAL_PATH)/effects/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \ + $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ + $(LOCAL_PATH)/effects/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \ + $(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \ + $(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \ + $(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \ + $(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \ + $(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \ + $(LOCAL_PATH)/effects/Undock.ogg:system/media/audio/ui/Undock.ogg \ + $(LOCAL_PATH)/effects/Lock.ogg:system/media/audio/ui/Lock.ogg \ + $(LOCAL_PATH)/effects/Unlock.ogg:system/media/audio/ui/Unlock.ogg \ + $(LOCAL_PATH)/notifications/Aldebaran.ogg:system/media/audio/notifications/Aldebaran.ogg \ + $(LOCAL_PATH)/notifications/Altair.ogg:system/media/audio/notifications/Altair.ogg \ + $(LOCAL_PATH)/notifications/Antares.ogg:system/media/audio/notifications/Antares.ogg \ + $(LOCAL_PATH)/notifications/arcturus.ogg:system/media/audio/notifications/arcturus.ogg \ + $(LOCAL_PATH)/notifications/Betelgeuse.ogg:system/media/audio/notifications/Betelgeuse.ogg \ + $(LOCAL_PATH)/notifications/Canopus.ogg:system/media/audio/notifications/Canopus.ogg \ + $(LOCAL_PATH)/notifications/Capella.ogg:system/media/audio/notifications/Capella.ogg \ + $(LOCAL_PATH)/notifications/Castor.ogg:system/media/audio/notifications/Castor.ogg \ + $(LOCAL_PATH)/notifications/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg \ + $(LOCAL_PATH)/notifications/Deneb.ogg:system/media/audio/notifications/Deneb.ogg \ + $(LOCAL_PATH)/notifications/Electra.ogg:system/media/audio/notifications/Electra.ogg \ + $(LOCAL_PATH)/notifications/Fomalhaut.ogg:system/media/audio/notifications/Fomalhaut.ogg \ + $(LOCAL_PATH)/notifications/Merope.ogg:system/media/audio/notifications/Merope.ogg \ + $(LOCAL_PATH)/notifications/Polaris.ogg:system/media/audio/notifications/Polaris.ogg \ + $(LOCAL_PATH)/notifications/Pollux.ogg:system/media/audio/notifications/Pollux.ogg \ + $(LOCAL_PATH)/notifications/Procyon.ogg:system/media/audio/notifications/Procyon.ogg \ + $(LOCAL_PATH)/notifications/regulus.ogg:system/media/audio/notifications/regulus.ogg \ + $(LOCAL_PATH)/notifications/sirius.ogg:system/media/audio/notifications/sirius.ogg \ + $(LOCAL_PATH)/notifications/Sirrah.ogg:system/media/audio/notifications/Sirrah.ogg \ + $(LOCAL_PATH)/notifications/vega.ogg:system/media/audio/notifications/vega.ogg \ + $(LOCAL_PATH)/ringtones/ANDROMEDA.ogg:system/media/audio/ringtones/ANDROMEDA.ogg \ + $(LOCAL_PATH)/ringtones/Aquila.ogg:system/media/audio/ringtones/Aquila.ogg \ + $(LOCAL_PATH)/ringtones/ArgoNavis.ogg:system/media/audio/ringtones/ArgoNavis.ogg \ + $(LOCAL_PATH)/ringtones/BOOTES.ogg:system/media/audio/ringtones/BOOTES.ogg \ + $(LOCAL_PATH)/ringtones/CANISMAJOR.ogg:system/media/audio/ringtones/CANISMAJOR.ogg \ + $(LOCAL_PATH)/ringtones/Carina.ogg:system/media/audio/ringtones/Carina.ogg \ + $(LOCAL_PATH)/ringtones/CASSIOPEIA.ogg:system/media/audio/ringtones/CASSIOPEIA.ogg \ + $(LOCAL_PATH)/ringtones/Centaurus.ogg:system/media/audio/ringtones/Centaurus.ogg \ + $(LOCAL_PATH)/ringtones/Cygnus.ogg:system/media/audio/ringtones/Cygnus.ogg \ + $(LOCAL_PATH)/ringtones/Draco.ogg:system/media/audio/ringtones/Draco.ogg \ + $(LOCAL_PATH)/ringtones/Eridani.ogg:system/media/audio/ringtones/Eridani.ogg \ + $(LOCAL_PATH)/ringtones/hydra.ogg:system/media/audio/ringtones/hydra.ogg \ + $(LOCAL_PATH)/ringtones/Lyra.ogg:system/media/audio/ringtones/Lyra.ogg \ + $(LOCAL_PATH)/ringtones/Machina.ogg:system/media/audio/ringtones/Machina.ogg \ + $(LOCAL_PATH)/ringtones/Orion.ogg:system/media/audio/ringtones/Orion.ogg \ + $(LOCAL_PATH)/ringtones/Pegasus.ogg:system/media/audio/ringtones/Pegasus.ogg \ + $(LOCAL_PATH)/ringtones/PERSEUS.ogg:system/media/audio/ringtones/PERSEUS.ogg \ + $(LOCAL_PATH)/ringtones/Pyxis.ogg:system/media/audio/ringtones/Pyxis.ogg \ + $(LOCAL_PATH)/ringtones/Rigel.ogg:system/media/audio/ringtones/Rigel.ogg \ + $(LOCAL_PATH)/ringtones/Scarabaeus.ogg:system/media/audio/ringtones/Scarabaeus.ogg \ + $(LOCAL_PATH)/ringtones/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg \ + $(LOCAL_PATH)/ringtones/Solarium.ogg:system/media/audio/ringtones/Solarium.ogg \ + $(LOCAL_PATH)/ringtones/Testudo.ogg:system/media/audio/ringtones/Testudo.ogg \ + $(LOCAL_PATH)/ringtones/URSAMINOR.ogg:system/media/audio/ringtones/URSAMINOR.ogg \ + $(LOCAL_PATH)/ringtones/Vespa.ogg:system/media/audio/ringtones/Vespa.ogg diff --git a/data/sounds/notifications/Aldebaran.ogg b/data/sounds/notifications/Aldebaran.ogg Binary files differnew file mode 100755 index 0000000..fe30137 --- /dev/null +++ b/data/sounds/notifications/Aldebaran.ogg diff --git a/data/sounds/notifications/Altair.ogg b/data/sounds/notifications/Altair.ogg Binary files differnew file mode 100644 index 0000000..660c800 --- /dev/null +++ b/data/sounds/notifications/Altair.ogg diff --git a/data/sounds/notifications/Antares.ogg b/data/sounds/notifications/Antares.ogg Binary files differnew file mode 100755 index 0000000..f4f94d7 --- /dev/null +++ b/data/sounds/notifications/Antares.ogg diff --git a/data/sounds/notifications/Betelgeuse.ogg b/data/sounds/notifications/Betelgeuse.ogg Binary files differnew file mode 100755 index 0000000..7145a16 --- /dev/null +++ b/data/sounds/notifications/Betelgeuse.ogg diff --git a/data/sounds/notifications/Canopus.ogg b/data/sounds/notifications/Canopus.ogg Binary files differnew file mode 100755 index 0000000..0d55925 --- /dev/null +++ b/data/sounds/notifications/Canopus.ogg diff --git a/data/sounds/notifications/Capella.ogg b/data/sounds/notifications/Capella.ogg Binary files differnew file mode 100644 index 0000000..ae4f3c5 --- /dev/null +++ b/data/sounds/notifications/Capella.ogg diff --git a/data/sounds/notifications/Castor.ogg b/data/sounds/notifications/Castor.ogg Binary files differnew file mode 100644 index 0000000..92de8e7 --- /dev/null +++ b/data/sounds/notifications/Castor.ogg diff --git a/data/sounds/notifications/CetiAlpha.ogg b/data/sounds/notifications/CetiAlpha.ogg Binary files differnew file mode 100755 index 0000000..cd09526 --- /dev/null +++ b/data/sounds/notifications/CetiAlpha.ogg diff --git a/data/sounds/notifications/CetiAlpha.wav b/data/sounds/notifications/CetiAlpha.wav Binary files differnew file mode 100755 index 0000000..d209645 --- /dev/null +++ b/data/sounds/notifications/CetiAlpha.wav diff --git a/data/sounds/notifications/Deneb.ogg b/data/sounds/notifications/Deneb.ogg Binary files differnew file mode 100644 index 0000000..3b17e28 --- /dev/null +++ b/data/sounds/notifications/Deneb.ogg diff --git a/data/sounds/notifications/Electra.ogg b/data/sounds/notifications/Electra.ogg Binary files differnew file mode 100644 index 0000000..9335d8d --- /dev/null +++ b/data/sounds/notifications/Electra.ogg diff --git a/data/sounds/notifications/Fomalhaut.ogg b/data/sounds/notifications/Fomalhaut.ogg Binary files differnew file mode 100644 index 0000000..9448c18 --- /dev/null +++ b/data/sounds/notifications/Fomalhaut.ogg diff --git a/data/sounds/notifications/Merope.ogg b/data/sounds/notifications/Merope.ogg Binary files differnew file mode 100755 index 0000000..fb18c73 --- /dev/null +++ b/data/sounds/notifications/Merope.ogg diff --git a/data/sounds/notifications/Polaris.ogg b/data/sounds/notifications/Polaris.ogg Binary files differnew file mode 100644 index 0000000..d5b991f --- /dev/null +++ b/data/sounds/notifications/Polaris.ogg diff --git a/data/sounds/notifications/Pollux.ogg b/data/sounds/notifications/Pollux.ogg Binary files differnew file mode 100644 index 0000000..d37c75c --- /dev/null +++ b/data/sounds/notifications/Pollux.ogg diff --git a/data/sounds/notifications/Procyon.ogg b/data/sounds/notifications/Procyon.ogg Binary files differnew file mode 100644 index 0000000..93d1557 --- /dev/null +++ b/data/sounds/notifications/Procyon.ogg diff --git a/data/sounds/notifications/Sirrah.ogg b/data/sounds/notifications/Sirrah.ogg Binary files differnew file mode 100755 index 0000000..ee79e49 --- /dev/null +++ b/data/sounds/notifications/Sirrah.ogg diff --git a/data/sounds/notifications/arcturus.ogg b/data/sounds/notifications/arcturus.ogg Binary files differnew file mode 100644 index 0000000..9d73103 --- /dev/null +++ b/data/sounds/notifications/arcturus.ogg diff --git a/data/sounds/notifications/regulus.ogg b/data/sounds/notifications/regulus.ogg Binary files differnew file mode 100644 index 0000000..4f28d9e --- /dev/null +++ b/data/sounds/notifications/regulus.ogg diff --git a/data/sounds/notifications/sirius.ogg b/data/sounds/notifications/sirius.ogg Binary files differnew file mode 100644 index 0000000..78c9991 --- /dev/null +++ b/data/sounds/notifications/sirius.ogg diff --git a/data/sounds/notifications/vega.ogg b/data/sounds/notifications/vega.ogg Binary files differnew file mode 100644 index 0000000..e596e60 --- /dev/null +++ b/data/sounds/notifications/vega.ogg diff --git a/data/sounds/ringtones/ANDROMEDA.ogg b/data/sounds/ringtones/ANDROMEDA.ogg Binary files differnew file mode 100644 index 0000000..8f6bd3e --- /dev/null +++ b/data/sounds/ringtones/ANDROMEDA.ogg diff --git a/data/sounds/ringtones/Aquila.ogg b/data/sounds/ringtones/Aquila.ogg Binary files differnew file mode 100644 index 0000000..b391be9 --- /dev/null +++ b/data/sounds/ringtones/Aquila.ogg diff --git a/data/sounds/ringtones/ArgoNavis.ogg b/data/sounds/ringtones/ArgoNavis.ogg Binary files differnew file mode 100644 index 0000000..b4202ac --- /dev/null +++ b/data/sounds/ringtones/ArgoNavis.ogg diff --git a/data/sounds/ringtones/BOOTES.ogg b/data/sounds/ringtones/BOOTES.ogg Binary files differnew file mode 100644 index 0000000..0716a4f --- /dev/null +++ b/data/sounds/ringtones/BOOTES.ogg diff --git a/data/sounds/ringtones/CANISMAJOR.ogg b/data/sounds/ringtones/CANISMAJOR.ogg Binary files differnew file mode 100644 index 0000000..177d3de --- /dev/null +++ b/data/sounds/ringtones/CANISMAJOR.ogg diff --git a/data/sounds/ringtones/CASSIOPEIA.ogg b/data/sounds/ringtones/CASSIOPEIA.ogg Binary files differnew file mode 100644 index 0000000..c4a7948 --- /dev/null +++ b/data/sounds/ringtones/CASSIOPEIA.ogg diff --git a/data/sounds/ringtones/Carina.ogg b/data/sounds/ringtones/Carina.ogg Binary files differnew file mode 100644 index 0000000..aeb9b36 --- /dev/null +++ b/data/sounds/ringtones/Carina.ogg diff --git a/data/sounds/ringtones/Carina.wav b/data/sounds/ringtones/Carina.wav Binary files differnew file mode 100755 index 0000000..ecaeb58 --- /dev/null +++ b/data/sounds/ringtones/Carina.wav diff --git a/data/sounds/ringtones/Centaurus.ogg b/data/sounds/ringtones/Centaurus.ogg Binary files differnew file mode 100644 index 0000000..404bdbc --- /dev/null +++ b/data/sounds/ringtones/Centaurus.ogg diff --git a/data/sounds/ringtones/Cygnus.ogg b/data/sounds/ringtones/Cygnus.ogg Binary files differnew file mode 100644 index 0000000..b2e1e65 --- /dev/null +++ b/data/sounds/ringtones/Cygnus.ogg diff --git a/data/sounds/ringtones/Draco.ogg b/data/sounds/ringtones/Draco.ogg Binary files differnew file mode 100644 index 0000000..88d5a04 --- /dev/null +++ b/data/sounds/ringtones/Draco.ogg diff --git a/data/sounds/ringtones/Eridani.ogg b/data/sounds/ringtones/Eridani.ogg Binary files differnew file mode 100644 index 0000000..b665a29 --- /dev/null +++ b/data/sounds/ringtones/Eridani.ogg diff --git a/data/sounds/ringtones/Lyra.ogg b/data/sounds/ringtones/Lyra.ogg Binary files differnew file mode 100644 index 0000000..696f278 --- /dev/null +++ b/data/sounds/ringtones/Lyra.ogg diff --git a/data/sounds/ringtones/Machina.ogg b/data/sounds/ringtones/Machina.ogg Binary files differnew file mode 100644 index 0000000..ac16f7e --- /dev/null +++ b/data/sounds/ringtones/Machina.ogg diff --git a/data/sounds/ringtones/Orion.ogg b/data/sounds/ringtones/Orion.ogg Binary files differnew file mode 100644 index 0000000..807f107 --- /dev/null +++ b/data/sounds/ringtones/Orion.ogg diff --git a/data/sounds/ringtones/PERSEUS.ogg b/data/sounds/ringtones/PERSEUS.ogg Binary files differnew file mode 100644 index 0000000..ad06021 --- /dev/null +++ b/data/sounds/ringtones/PERSEUS.ogg diff --git a/data/sounds/ringtones/Pegasus.ogg b/data/sounds/ringtones/Pegasus.ogg Binary files differnew file mode 100644 index 0000000..66c4970 --- /dev/null +++ b/data/sounds/ringtones/Pegasus.ogg diff --git a/data/sounds/ringtones/Pyxis.ogg b/data/sounds/ringtones/Pyxis.ogg Binary files differnew file mode 100644 index 0000000..2d3adce --- /dev/null +++ b/data/sounds/ringtones/Pyxis.ogg diff --git a/data/sounds/ringtones/Rigel.ogg b/data/sounds/ringtones/Rigel.ogg Binary files differnew file mode 100644 index 0000000..af2c176 --- /dev/null +++ b/data/sounds/ringtones/Rigel.ogg diff --git a/data/sounds/ringtones/Scarabaeus.ogg b/data/sounds/ringtones/Scarabaeus.ogg Binary files differnew file mode 100644 index 0000000..e70fc69 --- /dev/null +++ b/data/sounds/ringtones/Scarabaeus.ogg diff --git a/data/sounds/ringtones/Sceptrum.ogg b/data/sounds/ringtones/Sceptrum.ogg Binary files differnew file mode 100644 index 0000000..fc50aef --- /dev/null +++ b/data/sounds/ringtones/Sceptrum.ogg diff --git a/data/sounds/ringtones/Solarium.ogg b/data/sounds/ringtones/Solarium.ogg Binary files differnew file mode 100644 index 0000000..d27f141 --- /dev/null +++ b/data/sounds/ringtones/Solarium.ogg diff --git a/data/sounds/ringtones/Testudo.ogg b/data/sounds/ringtones/Testudo.ogg Binary files differnew file mode 100644 index 0000000..0ca8d6b --- /dev/null +++ b/data/sounds/ringtones/Testudo.ogg diff --git a/data/sounds/ringtones/URSAMINOR.ogg b/data/sounds/ringtones/URSAMINOR.ogg Binary files differnew file mode 100644 index 0000000..c0010e82 --- /dev/null +++ b/data/sounds/ringtones/URSAMINOR.ogg diff --git a/data/sounds/ringtones/Vespa.ogg b/data/sounds/ringtones/Vespa.ogg Binary files differnew file mode 100644 index 0000000..4423bbb --- /dev/null +++ b/data/sounds/ringtones/Vespa.ogg diff --git a/data/sounds/ringtones/hydra.ogg b/data/sounds/ringtones/hydra.ogg Binary files differnew file mode 100644 index 0000000..edde14f --- /dev/null +++ b/data/sounds/ringtones/hydra.ogg diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index 3cc1806..2548128 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -71,6 +71,9 @@ <li><a href="<?cs var:toroot ?>guide/topics/ui/menus.html"> <span class="en">Creating Menus</span> </a></li> + <li><a href="<?cs var:toroot ?>guide/topics/ui/actionbar.html"> + <span class="en">Using the Action Bar</span> + </a> <span class="new">new!</span></li> <li><a href="<?cs var:toroot ?>guide/topics/ui/dialogs.html"> <span class="en">Creating Dialogs</span> </a></li> diff --git a/docs/html/guide/topics/fragments/index.jd b/docs/html/guide/topics/fragments/index.jd index ce10ef7..766146e 100644 --- a/docs/html/guide/topics/fragments/index.jd +++ b/docs/html/guide/topics/fragments/index.jd @@ -399,7 +399,7 @@ in the back stack:</p> Fragment newFragment = new MyFragment(); FragmentTransaction ft = openFragmentTransaction(); // Replace and add to back stack -ft.replace(newFragment, R.id.myfragment); +ft.replace(R.id.myfragment, newFragment); ft.addToBackStack(null); // Apply changes ft.commit(); diff --git a/docs/html/guide/topics/fundamentals.jd b/docs/html/guide/topics/fundamentals.jd index 84c2ed2..fffc1cd 100644 --- a/docs/html/guide/topics/fundamentals.jd +++ b/docs/html/guide/topics/fundamentals.jd @@ -19,6 +19,7 @@ page.title=Application Fundamentals <li><a href="#lmodes">Launch modes</a></li> <li><a href="#clearstack">Clearing the stack</a></li> <li><a href="#starttask">Starting tasks</a></li> + <li><a href="#commonpatterns">Common patterns</a></li> </ol></li> <li><a href="#procthread">Processes and Threads</a> <ol> @@ -892,6 +893,60 @@ See <a href="#clearstack">Clearing the stack</a>, earlier. </p> +<h3 id="commonpatterns">Common patterns</h3> + +<p> +In most cases an application won't use any flags or special features. +This gives the standard interaction the user expects: launching the application +brings any existing task to the foreground, or starts the main activity in +a new task if there isn't one. +</p> + +<p> +If an application posts notifications, it needs to decide how the user's +selection of a notification should impact any currently running task. The +current suggested behavior is that any current tasks be completely removed, +replaced with a new task containing a stack of activities representing where +the user is jumping in to the app. This can be accomplished with a combination +of the {@link android.app.PendingIntent#getActivities PendingIntent.getActivities()} +method and {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK Intent.FLAG_ACTIVITY_CLEAR_TASK}. +</p> + +<p> +For example, here is sample code to build an array of Intents to launch an +application into an activity three levels deep. The first Intent is always +the main Intent of the application as started by the launcher. The exact +details of the Intent data will of course depend on your application. +</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/StatusBarNotifications.java + intent_array} + +<p> +In some cases an application may not want to directly launch its application +from a notification, but instead go to a intermediate summary activity. To +accomplish this, the summary activity should be given a task affinity that +is different from the main application (one will typically give it no affinity, +that is "") so that it does not get launched into any existing application task. +</p> + +{@sample development/samples/ApiDemos/AndroidManifest.xml no_task_affinity} + +<p> +The PendingIntent to launch this then does not need to supply anything special: +</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/IncomingMessage.java + pending_intent} + +<p> +If an application implements an app widget, it should generally use the same +approach as the first one for notifications: when the user taps on the app +widget it should throw away any current task of the application and start a +new task with potentially multiple activities representing the state the +user is jumping in to. +</p> + <h2 id="procthread">Processes and Threads</h2> <p> diff --git a/docs/html/guide/topics/ui/actionbar.jd b/docs/html/guide/topics/ui/actionbar.jd new file mode 100644 index 0000000..2b942e7 --- /dev/null +++ b/docs/html/guide/topics/ui/actionbar.jd @@ -0,0 +1,332 @@ +page.title=Using the Action Bar +parent.title=User Interface +parent.link=index.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + + <h2>Quickview</h2> + <ul> + <li>A replacement for the title bar for displaying global actions for the activity</li> + <li>Provides toolbar actions and modes of navigating around the application</li> + <li>Switches to contextual menu options when one or more items are selected</li> + <li>Requires API Level HONEYCOMB</li> + </ul> + + <h2>In this document</h2> + <ol> + <li><a href="#Adding">Adding the Action Bar</a></li> + <li><a href="#ActionItems">Adding Action Items</a> + <ol> + <li><a href="#Home">Using the application icon as an action item</a></li> + </ol> + </li> + <li><a href="#Tabs">Adding Tabs</a></li> + <li><a href="#Dropdown">Adding Drop-down Navigation</a></li> + <li><a href="#Search">Adding Search</a></li> + </ol> + + <h2>Key classes</h2> + <ol> + <li>{@link android.app.ActionBar}</li> + <li>{@link android.view.Menu}</li> + </ol> + + <h2>See also</h2> + <ol> + <li><a href="{@docRoot}guide/topics/ui/menus.html">Creating Menus</a></li> + </ol> +</div> +</div> + +<p>The action bar is a widget for activities that replaces the traditional title bar at +the top of an activity. By default, the action bar includes the application logo on the left side, +followed by the activity title. The action bar offers several useful features for +applications—especially those targeted to tablet devices. The action bar features include +the ability to:</p> + +<ul> + <li>Display menu items from the <a +href="{@docRoot}guide/topics/ui/menus.html#OptionsMenu">options menu</a> as "action +items"—providing instant access to key user actions.</li> + <li>Provide tabs for navigating between <a +href="{@docRoot}guide/topics/fragments/index.html">fragments</a>.</li> + <li>Provide drop-down navigation items.</li> + <li>Embed a {@link android.widget.SearchView} for instant searching.</li> + <li>Use the application logo as a "return home" or "up" navigation action.</li> +</ul> + +<img src="{@docRoot}images/ui/actionbar.png" height="36" alt="" /> +<p class="img-caption"><strong>Figure 1.</strong> A screenshot of the action bar in the NotePad +sample application, containing action items to save and delete the note.</p> + + +<h2 id="Adding">Adding the Action Bar</h2> + +<p>To add the Action Bar to your activity, apply the holographic theme—{@code +Theme.Holo}—or the action bar theme—{@code Theme.WithActionBar}—in your manifest +file. For example:</p> + +<pre> +<activity android:theme="@android:style/Theme.Holo" > +</pre> + +<p>The activity now appears with the action bar in place of the traditional title bar.</p> + + +<h2 id="ActionItems">Adding Action Items</h2> + +<p>For each action item you want to add to the action bar, you must add a menu item to the +activity's <a href="{@docRoot}guide/topics/ui/menus.html#OptionsMenu">options menu</a> and declare +that the item be shown as an action, using the {@code android:showAsAction} attribute in the menu +XML or with {@link android.view.MenuItem#setShowAsAction setShowAsAction()} on the {@link +android.view.MenuItem}.</p> + +<div class="figure" style="width:359px"> + <img src="{@docRoot}images/ui/actionbar-item-withtext.png" height="57" alt="" /> + <p class="img-caption"><strong>Figure 2.</strong> A screenshot from an action bar with two +action items.</p> +</div> + +<p>You can specify a menu item to appear as an action item in the action bar—if there is room +for it—from the <a href="{@docRoot}guide/topics/resources/menu-resource.html">menu +resource</a> by declaring {@code +android:showAsAction="ifRoom"} for the {@code <item>} element. This way, the item will display +in the action bar for quick access only if there is room available for it—if there's not +enough room, the item is placed the options menu (revealed by the drop-down icon on the right side +of the action bar). From your application code, you can specify the item to appear as an action item +by calling {@link android.view.MenuItem#setShowAsAction setShowAsAction()} on the {@link +android.view.MenuItem} and passing {@link android.view.MenuItem#SHOW_AS_ACTION_IF_ROOM}.</p> + +<p>If your item supplies both a title and an icon, then the action item shows only +the icon by defult. If you want to include the text with the action item, add the <em>with text</em> +flag—in XML, add {@code withText} to the {@code android:showAsAction} attribute or, in +your application code, use the {@link android.view.MenuItem#SHOW_AS_ACTION_WITH_TEXT} flag when +calling {@link android.view.MenuItem#setShowAsAction setShowAsAction()}. Figure 2 shows a screenshot +of an action bar with two action items that include text.</p> + +<p>Here's an example of how you can declare a menu item as an action item in a <a +href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a> file:</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/menu_add" + android:icon="@drawable/ic_menu_save" + android:title="@string/menu_save" + <b>android:showAsAction="ifRoom|withText"</b> /> +</menu> +</pre> + +<p>In this case, both the {@code ifRoom} and {@code withText} flags are set, so that when this +item appears as an action item, it includes the title text along with the icon.</p> + +<p>A menu item placed in the action bar triggers the same callback methods as other items in the +options menu. When the user selects an item in the action bar, your activity receives a call to +{@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}, passing the +item ID. (If you added the item from a fragment, then the respective {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} method is called +for that fragment.)</p> + +<p class="note"><strong>Note:</strong> Even menu items that are contained in the options menu +(and not shown as action items) will show an icon, so when using the action bar, you should +provide an icon for every item in the options menu.</p> + +<p>You can also declare an item to <em>always</em> appear as an action item, but you should avoid +doing so. Most of the time, there will be enough room for several action items and they will appear +in the order you declare them. If you set items to always appear as action +items (instead of <em>if room</em>), then they are added without discrimination and there is a risk +that they will collide with other elements in the action bar, such as tabs or custom views.</p> + +<p>For more information about menus, see the <a +href="{@docRoot}guide/topics/ui/menus.html#options-menu">Creating Menus</a> developer guide.</p> + + +<h3 id="Home">Using the application icon as an action item</h3> + +<p>By default, the application icon appears in the action bar on the left side, but does nothing +when tapped. To use the application icon as an action item when tapped, you simply need to add a +condition to your {@link android.app.Activity#onOptionsItemSelected onOptionsItemSelected()} method +that performs an action when the {@link android.view.MenuItem} ID is {@code android.R.id.home}. +This ID is delivered every time the user taps the application icon.</p> + +<p>For example, here's an implementation of {@link android.app.Activity#onOptionsItemSelected +onOptionsItemSelected()} that returns to the application's "home" activity:</p> + +<pre> +@Override +public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + // app icon in action bar clicked; go home + Intent intent = new Intent(this, HomeActivity.class); + startActivity(intent); + break; + } + return super.onOptionsItemSelected(item); +} +</pre> + +<div class="figure" style="width:144px"> + <img src="{@docRoot}images/ui/actionbar-logo.png" height="140" alt="" /> + <p class="img-caption"><strong>Figure 3.</strong> The standard icon for the Email application +(top) and the "up navigation" icon (bottom).</p> +</div> + +<p>You can also use the application icon to provide "up" navigation. The way you handle the event +when a user taps the icon is the same, but if the user experience for the event is to <em>navigate +up to the parent activity</em>, then you should indicate this behavior by setting the +action bar to "show home as up." You can do so by calling {@link +android.app.ActionBar#setDisplayOptions setDisplayOptions()} on your activity's {@link +android.app.ActionBar}, and passing the {@link +android.app.ActionBar#DISPLAY_HOME_AS_UP} display option.</p> + +<p>To get the {@link android.app.ActionBar}, call {@link android.app.Activity#getActionBar} from +your {@link android.app.Activity} during {@link android.app.Activity#onCreate onCreate()} (but be +sure you do so <em>after</em> you've called {@link android.app.Activity#setContentView +setContentView()}).</p> + +<p>For example, here's how you can change the action bar display mode to show the application +icon as an "up" action:</p> + +<pre> +@Override +protected void onStart() { + super.onStart(); + ActionBar actionBar = this.getActionBar(); + actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP); +} +</pre> + +<p class="caution"><strong>Caution:</strong> If your activity does not have an action bar (if you +did not set the theme of your activity or application to the holographic or action bar theme), then +{@link android.app.Activity#getActionBar} returns null.</p> + + +<h2 id="Tabs">Adding Tabs</h2> + +<p>The action bar can display tabs that allow the user navigate between different fragments in the +activity. Each tab can include a title and/or an icon.</p> + +<p>To begin, your layout must include a {@link android.view.View} in which each {@link +android.app.Fragment} associated with a tab is displayed. Be sure the view has an ID that you +can use to reference it from your code.</p> + +<p>To add tabs to the action bar:</p> +<ol> + <li>Create an implementation of {@link android.app.ActionBar.TabListener} to handle the +interaction events on the action bar tabs. You must implement all methods: {@link +android.app.ActionBar.TabListener#onTabSelected onTabSelected()}, {@link +android.app.ActionBar.TabListener#onTabUnselected onTabUnselected()}, and {@link +android.app.ActionBar.TabListener#onTabReselected onTabReselected()}. + <p>Each callback method passes the {@link android.app.ActionBar.Tab} that received the +event and a {@link android.app.FragmentTransaction} for you to perform the fragment +transactions (add or remove fragments).</p> + <p>For example:</p> +<pre> +private class MyTabListener implements ActionBar.TabListener { + private TabContentFragment mFragment; + + // Called to create an instance of the listener when adding a new tab + public TabListener(TabContentFragment fragment) { + mFragment = fragment; + } + + @Override + public void onTabSelected(Tab tab, FragmentTransaction ft) { + ft.add(R.id.fragment_content, mFragment, null); + } + + @Override + public void onTabUnselected(Tab tab, FragmentTransaction ft) { + ft.remove(mFragment); + } + + @Override + public void onTabReselected(Tab tab, FragmentTransaction ft) { + // do nothing + } + +} +</pre> + <p>This implementation of {@link android.app.ActionBar.TabListener} adds a constructor +that saves the {@link android.app.Fragment} associated with a tab so that each callback +can add or remove that fragment.</p> + </li> + <li>Get the {@link android.app.ActionBar} for your activity by calling {@link +android.app.Activity#getActionBar} from your {@link android.app.Activity}, during {@link +android.app.Activity#onCreate onCreate()} (but be sure you do so <em>after</em> you've called +{@link android.app.Activity#setContentView setContentView()}).</li> + <li>Call {@link android.app.ActionBar#setNavigationMode(int) +setNavigationMode(NAVIGATION_MODE_TABS)} to enable tab mode for the {@link +android.app.ActionBar}.</li> + <li>Create each tab for the action bar: + <ol> + <li>Create a new {@link android.app.ActionBar.Tab} by calling {@link +android.app.ActionBar#newTab()} on the {@link android.app.ActionBar}.</li> + <li>Add title text and/or an icon for the tab by calling {@link +android.app.ActionBar.Tab#setText setText()} and/or {@link android.app.ActionBar.Tab#setIcon +setIcon()}. + <p class="note"><strong>Tip:</strong> These methods return the same {@link +android.app.ActionBar.Tab} instance, so you can chain the calls together.</p></li> + <li>Declare the {@link android.app.ActionBar.TabListener} to use for the tab by passing an +instance of your implementation to {@link android.app.ActionBar.Tab#setTabListener +setTabListener()}. + </ol> + </li> + <li>Add each {@link android.app.ActionBar.Tab} to the action bar by calling {@link +android.app.ActionBar#addTab addTab()} on the {@link android.app.ActionBar} and passing the +{@link android.app.ActionBar.Tab}.</li> +</ol> +<p>For example, the following code combines steps 2 - 5 to create two tabs and add them to +the action bar:</p> +<pre> +@Override +protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + // setup action bar for tabs + final ActionBar actionBar = getActionBar(); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + // remove the activity title to make space for tabs + actionBar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); + + // instantiate fragment for the tab + Fragment artistsFragment = new ArtistsFragment(); + // add a new tab and set its title text and tab listener + bar.addTab(bar.newTab().setText(R.string.tab_artists) + .setTabListener(new TabListener(artistsFragment))); + + Fragment albumsFragment = new AlbumsFragment(); + bar.addTab(bar.newTab().setText(R.string.tab_albums) + .setTabListener(new TabListener(albumsFragment))); +} +</pre> + +<p>All the behaviors that occur when a tab is selected must be defined by your {@link +android.app.ActionBar.TabListener} callback methods. When a tab is selected, it receives a call to +{@link android.app.ActionBar.TabListener#onTabSelected onTabSelected()} and that's where you should +add the appropriate fragment to the designated view in your layout, using {@link +android.app.FragmentTransaction#add add()} with the provided {@link +android.app.FragmentTransaction}. Likewise, when a tab is deselected (because another tab becomes +selected), you should remove that fragment from the layout, using {@link +android.app.FragmentTransaction#remove remove()}.</p> + +<p class="note"><strong>Note:</strong> You <strong>do not</strong> need +to call {@link android.app.FragmentTransaction#commit} for these transactions. You also +<strong>cannot</strong> add these fragment transactions to the back stack.</p> + +<p>If your activity is stopped, you should retain the currently selected tab with the saved state so +that when the user returns to your application, you can open the tab. When it's time to save the +state, you can query the currently selected tab with {@link +android.app.ActionBar#getSelectedNavigationItem()}. This returns the index position of the selected +tab.</p> + +<p class="caution"><strong>Caution:</strong> It's important that you save +the state of each fragment as necessary, so when the user switches fragments with the tabs, +then returns to a previous fragment, it appears the way they left. For information about saving +the state of your fragment, see the <a +href="{@docRoot}guide/topics/fragments/index.html">Fragments</a> developer guide.</p> + + diff --git a/docs/html/guide/topics/ui/declaring-layout.jd b/docs/html/guide/topics/ui/declaring-layout.jd index fe641a2..4d71d28 100644 --- a/docs/html/guide/topics/ui/declaring-layout.jd +++ b/docs/html/guide/topics/ui/declaring-layout.jd @@ -191,10 +191,12 @@ layout parameters for the View that are appropriate for the ViewGroup in which i <p>Every ViewGroup class implements a nested class that extends {@link android.view.ViewGroup.LayoutParams}. This subclass contains property types that define the size and position for each child view, as -appropriate for the view group. As you can see in the figure below, the parent +appropriate for the view group. As you can see in figure 1, the parent view group defines layout parameters for each child view (including the child view group).</p> <img src="{@docRoot}images/layoutparams.png" alt="" height="300" align="center"/> +<p class="img-caption"><strong>Figure 1.</strong> Visualization of a view hierarchy with layout +parameters associated with each view.</p> <p>Note that every LayoutParams subclass has its own syntax for setting values. Each child element must define LayoutParams that are appropriate for its parent, diff --git a/docs/html/guide/topics/ui/dialogs.jd b/docs/html/guide/topics/ui/dialogs.jd index 879eb8b..d50e1cb 100644 --- a/docs/html/guide/topics/ui/dialogs.jd +++ b/docs/html/guide/topics/ui/dialogs.jd @@ -22,20 +22,32 @@ parent.link=index.html </li> <li><a href="#CustomDialog">Creating a Custom Dialog</a></li> </ol> - + <h2>Key classes</h2> <ol> <li>{@link android.app.Dialog}</li> + <li>{@link android.app.AlertDialog}</li> + <li>{@link android.app.DialogFragment}</li> + </ol> + + <h2>Related tutorials</h2> + <ol> + <li><a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Hello +DatePicker</a></li> + <li><a href="{@docRoot}resources/tutorials/views/hello-timepicker.html">Hello +TimePicker</a></li> </ol> </div> </div> <p>A dialog is usually a small window that appears in front of the current Activity. -The underlying Activity loses focus and the dialog accepts all user interaction. -Dialogs are normally used -for notifications and short activities that directly relate to the application in progress.</p> +The underlying Activity loses focus and the dialog accepts all user interaction. Dialogs are +normally used for notifications that should interupt the user and to perform short tasks that +directly relate to the application in progress (such as a progress bar or a login prompt).</p> -<p>The Android API supports the following types of {@link android.app.Dialog} objects:</p> +<p>The {@link android.app.Dialog} class is the base class for creating dialogs. However, you +typically should not instantiate a {@link android.app.Dialog} directly. Instead, you should use one +of the following subclasses:</p> <dl> <dt>{@link android.app.AlertDialog}</dt> <dd>A dialog that can manage zero, one, two, or three buttons, and/or a list of diff --git a/docs/html/guide/topics/ui/menus.jd b/docs/html/guide/topics/ui/menus.jd index b4e467c..ce25b9f 100644 --- a/docs/html/guide/topics/ui/menus.jd +++ b/docs/html/guide/topics/ui/menus.jd @@ -36,6 +36,7 @@ parent.link=index.html <h2>See also</h2> <ol> + <li><a href="{@docRoot}guide/topics/ui/actionbar.html">Using the Action Bar</a></li> <li><a href="{@docRoot}guide/topics/resources/menu-resource.html">Menu Resource</a></li> </ol> </div> @@ -48,20 +49,9 @@ for you to provide application menus in your application.</p> <p>Android provides three types of application menus:</p> <dl> <dt><strong>Options Menu</strong></dt> - <dd>The primary menu for an Activity, which appears when the user presses - the device MENU key. Within the Options Menu are two groups: - <dl style="margin-top:1em"> - <dt><em>Icon Menu</em></dt> - <dd>The menu items visible at the bottom of the screen - at the press of the MENU key. It supports a maximum of six menu items. - These are the only menu items that support icons and the only menu items that <em>do not</em> support - checkboxes or radio buttons.</dd> - <dt><em>Expanded Menu</em></dt> - <dd>The vertical list of menu items exposed by the "More" menu item in the Icon Menu. - When the Icon Menu is full, the expanded menu is comprised of the sixth - menu item and the rest.</dd> - </dl> - </dd> + <dd>The primary collection of menu items for an Activity that is associated with the device MENU +key. To provide instant access to select menu items, you can place some items in the <a +href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a>, if available.</dd> <dt><strong>Context Menu</strong></dt> <dd>A floating list of menu items that appears when the user performs a long-press on a View. </dd> @@ -74,7 +64,7 @@ Menu or a context menu. A submenu item cannot support a nested submenu. </dd> <h2 id="xml">Defining Menus</h2> -<p>Instead of instantiating {@link android.view.Menu} objects in your application code, you should +<p>Instead of instantiating a {@link android.view.Menu} in your application code, you should define a menu and all its items in an XML <a href="{@docRoot}guide/topics/resources/menu-resource.html">menu resource</a>, then inflate the menu resource (load it as a programmable object) in your application code. Defining your menus in XML is @@ -104,9 +94,9 @@ href="#groups">Menu groups</a>.</dd> <item android:id="@+id/new_game" android:icon="@drawable/ic_new_game" android:title="@string/new_game" /> - <item android:id="@+id/quit" - android:icon="@drawable/ic_quit" - android:title="@string/quit" /> + <item android:id="@+id/help" + android:icon="@drawable/ic_help" + android:title="@string/help" /> </menu> </pre> @@ -161,36 +151,64 @@ creating an option menu is discussed more in the next section.)</p> </div> -<p>The Options Menu is where you should include basic application functions -and necessary navigation items (for example, a button -to open application settings). The user -can open the Options Menu with the device MENU key. -Figure 1 shows a screenshot of an Options Menu.</p> +<p>The Options Menu is where you should include basic application functions and necessary navigation +items (for example, a button to open the application settings). Items in the Options Menu are +accessible in two distinct ways: in the Action Bar and in the menu revealed by the MENU +key.</p> + +<p>The Action Bar is an optional widget that appears at the top of the activity in place of the +title bar. It can display several menu items that you choose from the Options Menu, but items in +the Action Bar display only an icon (no title text). Users can reveal the other menu items in the +Options Menu with the MENU key.</p> -<p>When opened, the first visible portion of the Options Menu is called the Icon Menu. It -holds the first six menu items. -If you add more than six items to the Options Menu, Android places the sixth item and those after it -into the Expanded Menu, which the user can open with the "More" menu item.</p> +<p>If you include the Action Bar in your activity, the menu items that are not placed in the Action +Bar can appear in two different styles:</p> +<dl> + <dt>Action Bar Menu</dt> + <dd>If the device has an extra-large screen ({@code xlarge}), then all items in the Options Menu +that are not placed in the Action Bar are placed into a drop-down list at the right side of the +Action Bar, with icons and title text. The user can reveal the drop-down list by pressing the +drop-down icon in the Action Bar or the MENU key.</dd> + <dt>Standard Options Menu</dt> + <dd>If the device <em>does not</em> have an extra-large screen, then all items in the Options +Menu that are not placed in the Action Bar are placed into the Standard Options Menu at the bottom +of the activity. The user can reveal the standard Options Menu by pressing the MENU key. + <p>The first visible portion of the Standard Options Menu is called the Icon Menu. +It holds the first six menu items (excluding any added to the Action Bar), with icons and title +text. If there are more than six items, Android adds a "More" item as the sixth menu item and places +the remaining items into the Expanded Menu, which the user can open by selecting "More". The +Expanded Menu displays menu items only by their title text (no icon)</p> + </dd> +</dl> <p>When the user opens the Options Menu for the first time, Android calls your Activity's {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} method. Override this method in your Activity and populate the {@link android.view.Menu} that is passed into the method. Populate the {@link android.view.Menu} by inflating a menu resource as described in <a -href="#Inflating">Inflating a Menu Resource</a>. (You can -also populate the menu in code, using {@link android.view.Menu#add(int,int,int,int) -add()} to add menu items.)</p> +href="#Inflating">Inflating a Menu Resource</a>. For example:</p> -<p>When the user selects a menu item from the Options Menu, the system calls your Activity's +<pre> +@Override +public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.game_menu, menu); + return true; +} +</pre> + +<p>(You can also populate the menu in code, using {@link android.view.Menu#add(int,int,int,int) +add()} to add items to the {@link android.view.Menu}.)</p> + +<p>When the user selects a menu item from the Options Menu (including items selected from the +Action Bar), the system calls your Activity's {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} method. This method passes the {@link android.view.MenuItem} that the user selected. You can identify the menu item by calling {@link android.view.MenuItem#getItemId()}, which returns the unique ID for the menu item (defined by the {@code android:id} attribute in the menu resource or with an integer passed to the {@link android.view.Menu#add(int,int,int,int) add()} method). You can match this ID -against known menu items and perform the appropriate action.</p> - -<p>For example:</p> +against known menu items and perform the appropriate action. For example:</p> <pre> @Override @@ -200,8 +218,8 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.new_game: newGame(); return true; - case R.id.quit: - quit(); + case R.id.help: + showHelp(); return true; default: return super.onOptionsItemSelected(item); @@ -224,8 +242,8 @@ an Activity that implements nothing except the {@link android.app.Activity#onCre onCreateOptionsMenu()} and {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} methods. Then extend this class for each Activity that should share the same Options Menu. This way, you have to manage only one set of code for handling menu -actions and each decendent class inherits the menu behaviors.<br/><br/> -If you want to add menu items to one of your decendent activities, +actions and each descendant class inherits the menu behaviors.<br/><br/> +If you want to add menu items to one of your descendant activities, override {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} in that Activity. Call {@code super.onCreateOptionsMenu(menu)} so the original menu items are created, then add new menu items with {@link @@ -542,7 +560,7 @@ hardware keyboard. Shortcuts cannot be added to items in a Context Menu.</p> <h3 id="intents">Intents for menu items</h3> <p>Sometimes you'll want a menu item to launch an Activity using an Intent (whether it's an -Actvitity in your application or another application). When you know the Intent you want to use and +Activity in your application or another application). When you know the Intent you want to use and have a specific menu item that should initiate the Intent, you can execute the Intent with {@link android.app.Activity#startActivity(Intent) startActivity()} during the appropriate on-item-selected callback method (such as the {@link android.app.Activity#onOptionsItemSelected(MenuItem) diff --git a/docs/html/images/ui/actionbar-item-withtext.png b/docs/html/images/ui/actionbar-item-withtext.png Binary files differnew file mode 100644 index 0000000..98b5f84 --- /dev/null +++ b/docs/html/images/ui/actionbar-item-withtext.png diff --git a/docs/html/images/ui/actionbar-logo.png b/docs/html/images/ui/actionbar-logo.png Binary files differnew file mode 100644 index 0000000..481ed2c --- /dev/null +++ b/docs/html/images/ui/actionbar-logo.png diff --git a/docs/html/images/ui/actionbar.png b/docs/html/images/ui/actionbar.png Binary files differnew file mode 100644 index 0000000..4fc871c --- /dev/null +++ b/docs/html/images/ui/actionbar.png diff --git a/drm/common/Android.mk b/drm/common/Android.mk index 808b2c2..c79a91a 100644 --- a/drm/common/Android.mk +++ b/drm/common/Android.mk @@ -18,6 +18,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ DrmConstraints.cpp \ + DrmMetadata.cpp \ DrmConvertedStatus.cpp \ DrmEngineBase.cpp \ DrmInfo.cpp \ diff --git a/drm/common/DrmEngineBase.cpp b/drm/common/DrmEngineBase.cpp index 10c64ee..9b16c36 100644 --- a/drm/common/DrmEngineBase.cpp +++ b/drm/common/DrmEngineBase.cpp @@ -31,6 +31,10 @@ DrmConstraints* DrmEngineBase::getConstraints( return onGetConstraints(uniqueId, path, action); } +DrmMetadata* DrmEngineBase::getMetadata(int uniqueId, const String8* path) { + return onGetMetadata(uniqueId, path); +} + status_t DrmEngineBase::initialize(int uniqueId) { return onInitialize(uniqueId); } @@ -80,7 +84,7 @@ status_t DrmEngineBase::consumeRights( } status_t DrmEngineBase::setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position) { + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) { return onSetPlaybackStatus(uniqueId, decryptHandle, playbackStatus, position); } @@ -116,7 +120,7 @@ DrmSupportInfo* DrmEngineBase::getSupportInfo(int uniqueId) { } status_t DrmEngineBase::openDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, int fd, int offset, int length) { + int uniqueId, DecryptHandle* decryptHandle, int fd, off64_t offset, off64_t length) { return onOpenDecryptSession(uniqueId, decryptHandle, fd, offset, length); } @@ -146,7 +150,7 @@ status_t DrmEngineBase::finalizeDecryptUnit( } ssize_t DrmEngineBase::pread( - int uniqueId, DecryptHandle* decryptHandle, void* buffer, ssize_t numBytes, off_t offset) { + int uniqueId, DecryptHandle* decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) { return onPread(uniqueId, decryptHandle, buffer, numBytes, offset); } diff --git a/drm/common/DrmMetadata.cpp b/drm/common/DrmMetadata.cpp new file mode 100644 index 0000000..6cc5ec1 --- /dev/null +++ b/drm/common/DrmMetadata.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <drm/DrmMetadata.h> + +using namespace android; + +int DrmMetadata::getCount(void) const { + return mMetadataMap.size(); +} + +status_t DrmMetadata::put(const String8* key, + const char* value) { + if((value != NULL) && (key != NULL)) { + int length = strlen(value); + char* charValue = new char[length + 1]; + + memcpy(charValue, value, length); + charValue[length] = '\0'; + mMetadataMap.add(*key, charValue); + } + return NO_ERROR; +} + +String8 DrmMetadata::get(const String8& key) const { + if (NULL != getValue(&key)) { + return String8(getValue(&key)); + } + else { + return String8(""); + } +} + +const char* DrmMetadata::getValue(const String8* key) const { + if(key != NULL) { + if (NAME_NOT_FOUND != mMetadataMap.indexOfKey(*key)) { + return mMetadataMap.valueFor(*key); + } + else { + return NULL; + } + } else { + return NULL; + } +} + +const char* DrmMetadata::getAsByteArray(const String8* key) const { + return getValue(key); +} + +bool DrmMetadata::KeyIterator::hasNext() { + return mIndex < mDrmMetadata->mMetadataMap.size(); +} + +const String8& DrmMetadata::KeyIterator::next() { + const String8& key = mDrmMetadata->mMetadataMap.keyAt(mIndex); + mIndex++; + return key; +} + +DrmMetadata::KeyIterator DrmMetadata::keyIterator() { + return KeyIterator(this); +} + +DrmMetadata::KeyIterator::KeyIterator(const DrmMetadata::KeyIterator& keyIterator) : + mDrmMetadata(keyIterator.mDrmMetadata), + mIndex(keyIterator.mIndex) { + LOGV("DrmMetadata::KeyIterator::KeyIterator"); +} + +DrmMetadata::KeyIterator& DrmMetadata::KeyIterator::operator=(const DrmMetadata::KeyIterator& keyIterator) { + LOGV("DrmMetadata::KeyIterator::operator="); + mDrmMetadata = keyIterator.mDrmMetadata; + mIndex = keyIterator.mIndex; + return *this; +} + + +DrmMetadata::Iterator DrmMetadata::iterator() { + return Iterator(this); +} + +DrmMetadata::Iterator::Iterator(const DrmMetadata::Iterator& iterator) : + mDrmMetadata(iterator.mDrmMetadata), + mIndex(iterator.mIndex) { + LOGV("DrmMetadata::Iterator::Iterator"); +} + +DrmMetadata::Iterator& DrmMetadata::Iterator::operator=(const DrmMetadata::Iterator& iterator) { + LOGV("DrmMetadata::Iterator::operator="); + mDrmMetadata = iterator.mDrmMetadata; + mIndex = iterator.mIndex; + return *this; +} + +bool DrmMetadata::Iterator::hasNext() { + return mIndex < mDrmMetadata->mMetadataMap.size(); +} + +String8 DrmMetadata::Iterator::next() { + String8 value = String8(mDrmMetadata->mMetadataMap.editValueAt(mIndex)); + mIndex++; + return value; +} diff --git a/drm/common/IDrmManagerService.cpp b/drm/common/IDrmManagerService.cpp index c3d12f0..75edac6 100644 --- a/drm/common/IDrmManagerService.cpp +++ b/drm/common/IDrmManagerService.cpp @@ -24,6 +24,7 @@ #include <drm/DrmInfo.h> #include <drm/DrmConstraints.h> +#include <drm/DrmMetadata.h> #include <drm/DrmRights.h> #include <drm/DrmInfoStatus.h> #include <drm/DrmConvertedStatus.h> @@ -123,6 +124,35 @@ DrmConstraints* BpDrmManagerService::getConstraints( return drmConstraints; } +DrmMetadata* BpDrmManagerService::getMetadata(int uniqueId, const String8* path) { + LOGV("Get Metadata"); + Parcel data, reply; + data.writeInterfaceToken(IDrmManagerService::getInterfaceDescriptor()); + data.writeInt32(uniqueId); + + DrmMetadata* drmMetadata = NULL; + data.writeString8(*path); + remote()->transact(GET_METADATA_FROM_CONTENT, data, &reply); + + if (0 != reply.dataAvail()) { + //Filling Drm Metadata + drmMetadata = new DrmMetadata(); + + const int size = reply.readInt32(); + for (int index = 0; index < size; ++index) { + const String8 key(reply.readString8()); + const int bufferSize = reply.readInt32(); + char* data = NULL; + if (0 < bufferSize) { + data = new char[bufferSize]; + reply.read(data, bufferSize); + } + drmMetadata->put(&key, data); + } + } + return drmMetadata; +} + bool BpDrmManagerService::canHandle(int uniqueId, const String8& path, const String8& mimeType) { LOGV("Can Handle"); Parcel data, reply; @@ -333,7 +363,7 @@ status_t BpDrmManagerService::consumeRights( } status_t BpDrmManagerService::setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position) { + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) { LOGV("setPlaybackStatus"); Parcel data, reply; @@ -429,7 +459,7 @@ DrmConvertedStatus* BpDrmManagerService::convertData( if (0 != reply.dataAvail()) { //Filling DRM Converted Status const int statusCode = reply.readInt32(); - const int offset = reply.readInt32(); + const off64_t offset = reply.readInt64(); DrmBuffer* convertedData = NULL; if (0 != reply.dataAvail()) { @@ -461,7 +491,7 @@ DrmConvertedStatus* BpDrmManagerService::closeConvertSession(int uniqueId, int c if (0 != reply.dataAvail()) { //Filling DRM Converted Status const int statusCode = reply.readInt32(); - const int offset = reply.readInt32(); + const off64_t offset = reply.readInt64(); DrmBuffer* convertedData = NULL; if (0 != reply.dataAvail()) { @@ -515,15 +545,15 @@ status_t BpDrmManagerService::getAllSupportInfo( } DecryptHandle* BpDrmManagerService::openDecryptSession( - int uniqueId, int fd, int offset, int length) { + int uniqueId, int fd, off64_t offset, off64_t length) { LOGV("Entering BpDrmManagerService::openDecryptSession"); Parcel data, reply; data.writeInterfaceToken(IDrmManagerService::getInterfaceDescriptor()); data.writeInt32(uniqueId); data.writeFileDescriptor(fd); - data.writeInt32(offset); - data.writeInt32(length); + data.writeInt64(offset); + data.writeInt64(length); remote()->transact(OPEN_DECRYPT_SESSION, data, &reply); @@ -697,7 +727,7 @@ status_t BpDrmManagerService::finalizeDecryptUnit( ssize_t BpDrmManagerService::pread( int uniqueId, DecryptHandle* decryptHandle, void* buffer, - ssize_t numBytes, off_t offset) { + ssize_t numBytes, off64_t offset) { LOGV("read"); Parcel data, reply; int result; @@ -717,7 +747,7 @@ ssize_t BpDrmManagerService::pread( } data.writeInt32(numBytes); - data.writeInt32(offset); + data.writeInt64(offset); remote()->transact(PREAD, data, &reply); result = reply.readInt32(); @@ -825,6 +855,38 @@ status_t BnDrmManagerService::onTransact( return DRM_NO_ERROR; } + case GET_METADATA_FROM_CONTENT: + { + LOGV("BnDrmManagerService::onTransact :GET_METADATA_FROM_CONTENT"); + CHECK_INTERFACE(IDrmManagerService, data, reply); + + const int uniqueId = data.readInt32(); + const String8 path = data.readString8(); + + DrmMetadata* drmMetadata = getMetadata(uniqueId, &path); + if (NULL != drmMetadata) { + //Filling DRM Metadata contents + reply->writeInt32(drmMetadata->getCount()); + + DrmMetadata::KeyIterator keyIt = drmMetadata->keyIterator(); + while (keyIt.hasNext()) { + const String8 key = keyIt.next(); + reply->writeString8(key); + const char* value = drmMetadata->getAsByteArray(&key); + int bufferSize = 0; + if (NULL != value) { + bufferSize = strlen(value); + reply->writeInt32(bufferSize + 1); + reply->write(value, bufferSize + 1); + } else { + reply->writeInt32(0); + } + } + } + delete drmMetadata; drmMetadata = NULL; + return NO_ERROR; + } + case CAN_HANDLE: { LOGV("BnDrmManagerService::onTransact :CAN_HANDLE"); @@ -1121,7 +1183,7 @@ status_t BnDrmManagerService::onTransact( if (NULL != drmConvertedStatus) { //Filling Drm Converted Ststus reply->writeInt32(drmConvertedStatus->statusCode); - reply->writeInt32(drmConvertedStatus->offset); + reply->writeInt64(drmConvertedStatus->offset); if (NULL != drmConvertedStatus->convertedData) { const DrmBuffer* convertedData = drmConvertedStatus->convertedData; @@ -1150,7 +1212,7 @@ status_t BnDrmManagerService::onTransact( if (NULL != drmConvertedStatus) { //Filling Drm Converted Ststus reply->writeInt32(drmConvertedStatus->statusCode); - reply->writeInt32(drmConvertedStatus->offset); + reply->writeInt64(drmConvertedStatus->offset); if (NULL != drmConvertedStatus->convertedData) { const DrmBuffer* convertedData = drmConvertedStatus->convertedData; @@ -1210,7 +1272,7 @@ status_t BnDrmManagerService::onTransact( const int fd = data.readFileDescriptor(); DecryptHandle* handle - = openDecryptSession(uniqueId, fd, data.readInt32(), data.readInt32()); + = openDecryptSession(uniqueId, fd, data.readInt64(), data.readInt64()); if (NULL != handle) { reply->writeInt32(handle->decryptId); @@ -1415,7 +1477,7 @@ status_t BnDrmManagerService::onTransact( const int numBytes = data.readInt32(); char* buffer = new char[numBytes]; - const off_t offset = data.readInt32(); + const off64_t offset = data.readInt64(); ssize_t result = pread(uniqueId, &handle, buffer, numBytes, offset); reply->writeInt32(result); diff --git a/drm/common/ReadWriteUtils.cpp b/drm/common/ReadWriteUtils.cpp index 7ec4fa2..c16214e 100644 --- a/drm/common/ReadWriteUtils.cpp +++ b/drm/common/ReadWriteUtils.cpp @@ -42,7 +42,7 @@ String8 ReadWriteUtils::readBytes(const String8& filePath) { struct stat sb; if (fstat(fd, &sb) == 0 && sb.st_size > 0) { - int length = sb.st_size; + off64_t length = sb.st_size; char* bytes = new char[length]; if (length == read(fd, (void*) bytes, length)) { string.append(bytes, length); @@ -57,7 +57,7 @@ String8 ReadWriteUtils::readBytes(const String8& filePath) { int ReadWriteUtils::readBytes(const String8& filePath, char** buffer) { FILE* file = NULL; file = fopen(filePath.string(), "r"); - int length = 0; + off64_t length = 0; if (NULL != file) { int fd = fileno(file); diff --git a/drm/drmserver/DrmManager.cpp b/drm/drmserver/DrmManager.cpp index 35e62f3..9a6f787 100644 --- a/drm/drmserver/DrmManager.cpp +++ b/drm/drmserver/DrmManager.cpp @@ -23,6 +23,7 @@ #include <drm/DrmInfoEvent.h> #include <drm/DrmRights.h> #include <drm/DrmConstraints.h> +#include <drm/DrmMetadata.h> #include <drm/DrmInfoStatus.h> #include <drm/DrmInfoRequest.h> #include <drm/DrmSupportInfo.h> @@ -148,6 +149,15 @@ DrmConstraints* DrmManager::getConstraints(int uniqueId, const String8* path, co return NULL; } +DrmMetadata* DrmManager::getMetadata(int uniqueId, const String8* path) { + const String8 plugInId = getSupportedPlugInIdFromPath(uniqueId, *path); + if (EMPTY_STRING != plugInId) { + IDrmEngine& rDrmEngine = mPlugInManager.getPlugIn(plugInId); + return rDrmEngine.getMetadata(uniqueId, path); + } + return NULL; +} + status_t DrmManager::installDrmEngine(int uniqueId, const String8& absolutePath) { mPlugInManager.loadPlugIn(absolutePath); @@ -258,7 +268,7 @@ status_t DrmManager::consumeRights( } status_t DrmManager::setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position) { + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) { status_t result = DRM_ERROR_UNKNOWN; if (mDecryptSessionMap.indexOfKey(decryptHandle->decryptId) != NAME_NOT_FOUND) { IDrmEngine* drmEngine = mDecryptSessionMap.valueFor(decryptHandle->decryptId); @@ -370,7 +380,7 @@ status_t DrmManager::getAllSupportInfo( return DRM_NO_ERROR; } -DecryptHandle* DrmManager::openDecryptSession(int uniqueId, int fd, int offset, int length) { +DecryptHandle* DrmManager::openDecryptSession(int uniqueId, int fd, off64_t offset, off64_t length) { Mutex::Autolock _l(mDecryptLock); status_t result = DRM_ERROR_CANNOT_HANDLE; Vector<String8> plugInIdList = mPlugInManager.getPlugInIdList(); @@ -470,7 +480,7 @@ status_t DrmManager::finalizeDecryptUnit( } ssize_t DrmManager::pread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset) { + void* buffer, ssize_t numBytes, off64_t offset) { ssize_t result = DECRYPT_FILE_ERROR; if (mDecryptSessionMap.indexOfKey(decryptHandle->decryptId) != NAME_NOT_FOUND) { diff --git a/drm/drmserver/DrmManagerService.cpp b/drm/drmserver/DrmManagerService.cpp index 8cf510d..0901a44 100644 --- a/drm/drmserver/DrmManagerService.cpp +++ b/drm/drmserver/DrmManagerService.cpp @@ -18,18 +18,50 @@ #define LOG_TAG "DrmManagerService(Native)" #include <utils/Log.h> +#include <private/android_filesystem_config.h> + #include <errno.h> #include <utils/threads.h> #include <binder/IServiceManager.h> +#include <binder/IPCThreadState.h> #include <sys/stat.h> #include "DrmManagerService.h" #include "DrmManager.h" using namespace android; +static Vector<uid_t> trustedUids; + +static bool isProtectedCallAllowed() { + // TODO + // Following implementation is just for reference. + // Each OEM manufacturer should implement/replace with their own solutions. + bool result = false; + + IPCThreadState* ipcState = IPCThreadState::self(); + uid_t uid = ipcState->getCallingUid(); + + for (unsigned int i = 0; i < trustedUids.size(); ++i) { + if (trustedUids[i] == uid) { + result = true; + break; + } + } + return result; +} + void DrmManagerService::instantiate() { LOGV("instantiate"); defaultServiceManager()->addService(String16("drm.drmManager"), new DrmManagerService()); + + if (0 >= trustedUids.size()) { + // TODO + // Following implementation is just for reference. + // Each OEM manufacturer should implement/replace with their own solutions. + + // Add trusted uids here + trustedUids.push(AID_MEDIA); + } } DrmManagerService::DrmManagerService() : @@ -79,6 +111,11 @@ DrmConstraints* DrmManagerService::getConstraints( return mDrmManager->getConstraints(uniqueId, path, action); } +DrmMetadata* DrmManagerService::getMetadata(int uniqueId, const String8* path) { + LOGV("Entering getMetadata from content"); + return mDrmManager->getMetadata(uniqueId, path); +} + bool DrmManagerService::canHandle(int uniqueId, const String8& path, const String8& mimeType) { LOGV("Entering canHandle"); return mDrmManager->canHandle(uniqueId, path, mimeType); @@ -125,7 +162,7 @@ status_t DrmManagerService::consumeRights( } status_t DrmManagerService::setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position) { + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) { LOGV("Entering setPlaybackStatus"); return mDrmManager->setPlaybackStatus(uniqueId, decryptHandle, playbackStatus, position); } @@ -170,15 +207,23 @@ status_t DrmManagerService::getAllSupportInfo( } DecryptHandle* DrmManagerService::openDecryptSession( - int uniqueId, int fd, int offset, int length) { + int uniqueId, int fd, off64_t offset, off64_t length) { LOGV("Entering DrmManagerService::openDecryptSession"); - return mDrmManager->openDecryptSession(uniqueId, fd, offset, length); + if (isProtectedCallAllowed()) { + return mDrmManager->openDecryptSession(uniqueId, fd, offset, length); + } + + return NULL; } DecryptHandle* DrmManagerService::openDecryptSession( int uniqueId, const char* uri) { LOGV("Entering DrmManagerService::openDecryptSession with uri"); - return mDrmManager->openDecryptSession(uniqueId, uri); + if (isProtectedCallAllowed()) { + return mDrmManager->openDecryptSession(uniqueId, uri); + } + + return NULL; } status_t DrmManagerService::closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle) { @@ -206,7 +251,7 @@ status_t DrmManagerService::finalizeDecryptUnit( } ssize_t DrmManagerService::pread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset) { + void* buffer, ssize_t numBytes, off64_t offset) { LOGV("Entering pread"); return mDrmManager->pread(uniqueId, decryptHandle, buffer, numBytes, offset); } diff --git a/drm/java/android/drm/DrmErrorEvent.java b/drm/java/android/drm/DrmErrorEvent.java index 9294884..20fd8aa 100644 --- a/drm/java/android/drm/DrmErrorEvent.java +++ b/drm/java/android/drm/DrmErrorEvent.java @@ -53,11 +53,6 @@ public class DrmErrorEvent extends DrmEvent { * associated with all DRM schemes. */ public static final int TYPE_REMOVE_ALL_RIGHTS_FAILED = 2007; - /** - * TYPE_DRM_INFO_ACQUISITION_FAILED, when failed to get the required information to - * communicate with the service. - */ - public static final int TYPE_DRM_INFO_ACQUISITION_FAILED = 2008; /** * constructor to create DrmErrorEvent object with given parameters diff --git a/drm/java/android/drm/DrmEvent.java b/drm/java/android/drm/DrmEvent.java index 583337f..f7bc5cd 100644 --- a/drm/java/android/drm/DrmEvent.java +++ b/drm/java/android/drm/DrmEvent.java @@ -31,14 +31,8 @@ public class DrmEvent { * Constant field signifies that given information is processed successfully */ public static final int TYPE_DRM_INFO_PROCESSED = 1002; - /** - * Constant field signifies that the required information to communicate with - * the service is acquired sucessfully - */ - public static final int TYPE_DRM_INFO_ACQUIRED = 1003; public static final String DRM_INFO_STATUS_OBJECT = "drm_info_status_object"; - public static final String DRM_INFO_OBJECT = "drm_info_object"; private final int mUniqueId; private final int mType; diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java index 5044d36..2f54b33 100644 --- a/drm/java/android/drm/DrmManagerClient.java +++ b/drm/java/android/drm/DrmManagerClient.java @@ -102,8 +102,7 @@ public class DrmManagerClient { } private static final int ACTION_REMOVE_ALL_RIGHTS = 1001; - private static final int ACTION_ACQUIRE_DRM_INFO = 1002; - private static final int ACTION_PROCESS_DRM_INFO = 1003; + private static final int ACTION_PROCESS_DRM_INFO = 1002; private int mUniqueId; private int mNativeContext; @@ -126,18 +125,6 @@ public class DrmManagerClient { HashMap<String, Object> attributes = new HashMap<String, Object>(); switch(msg.what) { - case ACTION_ACQUIRE_DRM_INFO: { - final DrmInfoRequest request = (DrmInfoRequest) msg.obj; - DrmInfo drmInfo = _acquireDrmInfo(mUniqueId, request); - if (null != drmInfo) { - attributes.put(DrmEvent.DRM_INFO_OBJECT, drmInfo); - event = new DrmEvent(mUniqueId, DrmEvent.TYPE_DRM_INFO_ACQUIRED, null); - } else { - error = new DrmErrorEvent(mUniqueId, - DrmErrorEvent.TYPE_DRM_INFO_ACQUISITION_FAILED, null); - } - break; - } case ACTION_PROCESS_DRM_INFO: { final DrmInfo drmInfo = (DrmInfo) msg.obj; DrmInfoStatus status = _processDrmInfo(mUniqueId, drmInfo); @@ -243,19 +230,14 @@ public class DrmManagerClient { */ public DrmManagerClient(Context context) { mContext = context; - Looper looper; - if (null != (looper = Looper.myLooper())) { - mInfoHandler = new InfoHandler(looper); - } else if (null != (looper = Looper.getMainLooper())) { - mInfoHandler = new InfoHandler(looper); - } else { - mInfoHandler = null; - } + HandlerThread infoThread = new HandlerThread("DrmManagerClient.InfoHandler"); + infoThread.start(); + mInfoHandler = new InfoHandler(infoThread.getLooper()); - HandlerThread thread = new HandlerThread("DrmManagerClient.EventHandler"); - thread.start(); - mEventHandler = new EventHandler(thread.getLooper()); + HandlerThread eventThread = new HandlerThread("DrmManagerClient.EventHandler"); + eventThread.start(); + mEventHandler = new EventHandler(eventThread.getLooper()); // save the unique id mUniqueId = hashCode(); @@ -335,10 +317,24 @@ public class DrmManagerClient { return _getConstraints(mUniqueId, path, action); } + /** + * Get metadata information from DRM content + * + * @param path Content path from where DRM metadata would be retrieved. + * @return ContentValues instance in which metadata key-value pairs are embedded + * or null in case of failure + */ + public ContentValues getMetadata(String path) { + if (null == path || path.equals("")) { + throw new IllegalArgumentException("Given path is invalid/null"); + } + return _getMetadata(mUniqueId, path); + } + /** * Get constraints information evaluated from DRM content * - * @param uri The Content URI of the data + * @param uri Content URI from where DRM constraints would be retrieved. * @param action Actions defined in {@link DrmStore.Action} * @return ContentValues instance in which constraints key-value pairs are embedded * or null in case of failure @@ -350,6 +346,20 @@ public class DrmManagerClient { return getConstraints(convertUriToPath(uri), action); } + /** + * Get metadata information from DRM content + * + * @param uri Content URI from where DRM metadata would be retrieved. + * @return ContentValues instance in which metadata key-value pairs are embedded + * or null in case of failure + */ + public ContentValues getMetadata(Uri uri) { + if (null == uri || Uri.EMPTY == uri) { + throw new IllegalArgumentException("Uri should be non null"); + } + return getMetadata(convertUriToPath(uri)); + } + /** * Save DRM rights to specified rights path * and make association with content path. @@ -408,7 +418,7 @@ public class DrmManagerClient { /** * Check whether the given mimetype or uri can be handled. * - * @param uri The content URI of the data + * @param uri Content URI of the data to be handled. * @param mimeType Mimetype of the object to be handled * @return * true - if the given mimeType or path can be handled @@ -445,20 +455,31 @@ public class DrmManagerClient { * Retrieves necessary information for register, unregister or rights acquisition. * * @param drmInfoRequest Request information to retrieve drmInfo - * @return - * ERROR_NONE for success - * ERROR_UNKNOWN for failure + * @return DrmInfo Instance as a result of processing given input */ - public int acquireDrmInfo(DrmInfoRequest drmInfoRequest) { + public DrmInfo acquireDrmInfo(DrmInfoRequest drmInfoRequest) { if (null == drmInfoRequest || !drmInfoRequest.isValid()) { throw new IllegalArgumentException("Given drmInfoRequest is invalid/null"); } - int result = ERROR_UNKNOWN; - if (null != mEventHandler) { - Message msg = mEventHandler.obtainMessage(ACTION_ACQUIRE_DRM_INFO, drmInfoRequest); - result = (mEventHandler.sendMessage(msg)) ? ERROR_NONE : result; - } - return result; + return _acquireDrmInfo(mUniqueId, drmInfoRequest); + } + + /** + * Executes given DrmInfoRequest and returns the rights information asynchronously. + * This is a utility API which consists of {@link #acquireDrmInfo(DrmInfoRequest)} + * and {@link #processDrmInfo(DrmInfo)}. + * It can be used if selected DRM agent can work with this combined sequences. + * In case of some DRM schemes, such as OMA DRM, application needs to invoke + * {@link #acquireDrmInfo(DrmInfoRequest)} and {@link #processDrmInfo(DrmInfo)}, separately. + * + * @param drmInfoRequest Request information to retrieve drmInfo + * @return + * ERROR_NONE for success + * ERROR_UNKNOWN for failure + */ + public int acquireRights(DrmInfoRequest drmInfoRequest) { + DrmInfo drmInfo = acquireDrmInfo(drmInfoRequest); + return processDrmInfo(drmInfo); } /** @@ -750,6 +771,8 @@ public class DrmManagerClient { private native ContentValues _getConstraints(int uniqueId, String path, int usage); + private native ContentValues _getMetadata(int uniqueId, String path); + private native boolean _canHandle(int uniqueId, String path, String mimeType); private native DrmInfoStatus _processDrmInfo(int uniqueId, DrmInfo drmInfo); diff --git a/drm/jni/android_drm_DrmManagerClient.cpp b/drm/jni/android_drm_DrmManagerClient.cpp index e5e4547..e131839 100644 --- a/drm/jni/android_drm_DrmManagerClient.cpp +++ b/drm/jni/android_drm_DrmManagerClient.cpp @@ -29,6 +29,7 @@ #include <drm/DrmInfoRequest.h> #include <drm/DrmSupportInfo.h> #include <drm/DrmConstraints.h> +#include <drm/DrmMetadata.h> #include <drm/DrmConvertedStatus.h> #include <drm/drm_framework_common.h> @@ -298,6 +299,43 @@ static jobject android_drm_DrmManagerClient_getConstraintsFromContent( return constraints; } +static jobject android_drm_DrmManagerClient_getMetadataFromContent( + JNIEnv* env, jobject thiz, jint uniqueId, jstring jpath) { + LOGV("GetMetadata - Enter"); + const String8 pathString = Utility::getStringValue(env, jpath); + DrmMetadata* pMetadata = + getDrmManagerClientImpl(env, thiz)->getMetadata(uniqueId, &pathString); + + jobject metadata = NULL; + + jclass localRef = NULL; + localRef = env->FindClass("android/content/ContentValues"); + if (NULL != localRef && NULL != pMetadata) { + // Get the constructor id + jmethodID constructorId = NULL; + constructorId = env->GetMethodID(localRef, "<init>", "()V"); + if (NULL != constructorId) { + // create the java DrmMetadata object + metadata = env->NewObject(localRef, constructorId); + if (NULL != metadata) { + DrmMetadata::KeyIterator keyIt = pMetadata->keyIterator(); + while (keyIt.hasNext()) { + String8 key = keyIt.next(); + // insert the entry<constraintKey, constraintValue> + // to newly created java object + String8 value = pMetadata->get(key); + env->CallVoidMethod(metadata, env->GetMethodID(localRef, "put", + "(Ljava/lang/String;Ljava/lang/String;)V"), + env->NewStringUTF(key.string()), env->NewStringUTF(value.string())); + } + } + } + } + delete pMetadata; pMetadata = NULL; + LOGV("GetMetadata - Exit"); + return metadata; +} + static jobjectArray android_drm_DrmManagerClient_getAllSupportInfo( JNIEnv* env, jobject thiz, jint uniqueId) { LOGV("GetAllSupportInfo - Enter"); @@ -682,6 +720,9 @@ static JNINativeMethod nativeMethods[] = { {"_getConstraints", "(ILjava/lang/String;I)Landroid/content/ContentValues;", (void*)android_drm_DrmManagerClient_getConstraintsFromContent}, + {"_getMetadata", "(ILjava/lang/String;)Landroid/content/ContentValues;", + (void*)android_drm_DrmManagerClient_getMetadataFromContent}, + {"_getAllSupportInfo", "(I)[Landroid/drm/DrmSupportInfo;", (void*)android_drm_DrmManagerClient_getAllSupportInfo}, diff --git a/drm/libdrmframework/DrmManagerClient.cpp b/drm/libdrmframework/DrmManagerClient.cpp index f0439eb..578e135 100644 --- a/drm/libdrmframework/DrmManagerClient.cpp +++ b/drm/libdrmframework/DrmManagerClient.cpp @@ -43,6 +43,10 @@ DrmConstraints* DrmManagerClient::getConstraints(const String8* path, const int return mDrmManagerClientImpl->getConstraints(mUniqueId, path, action); } +DrmMetadata* DrmManagerClient::getMetadata(const String8* path) { + return mDrmManagerClientImpl->getMetadata(mUniqueId, path); +} + bool DrmManagerClient::canHandle(const String8& path, const String8& mimeType) { return mDrmManagerClientImpl->canHandle(mUniqueId, path, mimeType); } @@ -78,7 +82,7 @@ status_t DrmManagerClient::consumeRights(DecryptHandle* decryptHandle, int actio } status_t DrmManagerClient::setPlaybackStatus( - DecryptHandle* decryptHandle, int playbackStatus, int position) { + DecryptHandle* decryptHandle, int playbackStatus, int64_t position) { return mDrmManagerClientImpl ->setPlaybackStatus(mUniqueId, decryptHandle, playbackStatus, position); } @@ -112,7 +116,7 @@ status_t DrmManagerClient::getAllSupportInfo(int* length, DrmSupportInfo** drmSu return mDrmManagerClientImpl->getAllSupportInfo(mUniqueId, length, drmSupportInfoArray); } -DecryptHandle* DrmManagerClient::openDecryptSession(int fd, int offset, int length) { +DecryptHandle* DrmManagerClient::openDecryptSession(int fd, off64_t offset, off64_t length) { return mDrmManagerClientImpl->openDecryptSession(mUniqueId, fd, offset, length); } @@ -145,7 +149,7 @@ status_t DrmManagerClient::finalizeDecryptUnit(DecryptHandle* decryptHandle, int } ssize_t DrmManagerClient::pread( - DecryptHandle* decryptHandle, void* buffer, ssize_t numBytes, off_t offset) { + DecryptHandle* decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) { Mutex::Autolock _l(mDecryptLock); return mDrmManagerClientImpl->pread(mUniqueId, decryptHandle, buffer, numBytes, offset); } diff --git a/drm/libdrmframework/DrmManagerClientImpl.cpp b/drm/libdrmframework/DrmManagerClientImpl.cpp index b3ae9a7..f39131d 100644 --- a/drm/libdrmframework/DrmManagerClientImpl.cpp +++ b/drm/libdrmframework/DrmManagerClientImpl.cpp @@ -101,6 +101,14 @@ DrmConstraints* DrmManagerClientImpl::getConstraints( return drmConstraints; } +DrmMetadata* DrmManagerClientImpl::getMetadata(int uniqueId, const String8* path) { + DrmMetadata *drmMetadata = NULL; + if ((NULL != path) && (EMPTY_STRING != *path)) { + drmMetadata = getDrmManagerService()->getMetadata(uniqueId, path); + } + return drmMetadata; +} + bool DrmManagerClientImpl::canHandle(int uniqueId, const String8& path, const String8& mimeType) { bool retCode = false; if ((EMPTY_STRING != path) || (EMPTY_STRING != mimeType)) { @@ -170,7 +178,7 @@ status_t DrmManagerClientImpl::consumeRights( } status_t DrmManagerClientImpl::setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position) { + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) { status_t status = DRM_ERROR_UNKNOWN; if (NULL != decryptHandle) { status = getDrmManagerService()->setPlaybackStatus( @@ -231,7 +239,7 @@ status_t DrmManagerClientImpl::getAllSupportInfo( } DecryptHandle* DrmManagerClientImpl::openDecryptSession( - int uniqueId, int fd, int offset, int length) { + int uniqueId, int fd, off64_t offset, off64_t length) { return getDrmManagerService()->openDecryptSession(uniqueId, fd, offset, length); } @@ -283,7 +291,7 @@ status_t DrmManagerClientImpl::finalizeDecryptUnit( } ssize_t DrmManagerClientImpl::pread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset) { + void* buffer, ssize_t numBytes, off64_t offset) { ssize_t retCode = INVALID_VALUE; if ((NULL != decryptHandle) && (NULL != buffer) && (0 < numBytes)) { retCode = getDrmManagerService()->pread(uniqueId, decryptHandle, buffer, numBytes, offset); diff --git a/drm/libdrmframework/include/DrmManager.h b/drm/libdrmframework/include/DrmManager.h index d782f5b..e05366d 100644 --- a/drm/libdrmframework/include/DrmManager.h +++ b/drm/libdrmframework/include/DrmManager.h @@ -32,6 +32,7 @@ class DrmUnregistrationInfo; class DrmRightsAcquisitionInfo; class DrmContentIds; class DrmConstraints; +class DrmMetadata; class DrmRights; class DrmInfo; class DrmInfoStatus; @@ -74,6 +75,8 @@ public: DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); + DrmMetadata* getMetadata(int uniqueId, const String8* path); + bool canHandle(int uniqueId, const String8& path, const String8& mimeType); DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); @@ -92,7 +95,7 @@ public: status_t consumeRights(int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve); status_t setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position); + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position); bool validateAction( int uniqueId, const String8& path, int action, const ActionDescription& description); @@ -109,7 +112,7 @@ public: status_t getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); - DecryptHandle* openDecryptSession(int uniqueId, int fd, int offset, int length); + DecryptHandle* openDecryptSession(int uniqueId, int fd, off64_t offset, off64_t length); DecryptHandle* openDecryptSession(int uniqueId, const char* uri); @@ -124,7 +127,7 @@ public: status_t finalizeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId); ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset); + void* buffer, ssize_t numBytes, off64_t offset); void onInfo(const DrmInfoEvent& event); diff --git a/drm/libdrmframework/include/DrmManagerClientImpl.h b/drm/libdrmframework/include/DrmManagerClientImpl.h index 1c6be46..0a7fcd1 100644 --- a/drm/libdrmframework/include/DrmManagerClientImpl.h +++ b/drm/libdrmframework/include/DrmManagerClientImpl.h @@ -86,6 +86,18 @@ public: DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); /** + * Get metadata information associated with input content. + * + * @param[in] uniqueId Unique identifier for a session + * @param[in] path Path of the protected content + * @return DrmMetadata + * key-value pairs of metadata are embedded in it + * @note + * In case of error, return NULL + */ + DrmMetadata* getMetadata(int uniqueId, const String8* path); + + /** * Check whether the given mimetype or path can be handled * * @param[in] uniqueId Unique identifier for a session @@ -191,7 +203,7 @@ public: * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ status_t setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position); + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position); /** * Validates whether an action on the DRM content is allowed or not. @@ -291,7 +303,7 @@ public: * @return * Handle for the decryption session */ - DecryptHandle* openDecryptSession(int uniqueId, int fd, int offset, int length); + DecryptHandle* openDecryptSession(int uniqueId, int fd, off64_t offset, off64_t length); /** * Open the decrypt session to decrypt the given protected content @@ -369,7 +381,7 @@ public: * @return Number of bytes read. Returns -1 for Failure. */ ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset); + void* buffer, ssize_t numBytes, off64_t offset); /** * Notify the event to the registered listener diff --git a/drm/libdrmframework/include/DrmManagerService.h b/drm/libdrmframework/include/DrmManagerService.h index 4a3aeae..d0a0db7 100644 --- a/drm/libdrmframework/include/DrmManagerService.h +++ b/drm/libdrmframework/include/DrmManagerService.h @@ -61,6 +61,8 @@ public: DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); + DrmMetadata* getMetadata(int uniqueId, const String8* path); + bool canHandle(int uniqueId, const String8& path, const String8& mimeType); DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); @@ -79,7 +81,7 @@ public: status_t consumeRights(int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve); status_t setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position); + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position); bool validateAction(int uniqueId, const String8& path, int action, const ActionDescription& description); @@ -96,7 +98,7 @@ public: status_t getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); - DecryptHandle* openDecryptSession(int uniqueId, int fd, int offset, int length); + DecryptHandle* openDecryptSession(int uniqueId, int fd, off64_t offset, off64_t length); DecryptHandle* openDecryptSession(int uniqueId, const char* uri); @@ -111,7 +113,7 @@ public: status_t finalizeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId); ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset); + void* buffer, ssize_t numBytes, off64_t offset); private: DrmManager* mDrmManager; diff --git a/drm/libdrmframework/include/IDrmManagerService.h b/drm/libdrmframework/include/IDrmManagerService.h index 1275488..2424ea5 100644 --- a/drm/libdrmframework/include/IDrmManagerService.h +++ b/drm/libdrmframework/include/IDrmManagerService.h @@ -27,6 +27,7 @@ namespace android { class DrmContentIds; class DrmConstraints; +class DrmMetadata; class DrmRights; class DrmInfo; class DrmInfoStatus; @@ -51,6 +52,7 @@ public: SET_DRM_SERVICE_LISTENER, INSTALL_DRM_ENGINE, GET_CONSTRAINTS_FROM_CONTENT, + GET_METADATA_FROM_CONTENT, CAN_HANDLE, PROCESS_DRM_INFO, ACQUIRE_DRM_INFO, @@ -96,6 +98,8 @@ public: virtual DrmConstraints* getConstraints( int uniqueId, const String8* path, const int action) = 0; + virtual DrmMetadata* getMetadata(int uniqueId, const String8* path) = 0; + virtual bool canHandle(int uniqueId, const String8& path, const String8& mimeType) = 0; virtual DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo) = 0; @@ -116,7 +120,7 @@ public: int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve) = 0; virtual status_t setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position) = 0; + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) = 0; virtual bool validateAction( int uniqueId, const String8& path, @@ -136,7 +140,7 @@ public: virtual status_t getAllSupportInfo( int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray) = 0; - virtual DecryptHandle* openDecryptSession(int uniqueId, int fd, int offset, int length) = 0; + virtual DecryptHandle* openDecryptSession(int uniqueId, int fd, off64_t offset, off64_t length) = 0; virtual DecryptHandle* openDecryptSession(int uniqueId, const char* uri) = 0; @@ -152,7 +156,7 @@ public: int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId) = 0; virtual ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes,off_t offset) = 0; + void* buffer, ssize_t numBytes,off64_t offset) = 0; }; /** @@ -179,6 +183,8 @@ public: virtual DrmConstraints* getConstraints(int uniqueId, const String8* path, const int action); + virtual DrmMetadata* getMetadata(int uniqueId, const String8* path); + virtual bool canHandle(int uniqueId, const String8& path, const String8& mimeType); virtual DrmInfoStatus* processDrmInfo(int uniqueId, const DrmInfo* drmInfo); @@ -198,7 +204,7 @@ public: int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve); virtual status_t setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position); + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position); virtual bool validateAction( int uniqueId, const String8& path, int action, const ActionDescription& description); @@ -217,7 +223,7 @@ public: virtual status_t getAllSupportInfo( int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); - virtual DecryptHandle* openDecryptSession(int uniqueId, int fd, int offset, int length); + virtual DecryptHandle* openDecryptSession(int uniqueId, int fd, off64_t offset, off64_t length); virtual DecryptHandle* openDecryptSession(int uniqueId, const char* uri); @@ -233,7 +239,7 @@ public: int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId); virtual ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset); + void* buffer, ssize_t numBytes, off64_t offset); }; /** diff --git a/drm/libdrmframework/plugins/common/include/DrmEngineBase.h b/drm/libdrmframework/plugins/common/include/DrmEngineBase.h index 5851af5..b61e3d3 100644 --- a/drm/libdrmframework/plugins/common/include/DrmEngineBase.h +++ b/drm/libdrmframework/plugins/common/include/DrmEngineBase.h @@ -36,6 +36,8 @@ public: public: DrmConstraints* getConstraints(int uniqueId, const String8* path, int action); + DrmMetadata* getMetadata(int uniqueId, const String8* path); + status_t initialize(int uniqueId); status_t setOnInfoListener(int uniqueId, const IDrmEngine::OnInfoListener* infoListener); @@ -60,7 +62,7 @@ public: status_t consumeRights(int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve); status_t setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position); + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position); bool validateAction( int uniqueId, const String8& path, int action, const ActionDescription& description); @@ -78,7 +80,7 @@ public: DrmSupportInfo* getSupportInfo(int uniqueId); status_t openDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, int fd, int offset, int length); + int uniqueId, DecryptHandle* decryptHandle, int fd, off64_t offset, off64_t length); status_t openDecryptSession( int uniqueId, DecryptHandle* decryptHandle, const char* uri); @@ -94,7 +96,7 @@ public: status_t finalizeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId); ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset); + void* buffer, ssize_t numBytes, off64_t offset); protected: ///////////////////////////////////////////////////// @@ -117,6 +119,18 @@ protected: int uniqueId, const String8* path, int action) = 0; /** + * Get metadata information associated with input content + * + * @param[in] uniqueId Unique identifier for a session + * @param[in] path Path of the protected content + * @return DrmMetadata + * key-value pairs of metadata + * @note + * In case of error, return NULL + */ + virtual DrmMetadata* onGetMetadata(int uniqueId, const String8* path) = 0; + + /** * Initialize plug-in * * @param[in] uniqueId Unique identifier for a session @@ -254,7 +268,7 @@ protected: * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ virtual status_t onSetPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position) = 0; + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) = 0; /** * Validates whether an action on the DRM content is allowed or not. @@ -355,7 +369,7 @@ protected: * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ virtual status_t onOpenDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, int fd, int offset, int length) = 0; + int uniqueId, DecryptHandle* decryptHandle, int fd, off64_t offset, off64_t length) = 0; /** * Open the decrypt session to decrypt the given protected content @@ -436,7 +450,7 @@ protected: * @return Number of bytes read. Returns -1 for Failure. */ virtual ssize_t onPread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset) = 0; + void* buffer, ssize_t numBytes, off64_t offset) = 0; }; }; diff --git a/drm/libdrmframework/plugins/common/include/IDrmEngine.h b/drm/libdrmframework/plugins/common/include/IDrmEngine.h index cc03ef2..d05c24f 100644 --- a/drm/libdrmframework/plugins/common/include/IDrmEngine.h +++ b/drm/libdrmframework/plugins/common/include/IDrmEngine.h @@ -23,6 +23,7 @@ namespace android { class DrmContentIds; class DrmConstraints; +class DrmMetadata; class DrmRights; class DrmInfo; class DrmInfoStatus; @@ -105,6 +106,18 @@ public: int uniqueId, const String8* path, int action) = 0; /** + * Get metadata information associated with input content + * + * @param[in] uniqueId Unique identifier for a session + * @param[in] path Path of the protected content + * @return DrmMetadata + * key-value pairs of metadata + * @note + * In case of error, return NULL + */ + virtual DrmMetadata* getMetadata(int uniqueId, const String8* path) = 0; + + /** * Get whether the given content can be handled by this plugin or not * * @param[in] uniqueId Unique identifier for a session @@ -211,7 +224,7 @@ public: * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ virtual status_t setPlaybackStatus(int uniqueId, DecryptHandle* decryptHandle, - int playbackStatus, int position) = 0; + int playbackStatus, int64_t position) = 0; /** * Validates whether an action on the DRM content is allowed or not. @@ -312,7 +325,7 @@ public: * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ virtual status_t openDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, int fd, int offset, int length) = 0; + int uniqueId, DecryptHandle* decryptHandle, int fd, off64_t offset, off64_t length) = 0; /** * Open the decrypt session to decrypt the given protected content @@ -393,7 +406,7 @@ public: * @return Number of bytes read. Returns -1 for Failure. */ virtual ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset) = 0; + void* buffer, ssize_t numBytes, off64_t offset) = 0; }; }; diff --git a/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h b/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h index ddb7fd3..f941f70 100644 --- a/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h +++ b/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h @@ -30,6 +30,8 @@ public: protected: DrmConstraints* onGetConstraints(int uniqueId, const String8* path, int action); + DrmMetadata* onGetMetadata(int uniqueId, const String8* path); + status_t onInitialize(int uniqueId); status_t onSetOnInfoListener(int uniqueId, const IDrmEngine::OnInfoListener* infoListener); @@ -54,7 +56,7 @@ protected: status_t onConsumeRights(int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve); status_t onSetPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int position); + int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position); bool onValidateAction( int uniqueId, const String8& path, int action, const ActionDescription& description); @@ -72,7 +74,7 @@ protected: DrmSupportInfo* onGetSupportInfo(int uniqueId); status_t onOpenDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, int fd, int offset, int length); + int uniqueId, DecryptHandle* decryptHandle, int fd, off64_t offset, off64_t length); status_t onOpenDecryptSession( int uniqueId, DecryptHandle* decryptHandle, const char* uri); @@ -88,7 +90,7 @@ protected: status_t onFinalizeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId); ssize_t onPread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset); + void* buffer, ssize_t numBytes, off64_t offset); private: DecryptHandle* openDecryptSessionImpl(); diff --git a/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp b/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp index 41f8e91..976978f 100644 --- a/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp +++ b/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp @@ -20,6 +20,7 @@ #include <drm/DrmRights.h> #include <drm/DrmConstraints.h> +#include <drm/DrmMetadata.h> #include <drm/DrmInfo.h> #include <drm/DrmInfoEvent.h> #include <drm/DrmInfoStatus.h> @@ -51,6 +52,10 @@ DrmPassthruPlugIn::~DrmPassthruPlugIn() { } +DrmMetadata* DrmPassthruPlugIn::onGetMetadata(int uniqueId, const String8* path) { + return NULL; +} + DrmConstraints* DrmPassthruPlugIn::onGetConstraints( int uniqueId, const String8* path, int action) { LOGD("DrmPassthruPlugIn::onGetConstraints From Path: %d", uniqueId); @@ -182,7 +187,7 @@ status_t DrmPassthruPlugIn::onConsumeRights(int uniqueId, DecryptHandle* decrypt } status_t DrmPassthruPlugIn::onSetPlaybackStatus(int uniqueId, DecryptHandle* decryptHandle, - int playbackStatus, int position) { + int playbackStatus, int64_t position) { LOGD("DrmPassthruPlugIn::onSetPlaybackStatus() : %d", uniqueId); return DRM_NO_ERROR; } @@ -229,7 +234,7 @@ DrmConvertedStatus* DrmPassthruPlugIn::onCloseConvertSession(int uniqueId, int c } status_t DrmPassthruPlugIn::onOpenDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, int fd, int offset, int length) { + int uniqueId, DecryptHandle* decryptHandle, int fd, off64_t offset, off64_t length) { LOGD("DrmPassthruPlugIn::onOpenDecryptSession() : %d", uniqueId); #ifdef ENABLE_PASSTHRU_DECRYPTION @@ -287,7 +292,7 @@ status_t DrmPassthruPlugIn::onFinalizeDecryptUnit( } ssize_t DrmPassthruPlugIn::onPread(int uniqueId, DecryptHandle* decryptHandle, - void* buffer, ssize_t numBytes, off_t offset) { + void* buffer, ssize_t numBytes, off64_t offset) { LOGD("DrmPassthruPlugIn::onPread() : %d", uniqueId); return 0; } diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java index 8d5c913..241ab17 100644 --- a/graphics/java/android/graphics/ComposeShader.java +++ b/graphics/java/android/graphics/ComposeShader.java @@ -30,7 +30,7 @@ public class ComposeShader extends Shader { /** Create a new compose shader, given shaders A, B, and a combining mode. When the mode is applied, it will be given the result from shader A as its - "dst", and the result of from shader B as its "src". + "dst", and the result from shader B as its "src". @param shaderA The colors from this shader are seen as the "dst" by the mode @param shaderB The colors from this shader are seen as the "src" by the mode @param mode The mode that combines the colors from the two shaders. If mode @@ -53,7 +53,7 @@ public class ComposeShader extends Shader { /** Create a new compose shader, given shaders A, B, and a combining PorterDuff mode. When the mode is applied, it will be given the result from shader A as its - "dst", and the result of from shader B as its "src". + "dst", and the result from shader B as its "src". @param shaderA The colors from this shader are seen as the "dst" by the mode @param shaderB The colors from this shader are seen as the "src" by the mode @param mode The PorterDuff mode that combines the colors from the two shaders. diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java index 0660441..f16e045 100644 --- a/graphics/java/android/renderscript/RenderScript.java +++ b/graphics/java/android/renderscript/RenderScript.java @@ -685,6 +685,8 @@ public class RenderScript { public static final int RS_MESSAGE_TO_CLIENT_ERROR = 3; public static final int RS_MESSAGE_TO_CLIENT_USER = 4; + public static final int RS_ERROR_FATAL_UNKNOWN = 0x1000; + MessageThread(RenderScript rs) { super("RSMessageThread"); mRS = rs; @@ -722,6 +724,10 @@ public class RenderScript { if (msg == RS_MESSAGE_TO_CLIENT_ERROR) { String e = mRS.nContextGetErrorMessage(mRS.mContext); + if (subID >= RS_ERROR_FATAL_UNKNOWN) { + throw new RSRuntimeException("Fatal error " + subID + ", details: " + e); + } + if(mRS.mErrorCallback != null) { mRS.mErrorCallback.mErrorMessage = e; mRS.mErrorCallback.mErrorNum = subID; diff --git a/include/drm/DrmManagerClient.h b/include/drm/DrmManagerClient.h index 5963c42..e6ba3c4 100644 --- a/include/drm/DrmManagerClient.h +++ b/include/drm/DrmManagerClient.h @@ -25,6 +25,7 @@ namespace android { class DrmInfo; class DrmRights; +class DrmMetadata; class DrmInfoEvent; class DrmInfoStatus; class DrmInfoRequest; @@ -65,7 +66,7 @@ public: * @return * Handle for the decryption session */ - DecryptHandle* openDecryptSession(int fd, int offset, int length); + DecryptHandle* openDecryptSession(int fd, off64_t offset, off64_t length); /** * Open the decrypt session to decrypt the given protected content @@ -109,7 +110,7 @@ public: * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - status_t setPlaybackStatus(DecryptHandle* decryptHandle, int playbackStatus, int position); + status_t setPlaybackStatus(DecryptHandle* decryptHandle, int playbackStatus, int64_t position); /** * Initialize decryption for the given unit of the protected content @@ -163,7 +164,7 @@ public: * * @return Number of bytes read. Returns -1 for Failure. */ - ssize_t pread(DecryptHandle* decryptHandle, void* buffer, ssize_t numBytes, off_t offset); + ssize_t pread(DecryptHandle* decryptHandle, void* buffer, ssize_t numBytes, off64_t offset); /** * Validates whether an action on the DRM content is allowed or not. @@ -204,6 +205,17 @@ public: DrmConstraints* getConstraints(const String8* path, const int action); /** + * Get metadata information associated with input content + * + * @param[in] path Path of the protected content + * @return DrmMetadata + * key-value pairs of metadata + * @note + * In case of error, return NULL + */ + DrmMetadata* getMetadata(const String8* path); + + /** * Check whether the given mimetype or path can be handled * * @param[in] path Path of the content needs to be handled diff --git a/include/drm/DrmMetadata.h b/include/drm/DrmMetadata.h new file mode 100644 index 0000000..2c7538a --- /dev/null +++ b/include/drm/DrmMetadata.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __DRM_METADATA_H__ +#define __DRM_METADATA_H__ + +#include "drm_framework_common.h" + +namespace android { + +/** + * This is an utility class which contains the constraints information. + * + * As a result of DrmManagerClient::getMetadata(const String8*) + * an instance of DrmMetadata would be returned. + */ +class DrmMetadata { +public: + /** + * Iterator for key + */ + class KeyIterator { + friend class DrmMetadata; + private: + KeyIterator(DrmMetadata* drmMetadata) : mDrmMetadata(drmMetadata), mIndex(0) {} + + public: + KeyIterator(const KeyIterator& keyIterator); + KeyIterator& operator=(const KeyIterator& keyIterator); + virtual ~KeyIterator() {} + + public: + bool hasNext(); + const String8& next(); + + private: + DrmMetadata* mDrmMetadata; + unsigned int mIndex; + }; + + /** + * Iterator for constraints + */ + class Iterator { + friend class DrmMetadata; + private: + Iterator(DrmMetadata* drmMetadata) : mDrmMetadata(drmMetadata), mIndex(0) {} + + public: + Iterator(const Iterator& iterator); + Iterator& operator=(const Iterator& iterator); + virtual ~Iterator() {} + + public: + bool hasNext(); + String8 next(); + + private: + DrmMetadata* mDrmMetadata; + unsigned int mIndex; + }; + +public: + DrmMetadata() {} + virtual ~DrmMetadata() { + DrmMetadata::KeyIterator keyIt = this->keyIterator(); + + while (keyIt.hasNext()) { + String8 key = keyIt.next(); + const char* value = this->getAsByteArray(&key); + if (NULL != value) { + delete[] value; + value = NULL; + } + } + mMetadataMap.clear(); + } + +public: + int getCount(void) const; + status_t put(const String8* key, const char* value); + String8 get(const String8& key) const; + const char* getAsByteArray(const String8* key) const; + KeyIterator keyIterator(); + Iterator iterator(); + +private: + const char* getValue(const String8* key) const; + +private: + typedef KeyedVector<String8, const char*> DrmMetadataMap; + DrmMetadataMap mMetadataMap; +}; + +}; + +#endif /* __DRM_METADATA_H__ */ + diff --git a/include/media/stagefright/ColorConverter.h b/include/media/stagefright/ColorConverter.h index bc3f464..2b61f58 100644 --- a/include/media/stagefright/ColorConverter.h +++ b/include/media/stagefright/ColorConverter.h @@ -33,35 +33,47 @@ struct ColorConverter { bool isValid() const; void convert( - size_t width, size_t height, - const void *srcBits, size_t srcSkip, - void *dstBits, size_t dstSkip); + const void *srcBits, + size_t srcWidth, size_t srcHeight, + size_t srcCropLeft, size_t srcCropTop, + size_t srcCropRight, size_t srcCropBottom, + void *dstBits, + size_t dstWidth, size_t dstHeight, + size_t dstCropLeft, size_t dstCropTop, + size_t dstCropRight, size_t dstCropBottom); private: + struct BitmapParams { + BitmapParams( + void *bits, + size_t width, size_t height, + size_t cropLeft, size_t cropTop, + size_t cropRight, size_t cropBottom); + + size_t cropWidth() const; + size_t cropHeight() const; + + void *mBits; + size_t mWidth, mHeight; + size_t mCropLeft, mCropTop, mCropRight, mCropBottom; + }; + OMX_COLOR_FORMATTYPE mSrcFormat, mDstFormat; uint8_t *mClip; uint8_t *initClip(); void convertCbYCrY( - size_t width, size_t height, - const void *srcBits, size_t srcSkip, - void *dstBits, size_t dstSkip); + const BitmapParams &src, const BitmapParams &dst); void convertYUV420Planar( - size_t width, size_t height, - const void *srcBits, size_t srcSkip, - void *dstBits, size_t dstSkip); + const BitmapParams &src, const BitmapParams &dst); void convertQCOMYUV420SemiPlanar( - size_t width, size_t height, - const void *srcBits, size_t srcSkip, - void *dstBits, size_t dstSkip); + const BitmapParams &src, const BitmapParams &dst); void convertYUV420SemiPlanar( - size_t width, size_t height, - const void *srcBits, size_t srcSkip, - void *dstBits, size_t dstSkip); + const BitmapParams &src, const BitmapParams &dst); ColorConverter(const ColorConverter &); ColorConverter &operator=(const ColorConverter &); diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h index 295b127..5f33739 100644 --- a/include/media/stagefright/MetaData.h +++ b/include/media/stagefright/MetaData.h @@ -32,6 +32,10 @@ enum { kKeyMIMEType = 'mime', // cstring kKeyWidth = 'widt', // int32_t kKeyHeight = 'heig', // int32_t + + // a rectangle, if absent assumed to be (0, 0, width - 1, height - 1) + kKeyCropRect = 'crop', + kKeyRotation = 'rotA', // int32_t (angle in degrees) kKeyIFramesInterval = 'ifiv', // int32_t kKeyStride = 'strd', // int32_t @@ -125,6 +129,7 @@ public: TYPE_INT64 = 'in64', TYPE_FLOAT = 'floa', TYPE_POINTER = 'ptr ', + TYPE_RECT = 'rect', }; void clear(); @@ -136,12 +141,22 @@ public: bool setFloat(uint32_t key, float value); bool setPointer(uint32_t key, void *value); + bool setRect( + uint32_t key, + int32_t left, int32_t top, + int32_t right, int32_t bottom); + bool findCString(uint32_t key, const char **value); bool findInt32(uint32_t key, int32_t *value); bool findInt64(uint32_t key, int64_t *value); bool findFloat(uint32_t key, float *value); bool findPointer(uint32_t key, void **value); + bool findRect( + uint32_t key, + int32_t *left, int32_t *top, + int32_t *right, int32_t *bottom); + bool setData(uint32_t key, uint32_t type, const void *data, size_t size); bool findData(uint32_t key, uint32_t *type, @@ -187,6 +202,10 @@ private: } }; + struct Rect { + int32_t mLeft, mTop, mRight, mBottom; + }; + KeyedVector<uint32_t, typed_data> mItems; // MetaData &operator=(const MetaData &); diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp index 5ebd2c0..00de39b 100644 --- a/libs/hwui/ResourceCache.cpp +++ b/libs/hwui/ResourceCache.cpp @@ -111,11 +111,6 @@ void ResourceCache::recycle(SkBitmap* resource) { resource->setPixels(NULL, NULL); return; } - recycle((void*) resource); -} - -void ResourceCache::recycle(void* resource) { - Mutex::Autolock _l(mLock); ResourceReference* ref = mCache->indexOfKey(resource) >= 0 ? mCache->valueFor(resource) : NULL; if (ref == NULL) { // Should not get here - shouldn't get a call to recycle if we're not yet tracking it diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h index b0abe2c..1bb4390 100644 --- a/libs/hwui/ResourceCache.h +++ b/libs/hwui/ResourceCache.h @@ -61,7 +61,6 @@ public: void decrementRefcount(SkBitmap* resource); void decrementRefcount(SkiaShader* resource); void decrementRefcount(SkiaColorFilter* resource); - void recycle(void* resource); void recycle(SkBitmap* resource); void destructor(SkBitmap* resource); void destructor(SkiaShader* resource); diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h index 0ed129f..f00f748 100644 --- a/libs/rs/RenderScript.h +++ b/libs/rs/RenderScript.h @@ -236,11 +236,16 @@ enum RsPrimitive { }; enum RsError { - RS_ERROR_NONE, - RS_ERROR_BAD_SHADER, - RS_ERROR_BAD_SCRIPT, - RS_ERROR_BAD_VALUE, - RS_ERROR_OUT_OF_MEMORY + RS_ERROR_NONE = 0, + RS_ERROR_BAD_SHADER = 1, + RS_ERROR_BAD_SCRIPT = 2, + RS_ERROR_BAD_VALUE = 3, + RS_ERROR_OUT_OF_MEMORY = 4, + RS_ERROR_DRIVER = 5, + + RS_ERROR_FATAL_UNKNOWN = 0x1000, + RS_ERROR_FATAL_DRIVER = 0x1001, + RS_ERROR_FATAL_PROGRAM_LINK = 0x1002 }; enum RsAnimationInterpolation { diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec index 1b584c8..7b35305 100644 --- a/libs/rs/rs.spec +++ b/libs/rs/rs.spec @@ -43,11 +43,6 @@ ContextDump { param int32_t bits } -ContextGetError { - param RsError *err - ret const char * - } - ContextSetPriority { param int32_t priority } diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp index 840a10e..35db332 100644 --- a/libs/rs/rsContext.cpp +++ b/libs/rs/rsContext.cpp @@ -287,10 +287,27 @@ uint32_t Context::runScript(Script *s) { return ret; } -void Context::checkError(const char *msg) const { +void Context::checkError(const char *msg, bool isFatal) const { + GLenum err = glGetError(); if (err != GL_NO_ERROR) { - LOGE("%p, GL Error, 0x%x, from %s", this, err, msg); + char buf[1024]; + snprintf(buf, sizeof(buf), "GL Error = 0x%08x, from: %s", err, msg); + + if (isFatal) { + setError(RS_ERROR_FATAL_DRIVER, buf); + } else { + switch (err) { + case GL_OUT_OF_MEMORY: + setError(RS_ERROR_OUT_OF_MEMORY, buf); + break; + default: + setError(RS_ERROR_DRIVER, buf); + break; + } + } + + LOGE("%p, %s", this, buf); } } @@ -597,7 +614,6 @@ Context::Context() { mPaused = false; mObjHead = NULL; mError = RS_ERROR_NONE; - mErrorMsg = NULL; } Context * Context::createContext(Device *dev, const RsSurfaceConfig *sc) { @@ -861,7 +877,8 @@ RsMessageToClientType Context::getMessageToClient(void *data, size_t *receiveLen return RS_MESSAGE_TO_CLIENT_RESIZE; } -bool Context::sendMessageToClient(const void *data, RsMessageToClientType cmdID, uint32_t subID, size_t len, bool waitForSpace) { +bool Context::sendMessageToClient(const void *data, RsMessageToClientType cmdID, + uint32_t subID, size_t len, bool waitForSpace) const { //LOGE("sendMessageToClient %i %i %i %i", cmdID, subID, len, waitForSpace); if (cmdID == 0) { LOGE("Attempting to send invalid command 0 to client."); @@ -894,18 +911,8 @@ void Context::deinitToClient() { mIO.mToClient.shutdown(); } -const char * Context::getError(RsError *err) { - *err = mError; - mError = RS_ERROR_NONE; - if (*err != RS_ERROR_NONE) { - return mErrorMsg; - } - return NULL; -} - -void Context::setError(RsError e, const char *msg) { +void Context::setError(RsError e, const char *msg) const { mError = e; - mErrorMsg = msg; sendMessageToClient(msg, RS_MESSAGE_TO_CLIENT_ERROR, e, strlen(msg) + 1, true); } @@ -1012,14 +1019,6 @@ void rsi_ContextDump(Context *rsc, int32_t bits) { ObjectBase::dumpAll(rsc); } -const char* rsi_ContextGetError(Context *rsc, RsError *e) { - const char *msg = rsc->getError(e); - if (*e != RS_ERROR_NONE) { - LOGE("RS Error %i %s", *e, msg); - } - return msg; -} - } } diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h index c377c73..cafbdff 100644 --- a/libs/rs/rsContext.h +++ b/libs/rs/rsContext.h @@ -144,7 +144,7 @@ public: RsMessageToClientType peekMessageToClient(size_t *receiveLen, uint32_t *subID, bool wait); RsMessageToClientType getMessageToClient(void *data, size_t *receiveLen, uint32_t *subID, size_t bufferLen, bool wait); - bool sendMessageToClient(const void *data, RsMessageToClientType cmdID, uint32_t subID, size_t len, bool waitForSpace); + bool sendMessageToClient(const void *data, RsMessageToClientType cmdID, uint32_t subID, size_t len, bool waitForSpace) const; uint32_t runScript(Script *s); void initToClient(); @@ -169,7 +169,7 @@ public: uint32_t getWidth() const {return mWidth;} uint32_t getHeight() const {return mHeight;} - ThreadIO mIO; + mutable ThreadIO mIO; // Timers enum Timers { @@ -197,9 +197,8 @@ public: } props; void dumpDebug() const; - void checkError(const char *) const; - const char * getError(RsError *); - void setError(RsError e, const char *msg = NULL); + void checkError(const char *, bool isFatal = false) const; + void setError(RsError e, const char *msg = NULL) const; mutable const ObjectBase * mObjHead; @@ -259,8 +258,7 @@ protected: bool mRunning; bool mExit; bool mPaused; - RsError mError; - const char *mErrorMsg; + mutable RsError mError; pthread_t mThreadId; pid_t mNativeThreadId; diff --git a/libs/rs/rsElement.cpp b/libs/rs/rsElement.cpp index 40321fe..6ae8bb8 100644 --- a/libs/rs/rsElement.cpp +++ b/libs/rs/rsElement.cpp @@ -348,7 +348,6 @@ RsElement rsi_ElementCreate(Context *rsc, RsDataKind dk, bool norm, uint32_t vecSize) { - //LOGE("rsi_ElementCreate %i %i %i %i", dt, dk, norm, vecSize); const Element *e = Element::create(rsc, dt, dk, norm, vecSize); e->incUserRef(); return (RsElement)e; @@ -360,7 +359,6 @@ RsElement rsi_ElementCreate2(Context *rsc, const char ** names, const size_t * nameLengths, const uint32_t * arraySizes) { - //LOGE("rsi_ElementCreate2 %i", count); const Element *e = Element::create(rsc, count, (const Element **)ein, names, nameLengths, arraySizes); e->incUserRef(); return (RsElement)e; diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp index ffa7d26..0713fb3 100644 --- a/libs/rs/rsProgramFragment.cpp +++ b/libs/rs/rsProgramFragment.cpp @@ -136,9 +136,9 @@ void ProgramFragment::createShader() { char buf[256]; for (uint32_t ct=0; ct < mTextureCount; ct++) { if (mTextureTargets[ct] == RS_TEXTURE_2D) { - sprintf(buf, "uniform sampler2D UNI_Tex%i;\n", ct); + snprintf(buf, sizeof(buf), "uniform sampler2D UNI_Tex%i;\n", ct); } else { - sprintf(buf, "uniform samplerCube UNI_Tex%i;\n", ct); + snprintf(buf, sizeof(buf), "uniform samplerCube UNI_Tex%i;\n", ct); } mShader.append(buf); } @@ -159,7 +159,7 @@ void ProgramFragment::init(Context *rsc) { mTextureUniformIndexStart = uniformIndex; char buf[256]; for (uint32_t ct=0; ct < mTextureCount; ct++) { - sprintf(buf, "UNI_Tex%i", ct); + snprintf(buf, sizeof(buf), "UNI_Tex%i", ct); mUniformNames[uniformIndex].setTo(buf); mUniformArraySizes[uniformIndex] = 1; uniformIndex++; diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp index 3fd2981..a28b9bd 100644 --- a/libs/rs/rsProgramVertex.cpp +++ b/libs/rs/rsProgramVertex.cpp @@ -49,7 +49,7 @@ void ProgramVertex::loadShader(Context *rsc) { Program::loadShader(rsc, GL_VERTEX_SHADER); } -void ProgramVertex::createShader() { +void ProgramVertex::createShader(Context *rsc) { if (mUserShader.length() > 1) { appendUserConstants(); @@ -81,13 +81,12 @@ void ProgramVertex::createShader() { } mShader.append(mUserShader); } else { - LOGE("ProgramFragment::createShader cannot create program, shader code not defined"); - rsAssert(0); + rsc->setError(RS_ERROR_FATAL_UNKNOWN, + "ProgramFragment::createShader cannot create program, shader code not defined"); } } void ProgramVertex::setupGL2(Context *rsc, ProgramVertexState *state, ShaderCache *sc) { - //LOGE("sgl2 vtx1 %x", glGetError()); if ((state->mLast.get() == this) && !mDirty) { return; } @@ -96,8 +95,8 @@ void ProgramVertex::setupGL2(Context *rsc, ProgramVertexState *state, ShaderCach if (!isUserProgram()) { if (mConstants[0].get() == NULL) { - LOGE("Unable to set fixed function emulation matrices because allocation is missing"); - rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing"); + rsc->setError(RS_ERROR_FATAL_UNKNOWN, + "Unable to set fixed function emulation matrices because allocation is missing"); return; } float *f = static_cast<float *>(mConstants[0]->getPtr()); @@ -120,12 +119,13 @@ void ProgramVertex::setupGL2(Context *rsc, ProgramVertexState *state, ShaderCach void ProgramVertex::setProjectionMatrix(Context *rsc, const rsc_Matrix *m) const { if (isUserProgram()) { - LOGE("Attempting to set fixed function emulation matrix projection on user program"); - rsc->setError(RS_ERROR_BAD_SHADER, "Cannot set emulation matrix on user shader"); + rsc->setError(RS_ERROR_FATAL_UNKNOWN, + "Attempting to set fixed function emulation matrix projection on user program"); return; } if (mConstants[0].get() == NULL) { - LOGE("Unable to set fixed function emulation matrix projection because allocation is missing"); + rsc->setError(RS_ERROR_FATAL_UNKNOWN, + "Unable to set fixed function emulation matrix projection because allocation is missing"); return; } float *f = static_cast<float *>(mConstants[0]->getPtr()); @@ -135,13 +135,13 @@ void ProgramVertex::setProjectionMatrix(Context *rsc, const rsc_Matrix *m) const void ProgramVertex::setModelviewMatrix(Context *rsc, const rsc_Matrix *m) const { if (isUserProgram()) { - LOGE("Attempting to set fixed function emulation matrix modelview on user program"); - rsc->setError(RS_ERROR_BAD_SHADER, "Cannot set emulation matrix on user shader"); + rsc->setError(RS_ERROR_FATAL_UNKNOWN, + "Attempting to set fixed function emulation matrix modelview on user program"); return; } if (mConstants[0].get() == NULL) { - LOGE("Unable to set fixed function emulation matrix modelview because allocation is missing"); - rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing"); + rsc->setError(RS_ERROR_FATAL_UNKNOWN, + "Unable to set fixed function emulation matrix modelview because allocation is missing"); return; } float *f = static_cast<float *>(mConstants[0]->getPtr()); @@ -151,13 +151,13 @@ void ProgramVertex::setModelviewMatrix(Context *rsc, const rsc_Matrix *m) const void ProgramVertex::setTextureMatrix(Context *rsc, const rsc_Matrix *m) const { if (isUserProgram()) { - LOGE("Attempting to set fixed function emulation matrix texture on user program"); - rsc->setError(RS_ERROR_BAD_SHADER, "Cannot set emulation matrix on user shader"); + rsc->setError(RS_ERROR_FATAL_UNKNOWN, + "Attempting to set fixed function emulation matrix texture on user program"); return; } if (mConstants[0].get() == NULL) { - LOGE("Unable to set fixed function emulation matrix texture because allocation is missing"); - rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing"); + rsc->setError(RS_ERROR_FATAL_UNKNOWN, + "Unable to set fixed function emulation matrix texture because allocation is missing"); return; } float *f = static_cast<float *>(mConstants[0]->getPtr()); @@ -167,13 +167,13 @@ void ProgramVertex::setTextureMatrix(Context *rsc, const rsc_Matrix *m) const { void ProgramVertex::getProjectionMatrix(Context *rsc, rsc_Matrix *m) const { if (isUserProgram()) { - LOGE("Attempting to get fixed function emulation matrix projection on user program"); - rsc->setError(RS_ERROR_BAD_SHADER, "Cannot get emulation matrix on user shader"); + rsc->setError(RS_ERROR_FATAL_UNKNOWN, + "Attempting to get fixed function emulation matrix projection on user program"); return; } if (mConstants[0].get() == NULL) { - LOGE("Unable to get fixed function emulation matrix projection because allocation is missing"); - rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing"); + rsc->setError(RS_ERROR_FATAL_UNKNOWN, + "Unable to get fixed function emulation matrix projection because allocation is missing"); return; } float *f = static_cast<float *>(mConstants[0]->getPtr()); @@ -202,7 +202,7 @@ void ProgramVertex::init(Context *rsc) { initAddUserElement(mConstantTypes[ct]->getElement(), mUniformNames, mUniformArraySizes, &uniformCount, RS_SHADER_UNI); } } - createShader(); + createShader(rsc); } void ProgramVertex::serialize(OStream *stream) const { diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h index 824edbb..2a5c863 100644 --- a/libs/rs/rsProgramVertex.h +++ b/libs/rs/rsProgramVertex.h @@ -40,7 +40,7 @@ public: void transformToScreen(Context *, float *v4out, const float *v3in) const; - virtual void createShader(); + virtual void createShader(Context *); virtual void loadShader(Context *); virtual void init(Context *); diff --git a/libs/rs/rsShaderCache.cpp b/libs/rs/rsShaderCache.cpp index d254018..45384c9 100644 --- a/libs/rs/rsShaderCache.cpp +++ b/libs/rs/rsShaderCache.cpp @@ -150,7 +150,7 @@ bool ShaderCache::lookup(Context *rsc, ProgramVertex *vtx, ProgramFragment *frag } } glDeleteProgram(pgm); - rsc->setError(RS_ERROR_BAD_SHADER, "Error linking GL Programs"); + rsc->setError(RS_ERROR_FATAL_PROGRAM_LINK, "Error linking GL Programs"); return false; } diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp index 2a51335..c195b9b 100644 --- a/libs/rs/rsType.cpp +++ b/libs/rs/rsType.cpp @@ -136,7 +136,7 @@ void Type::dumpLOGV(const char *prefix) const { char buf[1024]; ObjectBase::dumpLOGV(prefix); LOGV("%s Type: x=%i y=%i z=%i mip=%i face=%i", prefix, mDimX, mDimY, mDimZ, mDimLOD, mFaces); - sprintf(buf, "%s element: ", prefix); + snprintf(buf, sizeof(buf), "%s element: ", prefix); mElement->dumpLOGV(buf); } diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk index 61d8abd..5948e04 100644 --- a/libs/ui/Android.mk +++ b/libs/ui/Android.mk @@ -1,7 +1,46 @@ +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + LOCAL_PATH:= $(call my-dir) + +# libui is partially built for the host (used by build time keymap validation tool) +# These files are common to host and target builds. +commonSources:= \ + Input.cpp \ + Keyboard.cpp \ + KeyLayoutMap.cpp \ + KeyCharacterMap.cpp \ + +# For the host +# ===================================================== + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= $(commonSources) + +LOCAL_MODULE:= libui + +include $(BUILD_HOST_STATIC_LIBRARY) + + +# For the device +# ===================================================== + include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + $(commonSources) \ EGLUtils.cpp \ EventHub.cpp \ EventRecurrence.cpp \ @@ -10,10 +49,6 @@ LOCAL_SRC_FILES:= \ GraphicBufferAllocator.cpp \ GraphicBufferMapper.cpp \ GraphicLog.cpp \ - Keyboard.cpp \ - KeyLayoutMap.cpp \ - KeyCharacterMap.cpp \ - Input.cpp \ InputDispatcher.cpp \ InputManager.cpp \ InputReader.cpp \ diff --git a/libs/ui/KeyCharacterMap.cpp b/libs/ui/KeyCharacterMap.cpp index 890cc3f..e689c4b 100644 --- a/libs/ui/KeyCharacterMap.cpp +++ b/libs/ui/KeyCharacterMap.cpp @@ -733,6 +733,7 @@ status_t KeyCharacterMap::Parser::parseModifier(const String8& token, int32_t* o } combinedMeta |= metaState; + start = cur + 1; if (ch == '\0') { break; diff --git a/libs/ui/Overlay.cpp b/libs/ui/Overlay.cpp index 3aa8950..b082c53 100644 --- a/libs/ui/Overlay.cpp +++ b/libs/ui/Overlay.cpp @@ -96,7 +96,6 @@ void* Overlay::getBufferAddress(overlay_buffer_t buffer) } void Overlay::destroy() { - if (mStatus != NO_ERROR) return; // Must delete the objects in reverse creation order, thus the // data side must be closed first and then the destroy send to @@ -104,9 +103,15 @@ void Overlay::destroy() { if (mOverlayData) { overlay_data_close(mOverlayData); mOverlayData = NULL; + } else { + LOGD("Overlay::destroy mOverlayData is NULL"); } - mOverlayRef->mOverlayChannel->destroy(); + if (mOverlayRef != 0) { + mOverlayRef->mOverlayChannel->destroy(); + } else { + LOGD("Overlay::destroy mOverlayRef is NULL"); + } } status_t Overlay::getStatus() const { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index def88ae..9058a7b 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -984,7 +984,8 @@ public class AudioManager { * application when it places a phone call, as it will cause signals from the radio layer * to feed the platform mixer. * - * @param mode the requested audio mode (NORMAL, RINGTONE, IN_CALL or IN_COMMUNICATION). + * @param mode the requested audio mode ({@link #MODE_NORMAL}, {@link #MODE_RINGTONE}, + * {@link #MODE_IN_CALL} or {@link #MODE_IN_COMMUNICATION}). * Informs the HAL about the current audio state so that * it can route the audio appropriately. */ @@ -1000,7 +1001,8 @@ public class AudioManager { /** * Returns the current audio mode. * - * @return the current audio mode (NORMAL, RINGTONE, IN_CALL or IN_COMMUNICATION). + * @return the current audio mode ({@link #MODE_NORMAL}, {@link #MODE_RINGTONE}, + * {@link #MODE_IN_CALL} or {@link #MODE_IN_COMMUNICATION}). * Returns the current current audio state from the HAL. */ public int getMode() { @@ -1038,7 +1040,6 @@ public class AudioManager { */ public static final int MODE_IN_CALL = AudioSystem.MODE_IN_CALL; /** - * @hide * In communication audio mode. An audio/video chat or VoIP call is established. */ public static final int MODE_IN_COMMUNICATION = AudioSystem.MODE_IN_COMMUNICATION; diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java index e48e9e8..250ec44 100644 --- a/media/java/android/media/MtpDatabase.java +++ b/media/java/android/media/MtpDatabase.java @@ -25,11 +25,11 @@ import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Environment; import android.os.RemoteException; +import android.provider.MediaStore; import android.provider.MediaStore.Audio; import android.provider.MediaStore.Files; import android.provider.MediaStore.Images; import android.provider.MediaStore.MediaColumns; -import android.provider.Mtp; import android.util.Log; import java.io.File; @@ -1023,7 +1023,7 @@ public class MtpDatabase { Log.d(TAG, "sessionEnded"); if (mDatabaseModified) { Log.d(TAG, "sending ACTION_MTP_SESSION_END"); - mContext.sendBroadcast(new Intent(Mtp.ACTION_MTP_SESSION_END)); + mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END)); mDatabaseModified = false; } } diff --git a/media/java/android/media/MtpClient.java b/media/java/android/media/PtpClient.java index dcb1983..2e40635 100644 --- a/media/java/android/media/MtpClient.java +++ b/media/java/android/media/PtpClient.java @@ -21,9 +21,9 @@ import android.util.Log; /** * {@hide} */ -public class MtpClient { +public class PtpClient { - private static final String TAG = "MtpClient"; + private static final String TAG = "PtpClient"; private final Listener mListener; @@ -31,10 +31,10 @@ public class MtpClient { System.loadLibrary("media_jni"); } - public MtpClient(Listener listener) { + public PtpClient(Listener listener) { native_setup(); if (listener == null) { - throw new NullPointerException("MtpClient: listener is null"); + throw new NullPointerException("PtpClient: listener is null"); } mListener = listener; } @@ -75,10 +75,10 @@ public class MtpClient { } public interface Listener { - // called when a new MTP device has been discovered + // called when a new PTP device has been discovered void deviceAdded(int id); - // called when an MTP device has been removed + // called when an PTP device has been removed void deviceRemoved(int id); } diff --git a/media/java/android/media/MtpCursor.java b/media/java/android/media/PtpCursor.java index daa3f4d..bb5b1ec 100644 --- a/media/java/android/media/MtpCursor.java +++ b/media/java/android/media/PtpCursor.java @@ -18,17 +18,17 @@ package android.media; import android.database.AbstractWindowedCursor; import android.database.CursorWindow; -import android.provider.Mtp; +import android.provider.Ptp; import android.util.Log; import java.util.HashMap; /** - * Cursor class for MTP content provider + * Cursor class for PTP content provider * @hide */ -public final class MtpCursor extends AbstractWindowedCursor { - static final String TAG = "MtpCursor"; +public final class PtpCursor extends AbstractWindowedCursor { + static final String TAG = "PtpCursor"; static final int NO_COUNT = -1; /* constants for queryType */ @@ -49,10 +49,10 @@ public final class MtpCursor extends AbstractWindowedCursor { private int mCount = NO_COUNT; - public MtpCursor(MtpClient client, int queryType, int deviceID, long storageID, long objectID, + public PtpCursor(PtpClient client, int queryType, int deviceID, long storageID, long objectID, String[] projection) { if (client == null) { - throw new NullPointerException("client null in MtpCursor constructor"); + throw new NullPointerException("client null in PtpCursor constructor"); } mColumns = projection; @@ -132,19 +132,19 @@ public final class MtpCursor extends AbstractWindowedCursor { } /* Device Column IDs */ - /* These must match the values in MtpCursor.cpp */ + /* These must match the values in PtpCursor.cpp */ private static final int DEVICE_ROW_ID = 1; private static final int DEVICE_MANUFACTURER = 2; private static final int DEVICE_MODEL = 3; /* Storage Column IDs */ - /* These must match the values in MtpCursor.cpp */ + /* These must match the values in PtpCursor.cpp */ private static final int STORAGE_ROW_ID = 101; private static final int STORAGE_IDENTIFIER = 102; private static final int STORAGE_DESCRIPTION = 103; /* Object Column IDs */ - /* These must match the values in MtpCursor.cpp */ + /* These must match the values in PtpCursor.cpp */ private static final int OBJECT_ROW_ID = 201; private static final int OBJECT_STORAGE_ID = 202; private static final int OBJECT_FORMAT = 203; @@ -173,45 +173,45 @@ public final class MtpCursor extends AbstractWindowedCursor { static { sDeviceProjectionMap = new HashMap<String, Integer>(); - sDeviceProjectionMap.put(Mtp.Device._ID, new Integer(DEVICE_ROW_ID)); - sDeviceProjectionMap.put(Mtp.Device.MANUFACTURER, new Integer(DEVICE_MANUFACTURER)); - sDeviceProjectionMap.put(Mtp.Device.MODEL, new Integer(DEVICE_MODEL)); + sDeviceProjectionMap.put(Ptp.Device._ID, new Integer(DEVICE_ROW_ID)); + sDeviceProjectionMap.put(Ptp.Device.MANUFACTURER, new Integer(DEVICE_MANUFACTURER)); + sDeviceProjectionMap.put(Ptp.Device.MODEL, new Integer(DEVICE_MODEL)); sStorageProjectionMap = new HashMap<String, Integer>(); - sStorageProjectionMap.put(Mtp.Storage._ID, new Integer(STORAGE_ROW_ID)); - sStorageProjectionMap.put(Mtp.Storage.IDENTIFIER, new Integer(STORAGE_IDENTIFIER)); - sStorageProjectionMap.put(Mtp.Storage.DESCRIPTION, new Integer(STORAGE_DESCRIPTION)); + sStorageProjectionMap.put(Ptp.Storage._ID, new Integer(STORAGE_ROW_ID)); + sStorageProjectionMap.put(Ptp.Storage.IDENTIFIER, new Integer(STORAGE_IDENTIFIER)); + sStorageProjectionMap.put(Ptp.Storage.DESCRIPTION, new Integer(STORAGE_DESCRIPTION)); sObjectProjectionMap = new HashMap<String, Integer>(); - sObjectProjectionMap.put(Mtp.Object._ID, new Integer(OBJECT_ROW_ID)); - sObjectProjectionMap.put(Mtp.Object.STORAGE_ID, new Integer(OBJECT_STORAGE_ID)); - sObjectProjectionMap.put(Mtp.Object.FORMAT, new Integer(OBJECT_FORMAT)); - sObjectProjectionMap.put(Mtp.Object.PROTECTION_STATUS, new Integer(OBJECT_PROTECTION_STATUS)); - sObjectProjectionMap.put(Mtp.Object.SIZE, new Integer(OBJECT_SIZE)); - sObjectProjectionMap.put(Mtp.Object.THUMB_FORMAT, new Integer(OBJECT_THUMB_FORMAT)); - sObjectProjectionMap.put(Mtp.Object.THUMB_SIZE, new Integer(OBJECT_THUMB_SIZE)); - sObjectProjectionMap.put(Mtp.Object.THUMB_WIDTH, new Integer(OBJECT_THUMB_WIDTH)); - sObjectProjectionMap.put(Mtp.Object.THUMB_HEIGHT, new Integer(OBJECT_THUMB_HEIGHT)); - sObjectProjectionMap.put(Mtp.Object.IMAGE_WIDTH, new Integer(OBJECT_IMAGE_WIDTH)); - sObjectProjectionMap.put(Mtp.Object.IMAGE_HEIGHT, new Integer(OBJECT_IMAGE_HEIGHT)); - sObjectProjectionMap.put(Mtp.Object.IMAGE_DEPTH, new Integer(OBJECT_IMAGE_DEPTH)); - sObjectProjectionMap.put(Mtp.Object.PARENT, new Integer(OBJECT_PARENT)); - sObjectProjectionMap.put(Mtp.Object.ASSOCIATION_TYPE, new Integer(OBJECT_ASSOCIATION_TYPE)); - sObjectProjectionMap.put(Mtp.Object.ASSOCIATION_DESC, new Integer(OBJECT_ASSOCIATION_DESC)); - sObjectProjectionMap.put(Mtp.Object.SEQUENCE_NUMBER, new Integer(OBJECT_SEQUENCE_NUMBER)); - sObjectProjectionMap.put(Mtp.Object.NAME, new Integer(OBJECT_NAME)); - sObjectProjectionMap.put(Mtp.Object.DATE_CREATED, new Integer(OBJECT_DATE_CREATED)); - sObjectProjectionMap.put(Mtp.Object.DATE_MODIFIED, new Integer(OBJECT_DATE_MODIFIED)); - sObjectProjectionMap.put(Mtp.Object.KEYWORDS, new Integer(OBJECT_KEYWORDS)); - sObjectProjectionMap.put(Mtp.Object.THUMB, new Integer(OBJECT_THUMB)); - - sObjectProjectionMap.put(Mtp.Object.NAME, new Integer(OBJECT_NAME)); + sObjectProjectionMap.put(Ptp.Object._ID, new Integer(OBJECT_ROW_ID)); + sObjectProjectionMap.put(Ptp.Object.STORAGE_ID, new Integer(OBJECT_STORAGE_ID)); + sObjectProjectionMap.put(Ptp.Object.FORMAT, new Integer(OBJECT_FORMAT)); + sObjectProjectionMap.put(Ptp.Object.PROTECTION_STATUS, new Integer(OBJECT_PROTECTION_STATUS)); + sObjectProjectionMap.put(Ptp.Object.SIZE, new Integer(OBJECT_SIZE)); + sObjectProjectionMap.put(Ptp.Object.THUMB_FORMAT, new Integer(OBJECT_THUMB_FORMAT)); + sObjectProjectionMap.put(Ptp.Object.THUMB_SIZE, new Integer(OBJECT_THUMB_SIZE)); + sObjectProjectionMap.put(Ptp.Object.THUMB_WIDTH, new Integer(OBJECT_THUMB_WIDTH)); + sObjectProjectionMap.put(Ptp.Object.THUMB_HEIGHT, new Integer(OBJECT_THUMB_HEIGHT)); + sObjectProjectionMap.put(Ptp.Object.IMAGE_WIDTH, new Integer(OBJECT_IMAGE_WIDTH)); + sObjectProjectionMap.put(Ptp.Object.IMAGE_HEIGHT, new Integer(OBJECT_IMAGE_HEIGHT)); + sObjectProjectionMap.put(Ptp.Object.IMAGE_DEPTH, new Integer(OBJECT_IMAGE_DEPTH)); + sObjectProjectionMap.put(Ptp.Object.PARENT, new Integer(OBJECT_PARENT)); + sObjectProjectionMap.put(Ptp.Object.ASSOCIATION_TYPE, new Integer(OBJECT_ASSOCIATION_TYPE)); + sObjectProjectionMap.put(Ptp.Object.ASSOCIATION_DESC, new Integer(OBJECT_ASSOCIATION_DESC)); + sObjectProjectionMap.put(Ptp.Object.SEQUENCE_NUMBER, new Integer(OBJECT_SEQUENCE_NUMBER)); + sObjectProjectionMap.put(Ptp.Object.NAME, new Integer(OBJECT_NAME)); + sObjectProjectionMap.put(Ptp.Object.DATE_CREATED, new Integer(OBJECT_DATE_CREATED)); + sObjectProjectionMap.put(Ptp.Object.DATE_MODIFIED, new Integer(OBJECT_DATE_MODIFIED)); + sObjectProjectionMap.put(Ptp.Object.KEYWORDS, new Integer(OBJECT_KEYWORDS)); + sObjectProjectionMap.put(Ptp.Object.THUMB, new Integer(OBJECT_THUMB)); + + sObjectProjectionMap.put(Ptp.Object.NAME, new Integer(OBJECT_NAME)); } // used by the JNI code private int mNativeContext; - private native final void native_setup(MtpClient client, int queryType, + private native final void native_setup(PtpClient client, int queryType, int deviceID, long storageID, long objectID, int[] columns); private native final void native_finalize(); private native void native_wait_for_event(); diff --git a/media/java/android/media/videoeditor/MediaImageItem.java b/media/java/android/media/videoeditor/MediaImageItem.java index fa8d61b..e6e9bc2 100755 --- a/media/java/android/media/videoeditor/MediaImageItem.java +++ b/media/java/android/media/videoeditor/MediaImageItem.java @@ -17,6 +17,7 @@ package android.media.videoeditor;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import android.graphics.Bitmap;
@@ -178,12 +179,11 @@ public class MediaImageItem extends MediaItem { // duration change.
invalidateEndTransition();
- final long oldDurationMs = mDurationMs;
mDurationMs = durationMs;
adjustTransitions();
- adjustOverlays();
- adjustEffects();
+ final List<Overlay> adjustedOverlays = adjustOverlays();
+ final List<Effect> adjustedEffects = adjustEffects();
// Invalidate the beginning and end transitions after adjustments.
// This invalidation is necessary for the case in which an effect or
@@ -191,11 +191,7 @@ public class MediaImageItem extends MediaItem { // before the setDuration reduces the duration of the media item and
// causes an overlap of the beginning and/or end transition with the
// effect.
- // If the duration is growing, the begin transition does not need to
- // be invalidated since the effects, overlays are not adjusted.
- if (mDurationMs < oldDurationMs) {
- invalidateBeginTransition();
- }
+ invalidateBeginTransition(adjustedEffects, adjustedOverlays);
invalidateEndTransition();
}
@@ -293,14 +289,16 @@ public class MediaImageItem extends MediaItem { /**
* Invalidate the begin transition if any effects and overlays overlap
* with the begin transition.
+ *
+ * @param effects List of effects to check for transition overlap
+ * @param overlays List of overlays to check for transition overlap
*/
- private void invalidateBeginTransition() {
+ private void invalidateBeginTransition(List<Effect> effects, List<Overlay> overlays) {
if (mBeginTransition != null && mBeginTransition.isGenerated()) {
final long transitionDurationMs = mBeginTransition.getDuration();
// The begin transition must be invalidated if it overlaps with
// an effect.
- final List<Effect> effects = getAllEffects();
for (Effect effect : effects) {
// Check if the effect overlaps with the begin transition
if (effect.getStartTime() < transitionDurationMs) {
@@ -312,7 +310,6 @@ public class MediaImageItem extends MediaItem { if (mBeginTransition.isGenerated()) {
// The end transition must be invalidated if it overlaps with
// an overlay.
- final List<Overlay> overlays = getAllOverlays();
for (Overlay overlay : overlays) {
// Check if the overlay overlaps with the end transition
if (overlay.getStartTime() < transitionDurationMs) {
@@ -362,8 +359,11 @@ public class MediaImageItem extends MediaItem { /**
* Adjust the start time and/or duration of effects.
+ *
+ * @return The list of effects which were adjusted
*/
- private void adjustEffects() {
+ private List<Effect> adjustEffects() {
+ final List<Effect> adjustedEffects = new ArrayList<Effect>();
final List<Effect> effects = getAllEffects();
for (Effect effect : effects) {
// Adjust the start time if necessary
@@ -385,14 +385,20 @@ public class MediaImageItem extends MediaItem { if (effectStartTimeMs != effect.getStartTime() ||
effectDurationMs != effect.getDuration()) {
effect.setStartTimeAndDuration(effectStartTimeMs, effectDurationMs);
+ adjustedEffects.add(effect);
}
}
+
+ return adjustedEffects;
}
/**
* Adjust the start time and/or duration of overlays.
+ *
+ * @return The list of overlays which were adjusted
*/
- private void adjustOverlays() {
+ private List<Overlay> adjustOverlays() {
+ final List<Overlay> adjustedOverlays = new ArrayList<Overlay>();
final List<Overlay> overlays = getAllOverlays();
for (Overlay overlay : overlays) {
// Adjust the start time if necessary
@@ -414,8 +420,11 @@ public class MediaImageItem extends MediaItem { if (overlayStartTimeMs != overlay.getStartTime() ||
overlayDurationMs != overlay.getDuration()) {
overlay.setStartTimeAndDuration(overlayStartTimeMs, overlayDurationMs);
+ adjustedOverlays.add(overlay);
}
}
+
+ return adjustedOverlays;
}
/**
diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 25d243b..fbdfa67 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -9,10 +9,10 @@ LOCAL_SRC_FILES:= \ android_media_ResampleInputStream.cpp \ android_media_MediaProfiles.cpp \ android_media_AmrInputStream.cpp \ - android_media_MtpClient.cpp \ - android_media_MtpCursor.cpp \ android_media_MtpDatabase.cpp \ android_media_MtpServer.cpp \ + android_media_PtpClient.cpp \ + android_media_PtpCursor.cpp \ LOCAL_SHARED_LIBRARIES := \ libandroid_runtime \ diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 997d017..28aef0c 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -774,8 +774,8 @@ extern int register_android_media_MediaRecorder(JNIEnv *env); extern int register_android_media_MediaScanner(JNIEnv *env); extern int register_android_media_ResampleInputStream(JNIEnv *env); extern int register_android_media_MediaProfiles(JNIEnv *env); -extern int register_android_media_MtpClient(JNIEnv *env); -extern int register_android_media_MtpCursor(JNIEnv *env); +extern int register_android_media_PtpClient(JNIEnv *env); +extern int register_android_media_PtpCursor(JNIEnv *env); extern int register_android_media_MtpDatabase(JNIEnv *env); extern int register_android_media_MtpServer(JNIEnv *env); extern int register_android_media_AmrInputStream(JNIEnv *env); @@ -826,13 +826,13 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) goto bail; } - if (register_android_media_MtpClient(env) < 0) { - LOGE("ERROR: MtpClient native registration failed"); + if (register_android_media_PtpClient(env) < 0) { + LOGE("ERROR: PtpClient native registration failed"); goto bail; } - if (register_android_media_MtpCursor(env) < 0) { - LOGE("ERROR: MtpCursor native registration failed"); + if (register_android_media_PtpCursor(env) < 0) { + LOGE("ERROR: PtpCursor native registration failed"); goto bail; } diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp index 4525d1f..f04a2ae 100644 --- a/media/jni/android_media_MtpDatabase.cpp +++ b/media/jni/android_media_MtpDatabase.cpp @@ -948,18 +948,21 @@ MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property, result = new MtpProperty(property, MTP_TYPE_UINT128); break; case MTP_PROPERTY_NAME: - case MTP_PROPERTY_DATE_MODIFIED: case MTP_PROPERTY_DISPLAY_NAME: - case MTP_PROPERTY_DATE_ADDED: case MTP_PROPERTY_ARTIST: case MTP_PROPERTY_ALBUM_NAME: case MTP_PROPERTY_ALBUM_ARTIST: - case MTP_PROPERTY_ORIGINAL_RELEASE_DATE: case MTP_PROPERTY_GENRE: case MTP_PROPERTY_COMPOSER: case MTP_PROPERTY_DESCRIPTION: result = new MtpProperty(property, MTP_TYPE_STR); break; + case MTP_PROPERTY_DATE_MODIFIED: + case MTP_PROPERTY_DATE_ADDED: + case MTP_PROPERTY_ORIGINAL_RELEASE_DATE: + result = new MtpProperty(property, MTP_TYPE_STR); + result->setFormDateTime(); + break; case MTP_PROPERTY_OBJECT_FILE_NAME: // We allow renaming files and folders result = new MtpProperty(property, MTP_TYPE_STR, true); diff --git a/media/jni/android_media_MtpClient.cpp b/media/jni/android_media_PtpClient.cpp index 144dfc8..6af83e4 100644 --- a/media/jni/android_media_MtpClient.cpp +++ b/media/jni/android_media_PtpClient.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "MtpClientJNI" +#define LOG_TAG "PtpClientJNI" #include "utils/Log.h" #include <stdio.h> @@ -103,7 +103,7 @@ void MyClient::deviceRemoved(MtpDevice *device) { // ---------------------------------------------------------------------------- static void -android_media_MtpClient_setup(JNIEnv *env, jobject thiz) +android_media_PtpClient_setup(JNIEnv *env, jobject thiz) { #ifdef HAVE_ANDROID_OS LOGD("setup\n"); @@ -114,7 +114,7 @@ android_media_MtpClient_setup(JNIEnv *env, jobject thiz) } static void -android_media_MtpClient_finalize(JNIEnv *env, jobject thiz) +android_media_PtpClient_finalize(JNIEnv *env, jobject thiz) { #ifdef HAVE_ANDROID_OS LOGD("finalize\n"); @@ -126,7 +126,7 @@ android_media_MtpClient_finalize(JNIEnv *env, jobject thiz) } static jboolean -android_media_MtpClient_start(JNIEnv *env, jobject thiz) +android_media_PtpClient_start(JNIEnv *env, jobject thiz) { #ifdef HAVE_ANDROID_OS LOGD("start\n"); @@ -138,7 +138,7 @@ android_media_MtpClient_start(JNIEnv *env, jobject thiz) } static void -android_media_MtpClient_stop(JNIEnv *env, jobject thiz) +android_media_PtpClient_stop(JNIEnv *env, jobject thiz) { #ifdef HAVE_ANDROID_OS LOGD("stop\n"); @@ -148,7 +148,7 @@ android_media_MtpClient_stop(JNIEnv *env, jobject thiz) } static jboolean -android_media_MtpClient_delete_object(JNIEnv *env, jobject thiz, +android_media_PtpClient_delete_object(JNIEnv *env, jobject thiz, jint device_id, jlong object_id) { #ifdef HAVE_ANDROID_OS @@ -162,7 +162,7 @@ android_media_MtpClient_delete_object(JNIEnv *env, jobject thiz, } static jlong -android_media_MtpClient_get_parent(JNIEnv *env, jobject thiz, +android_media_PtpClient_get_parent(JNIEnv *env, jobject thiz, jint device_id, jlong object_id) { #ifdef HAVE_ANDROID_OS @@ -176,7 +176,7 @@ android_media_MtpClient_get_parent(JNIEnv *env, jobject thiz, } static jlong -android_media_MtpClient_get_storage_id(JNIEnv *env, jobject thiz, +android_media_PtpClient_get_storage_id(JNIEnv *env, jobject thiz, jint device_id, jlong object_id) { #ifdef HAVE_ANDROID_OS @@ -190,7 +190,7 @@ android_media_MtpClient_get_storage_id(JNIEnv *env, jobject thiz, } static jboolean -android_media_MtpClient_import_file(JNIEnv *env, jobject thiz, +android_media_PtpClient_import_file(JNIEnv *env, jobject thiz, jint device_id, jlong object_id, jstring dest_path) { #ifdef HAVE_ANDROID_OS @@ -209,28 +209,28 @@ android_media_MtpClient_import_file(JNIEnv *env, jobject thiz, // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { - {"native_setup", "()V", (void *)android_media_MtpClient_setup}, - {"native_finalize", "()V", (void *)android_media_MtpClient_finalize}, - {"native_start", "()Z", (void *)android_media_MtpClient_start}, - {"native_stop", "()V", (void *)android_media_MtpClient_stop}, - {"native_delete_object", "(IJ)Z", (void *)android_media_MtpClient_delete_object}, - {"native_get_parent", "(IJ)J", (void *)android_media_MtpClient_get_parent}, - {"native_get_storage_id", "(IJ)J", (void *)android_media_MtpClient_get_storage_id}, + {"native_setup", "()V", (void *)android_media_PtpClient_setup}, + {"native_finalize", "()V", (void *)android_media_PtpClient_finalize}, + {"native_start", "()Z", (void *)android_media_PtpClient_start}, + {"native_stop", "()V", (void *)android_media_PtpClient_stop}, + {"native_delete_object", "(IJ)Z", (void *)android_media_PtpClient_delete_object}, + {"native_get_parent", "(IJ)J", (void *)android_media_PtpClient_get_parent}, + {"native_get_storage_id", "(IJ)J", (void *)android_media_PtpClient_get_storage_id}, {"native_import_file", "(IJLjava/lang/String;)Z", - (void *)android_media_MtpClient_import_file}, + (void *)android_media_PtpClient_import_file}, }; -static const char* const kClassPathName = "android/media/MtpClient"; +static const char* const kClassPathName = "android/media/PtpClient"; -int register_android_media_MtpClient(JNIEnv *env) +int register_android_media_PtpClient(JNIEnv *env) { jclass clazz; - LOGD("register_android_media_MtpClient\n"); + LOGD("register_android_media_PtpClient\n"); - clazz = env->FindClass("android/media/MtpClient"); + clazz = env->FindClass("android/media/PtpClient"); if (clazz == NULL) { - LOGE("Can't find android/media/MtpClient"); + LOGE("Can't find android/media/PtpClient"); return -1; } method_deviceAdded = env->GetMethodID(clazz, "deviceAdded", "(I)V"); @@ -245,10 +245,10 @@ int register_android_media_MtpClient(JNIEnv *env) } field_context = env->GetFieldID(clazz, "mNativeContext", "I"); if (field_context == NULL) { - LOGE("Can't find MtpClient.mNativeContext"); + LOGE("Can't find PtpClient.mNativeContext"); return -1; } return AndroidRuntime::registerNativeMethods(env, - "android/media/MtpClient", gMethods, NELEM(gMethods)); + "android/media/PtpClient", gMethods, NELEM(gMethods)); } diff --git a/media/jni/android_media_MtpCursor.cpp b/media/jni/android_media_PtpCursor.cpp index 7a0ae8a..76c88f6 100644 --- a/media/jni/android_media_MtpCursor.cpp +++ b/media/jni/android_media_PtpCursor.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "MtpCursorJNI" +#define LOG_TAG "PtpCursorJNI" #include "utils/Log.h" #include <stdio.h> @@ -29,7 +29,7 @@ #include "binder/CursorWindow.h" #include "MtpClient.h" -#include "MtpCursor.h" +#include "PtpCursor.h" using namespace android; @@ -37,7 +37,7 @@ using namespace android; static jfieldID field_context; -// From android_media_MtpClient.cpp +// From android_media_PtpClient.cpp MtpClient * get_client_from_object(JNIEnv * env, jobject javaClient); // ---------------------------------------------------------------------------- @@ -48,11 +48,11 @@ static bool ExceptionCheck(void* env) } static void -android_media_MtpCursor_setup(JNIEnv *env, jobject thiz, jobject javaClient, +android_media_PtpCursor_setup(JNIEnv *env, jobject thiz, jobject javaClient, jint queryType, jint deviceID, jlong storageID, jlong objectID, jintArray javaColumns) { #ifdef HAVE_ANDROID_OS - LOGD("android_media_MtpCursor_setup queryType: %d deviceID: %d storageID: %lld objectID: %lld\n", + LOGD("android_media_PtpCursor_setup queryType: %d deviceID: %d storageID: %lld objectID: %lld\n", queryType, deviceID, storageID, objectID); int* columns = NULL; @@ -63,7 +63,7 @@ android_media_MtpCursor_setup(JNIEnv *env, jobject thiz, jobject javaClient, } MtpClient* client = get_client_from_object(env, javaClient); - MtpCursor* cursor = new MtpCursor(client, queryType, + PtpCursor* cursor = new PtpCursor(client, queryType, deviceID, storageID, objectID, columnCount, columns); if (columns) @@ -73,17 +73,17 @@ android_media_MtpCursor_setup(JNIEnv *env, jobject thiz, jobject javaClient, } static void -android_media_MtpCursor_finalize(JNIEnv *env, jobject thiz) +android_media_PtpCursor_finalize(JNIEnv *env, jobject thiz) { #ifdef HAVE_ANDROID_OS LOGD("finalize\n"); - MtpCursor *cursor = (MtpCursor *)env->GetIntField(thiz, field_context); + PtpCursor *cursor = (PtpCursor *)env->GetIntField(thiz, field_context); delete cursor; #endif } static jint -android_media_MtpCursor_fill_window(JNIEnv *env, jobject thiz, jobject javaWindow, jint startPos) +android_media_PtpCursor_fill_window(JNIEnv *env, jobject thiz, jobject javaWindow, jint startPos) { #ifdef HAVE_ANDROID_OS CursorWindow* window = get_window_from_object(env, javaWindow); @@ -93,7 +93,7 @@ android_media_MtpCursor_fill_window(JNIEnv *env, jobject thiz, jobject javaWindo "Bad CursorWindow"); return 0; } - MtpCursor *cursor = (MtpCursor *)env->GetIntField(thiz, field_context); + PtpCursor *cursor = (PtpCursor *)env->GetIntField(thiz, field_context); return cursor->fillWindow(window, startPos); #else @@ -104,33 +104,33 @@ android_media_MtpCursor_fill_window(JNIEnv *env, jobject thiz, jobject javaWindo // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { - {"native_setup", "(Landroid/media/MtpClient;IIJJ[I)V", - (void *)android_media_MtpCursor_setup}, - {"native_finalize", "()V", (void *)android_media_MtpCursor_finalize}, + {"native_setup", "(Landroid/media/PtpClient;IIJJ[I)V", + (void *)android_media_PtpCursor_setup}, + {"native_finalize", "()V", (void *)android_media_PtpCursor_finalize}, {"native_fill_window", "(Landroid/database/CursorWindow;I)I", - (void *)android_media_MtpCursor_fill_window}, + (void *)android_media_PtpCursor_fill_window}, }; -static const char* const kClassPathName = "android/media/MtpCursor"; +static const char* const kClassPathName = "android/media/PtpCursor"; -int register_android_media_MtpCursor(JNIEnv *env) +int register_android_media_PtpCursor(JNIEnv *env) { jclass clazz; - LOGD("register_android_media_MtpCursor\n"); + LOGD("register_android_media_PtpCursor\n"); - clazz = env->FindClass("android/media/MtpCursor"); + clazz = env->FindClass("android/media/PtpCursor"); if (clazz == NULL) { - LOGE("Can't find android/media/MtpCursor"); + LOGE("Can't find android/media/PtpCursor"); return -1; } field_context = env->GetFieldID(clazz, "mNativeContext", "I"); if (field_context == NULL) { - LOGE("Can't find MtpCursor.mNativeContext"); + LOGE("Can't find PtpCursor.mNativeContext"); return -1; } return AndroidRuntime::registerNativeMethods(env, - "android/media/MtpCursor", gMethods, NELEM(gMethods)); + "android/media/PtpCursor", gMethods, NELEM(gMethods)); } diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk index 3341ff7..a9d537f 100644 --- a/media/libmediaplayerservice/Android.mk +++ b/media/libmediaplayerservice/Android.mk @@ -31,7 +31,6 @@ LOCAL_SHARED_LIBRARIES := \ libandroid_runtime \ libstagefright \ libstagefright_omx \ - libstagefright_color_conversion \ libstagefright_foundation \ libsurfaceflinger_client diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 4ad1eb4..db23836 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -71,6 +71,7 @@ LOCAL_SHARED_LIBRARIES := \ libcrypto LOCAL_STATIC_LIBRARIES := \ + libstagefright_color_conversion \ libstagefright_aacdec \ libstagefright_aacenc \ libstagefright_amrnbdec \ @@ -97,7 +98,6 @@ LOCAL_SHARED_LIBRARIES += \ libstagefright_enc_common \ libstagefright_avc_common \ libstagefright_foundation \ - libstagefright_color_conversion ifeq ($(TARGET_OS)-$(TARGET_SIMULATOR),linux-true) LOCAL_LDLIBS += -lpthread -ldl diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 922aaa8..ec58919 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -81,16 +81,8 @@ private: struct AwesomeLocalRenderer : public AwesomeRenderer { AwesomeLocalRenderer( - OMX_COLOR_FORMATTYPE colorFormat, - const sp<Surface> &surface, - size_t displayWidth, size_t displayHeight, - size_t decodedWidth, size_t decodedHeight, - int32_t rotationDegrees) - : mTarget(NULL) { - init(colorFormat, surface, - displayWidth, displayHeight, - decodedWidth, decodedHeight, - rotationDegrees); + const sp<Surface> &surface, const sp<MetaData> &meta) + : mTarget(new SoftwareRenderer(surface, meta)) { } virtual void render(MediaBuffer *buffer) { @@ -111,28 +103,10 @@ protected: private: SoftwareRenderer *mTarget; - void init( - OMX_COLOR_FORMATTYPE colorFormat, - const sp<Surface> &surface, - size_t displayWidth, size_t displayHeight, - size_t decodedWidth, size_t decodedHeight, - int32_t rotationDegrees); - AwesomeLocalRenderer(const AwesomeLocalRenderer &); AwesomeLocalRenderer &operator=(const AwesomeLocalRenderer &);; }; -void AwesomeLocalRenderer::init( - OMX_COLOR_FORMATTYPE colorFormat, - const sp<Surface> &surface, - size_t displayWidth, size_t displayHeight, - size_t decodedWidth, size_t decodedHeight, - int32_t rotationDegrees) { - mTarget = new SoftwareRenderer( - colorFormat, surface, displayWidth, displayHeight, - decodedWidth, decodedHeight, rotationDegrees); -} - struct AwesomeNativeWindowRenderer : public AwesomeRenderer { AwesomeNativeWindowRenderer( const sp<ANativeWindow> &nativeWindow, @@ -188,9 +162,7 @@ AwesomePlayer::AwesomePlayer() mAudioPlayer(NULL), mFlags(0), mExtractorFlags(0), - mLastVideoBuffer(NULL), mVideoBuffer(NULL), - mSuspensionState(NULL), mDecryptHandle(NULL) { CHECK_EQ(mClient.connect(), OK); @@ -433,11 +405,6 @@ void AwesomePlayer::reset_l() { mVideoRenderer.clear(); - if (mLastVideoBuffer) { - mLastVideoBuffer->release(); - mLastVideoBuffer = NULL; - } - if (mVideoBuffer) { mVideoBuffer->release(); mVideoBuffer = NULL; @@ -469,7 +436,6 @@ void AwesomePlayer::reset_l() { mDurationUs = -1; mFlags = 0; mExtractorFlags = 0; - mVideoWidth = mVideoHeight = -1; mTimeSourceDeltaUs = 0; mVideoTimeUs = 0; @@ -482,9 +448,6 @@ void AwesomePlayer::reset_l() { mFileSource.clear(); - delete mSuspensionState; - mSuspensionState = NULL; - mBitrate = -1; } @@ -637,11 +600,6 @@ void AwesomePlayer::partial_reset_l() { mVideoRenderer.clear(); - if (mLastVideoBuffer) { - mLastVideoBuffer->release(); - mLastVideoBuffer = NULL; - } - if (mVideoBuffer) { mVideoBuffer->release(); mVideoBuffer = NULL; @@ -749,6 +707,13 @@ status_t AwesomePlayer::play_l() { bool deferredAudioSeek = false; + if (mDecryptHandle != NULL) { + int64_t position; + getPosition(&position); + mDrmManagerClient->setPlaybackStatus(mDecryptHandle, + Playback::START, position / 1000); + } + if (mAudioSource != NULL) { if (mAudioPlayer == NULL) { if (mAudioSink != NULL) { @@ -766,6 +731,11 @@ status_t AwesomePlayer::play_l() { mFlags &= ~(PLAYING | FIRST_FRAME); + if (mDecryptHandle != NULL) { + mDrmManagerClient->setPlaybackStatus(mDecryptHandle, + Playback::STOP, 0); + } + return err; } @@ -802,22 +772,31 @@ status_t AwesomePlayer::play_l() { seekTo_l(0); } - if (mDecryptHandle != NULL) { - int64_t position; - getPosition(&position); - mDrmManagerClient->setPlaybackStatus(mDecryptHandle, - Playback::START, position / 1000); - } - return OK; } void AwesomePlayer::notifyVideoSize_l() { sp<MetaData> meta = mVideoSource->getFormat(); - int32_t decodedWidth, decodedHeight; - CHECK(meta->findInt32(kKeyWidth, &decodedWidth)); - CHECK(meta->findInt32(kKeyHeight, &decodedHeight)); + int32_t cropLeft, cropTop, cropRight, cropBottom; + if (!meta->findRect( + kKeyCropRect, &cropLeft, &cropTop, &cropRight, &cropBottom)) { + int32_t width, height; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + + cropLeft = cropTop = 0; + cropRight = width - 1; + cropBottom = height - 1; + + LOGV("got dimensions only %d x %d", width, height); + } else { + LOGV("got crop rect %d, %d, %d, %d", + cropLeft, cropTop, cropRight, cropBottom); + } + + int32_t usableWidth = cropRight - cropLeft + 1; + int32_t usableHeight = cropBottom - cropTop + 1; int32_t rotationDegrees; if (!mVideoTrack->getFormat()->findInt32( @@ -827,10 +806,10 @@ void AwesomePlayer::notifyVideoSize_l() { if (rotationDegrees == 90 || rotationDegrees == 270) { notifyListener_l( - MEDIA_SET_VIDEO_SIZE, decodedHeight, decodedWidth); + MEDIA_SET_VIDEO_SIZE, usableHeight, usableWidth); } else { notifyListener_l( - MEDIA_SET_VIDEO_SIZE, decodedWidth, decodedHeight); + MEDIA_SET_VIDEO_SIZE, usableWidth, usableHeight); } } @@ -872,12 +851,7 @@ void AwesomePlayer::initRenderer_l() { // allocate their buffers in local address space. This renderer // then performs a color conversion and copy to get the data // into the ANativeBuffer. - mVideoRenderer = new AwesomeLocalRenderer( - (OMX_COLOR_FORMATTYPE)format, - mSurface, - mVideoWidth, mVideoHeight, - decodedWidth, decodedHeight, - rotationDegrees); + mVideoRenderer = new AwesomeLocalRenderer(mSurface, meta); } } @@ -1041,20 +1015,6 @@ void AwesomePlayer::seekAudioIfNecessary_l() { } } -status_t AwesomePlayer::getVideoDimensions( - int32_t *width, int32_t *height) const { - Mutex::Autolock autoLock(mLock); - - if (mVideoWidth < 0 || mVideoHeight < 0) { - return UNKNOWN_ERROR; - } - - *width = mVideoWidth; - *height = mVideoHeight; - - return OK; -} - void AwesomePlayer::setAudioSource(sp<MediaSource> source) { CHECK(source != NULL); @@ -1123,9 +1083,6 @@ status_t AwesomePlayer::initVideoDecoder(uint32_t flags) { } } - CHECK(mVideoTrack->getFormat()->findInt32(kKeyWidth, &mVideoWidth)); - CHECK(mVideoTrack->getFormat()->findInt32(kKeyHeight, &mVideoHeight)); - status_t err = mVideoSource->start(); if (err != OK) { @@ -1180,11 +1137,6 @@ void AwesomePlayer::onVideoEvent() { mVideoEventPending = false; if (mSeeking) { - if (mLastVideoBuffer) { - mLastVideoBuffer->release(); - mLastVideoBuffer = NULL; - } - if (mVideoBuffer) { mVideoBuffer->release(); mVideoBuffer = NULL; @@ -1327,11 +1279,7 @@ void AwesomePlayer::onVideoEvent() { mVideoRenderer->render(mVideoBuffer); } - if (mLastVideoBuffer) { - mLastVideoBuffer->release(); - mLastVideoBuffer = NULL; - } - mLastVideoBuffer = mVideoBuffer; + mVideoBuffer->release(); mVideoBuffer = NULL; postVideoEvent_l(); @@ -1745,142 +1693,6 @@ void AwesomePlayer::finishAsyncPrepare_l() { mPreparedCondition.broadcast(); } -status_t AwesomePlayer::suspend() { - LOGV("suspend"); - Mutex::Autolock autoLock(mLock); - - if (mSuspensionState != NULL) { - if (mLastVideoBuffer == NULL) { - //go into here if video is suspended again - //after resuming without being played between - //them - SuspensionState *state = mSuspensionState; - mSuspensionState = NULL; - reset_l(); - mSuspensionState = state; - return OK; - } - - delete mSuspensionState; - mSuspensionState = NULL; - } - - if (mFlags & PREPARING) { - mFlags |= PREPARE_CANCELLED; - if (mConnectingDataSource != NULL) { - LOGI("interrupting the connection process"); - mConnectingDataSource->disconnect(); - } - } - - while (mFlags & PREPARING) { - mPreparedCondition.wait(mLock); - } - - SuspensionState *state = new SuspensionState; - state->mUri = mUri; - state->mUriHeaders = mUriHeaders; - state->mFileSource = mFileSource; - - state->mFlags = mFlags & (PLAYING | AUTO_LOOPING | LOOPING | AT_EOS); - getPosition(&state->mPositionUs); - - if (mLastVideoBuffer) { - size_t size = mLastVideoBuffer->range_length(); - - if (size) { - int32_t unreadable; - if (!mLastVideoBuffer->meta_data()->findInt32( - kKeyIsUnreadable, &unreadable) - || unreadable == 0) { - state->mLastVideoFrameSize = size; - state->mLastVideoFrame = malloc(size); - memcpy(state->mLastVideoFrame, - (const uint8_t *)mLastVideoBuffer->data() - + mLastVideoBuffer->range_offset(), - size); - - state->mVideoWidth = mVideoWidth; - state->mVideoHeight = mVideoHeight; - - sp<MetaData> meta = mVideoSource->getFormat(); - CHECK(meta->findInt32(kKeyColorFormat, &state->mColorFormat)); - CHECK(meta->findInt32(kKeyWidth, &state->mDecodedWidth)); - CHECK(meta->findInt32(kKeyHeight, &state->mDecodedHeight)); - } else { - LOGV("Unable to save last video frame, we have no access to " - "the decoded video data."); - } - } - } - - reset_l(); - - mSuspensionState = state; - - return OK; -} - -status_t AwesomePlayer::resume() { - LOGV("resume"); - Mutex::Autolock autoLock(mLock); - - if (mSuspensionState == NULL) { - return INVALID_OPERATION; - } - - SuspensionState *state = mSuspensionState; - mSuspensionState = NULL; - - status_t err; - if (state->mFileSource != NULL) { - err = setDataSource_l(state->mFileSource); - - if (err == OK) { - mFileSource = state->mFileSource; - } - } else { - err = setDataSource_l(state->mUri, &state->mUriHeaders); - } - - if (err != OK) { - delete state; - state = NULL; - - return err; - } - - seekTo_l(state->mPositionUs); - - mFlags = state->mFlags & (AUTO_LOOPING | LOOPING | AT_EOS); - - if (state->mLastVideoFrame && mSurface != NULL) { - mVideoRenderer = - new AwesomeLocalRenderer( - (OMX_COLOR_FORMATTYPE)state->mColorFormat, - mSurface, - state->mVideoWidth, - state->mVideoHeight, - state->mDecodedWidth, - state->mDecodedHeight, - 0); - - mVideoRendererIsPreview = true; - - ((AwesomeLocalRenderer *)mVideoRenderer.get())->render( - state->mLastVideoFrame, state->mLastVideoFrameSize); - } - - if (state->mFlags & PLAYING) { - play_l(); - } - - mSuspensionState = state; - state = NULL; - - return OK; -} - uint32_t AwesomePlayer::flags() const { return mExtractorFlags; } diff --git a/media/libstagefright/MetaData.cpp b/media/libstagefright/MetaData.cpp index 63b476e..884f3b4 100644 --- a/media/libstagefright/MetaData.cpp +++ b/media/libstagefright/MetaData.cpp @@ -70,6 +70,19 @@ bool MetaData::setPointer(uint32_t key, void *value) { return setData(key, TYPE_POINTER, &value, sizeof(value)); } +bool MetaData::setRect( + uint32_t key, + int32_t left, int32_t top, + int32_t right, int32_t bottom) { + Rect r; + r.mLeft = left; + r.mTop = top; + r.mRight = right; + r.mBottom = bottom; + + return setData(key, TYPE_RECT, &r, sizeof(r)); +} + bool MetaData::findCString(uint32_t key, const char **value) { uint32_t type; const void *data; @@ -143,6 +156,28 @@ bool MetaData::findPointer(uint32_t key, void **value) { return true; } +bool MetaData::findRect( + uint32_t key, + int32_t *left, int32_t *top, + int32_t *right, int32_t *bottom) { + uint32_t type; + const void *data; + size_t size; + if (!findData(key, &type, &data, &size) || type != TYPE_RECT) { + return false; + } + + CHECK_EQ(size, sizeof(Rect)); + + const Rect *r = (const Rect *)data; + *left = r->mLeft; + *top = r->mTop; + *right = r->mRight; + *bottom = r->mBottom; + + return true; +} + bool MetaData::setData( uint32_t key, uint32_t type, const void *data, size_t size) { bool overwrote_existing = true; diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 6ca0f4f..3108e4e 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -38,11 +38,11 @@ #include <binder/IServiceManager.h> #include <binder/MemoryDealer.h> #include <binder/ProcessState.h> +#include <media/stagefright/foundation/ADebug.h> #include <media/IMediaPlayerService.h> #include <media/stagefright/HardwareAPI.h> #include <media/stagefright/MediaBuffer.h> #include <media/stagefright/MediaBufferGroup.h> -#include <media/stagefright/MediaDebug.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaExtractor.h> #include <media/stagefright/MetaData.h> @@ -526,7 +526,7 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) { size_t size; if (meta->findData(kKeyESDS, &type, &data, &size)) { ESDS esds((const char *)data, size); - CHECK_EQ(esds.InitCheck(), OK); + CHECK_EQ(esds.InitCheck(), (status_t)OK); const void *codec_specific_data; size_t codec_specific_data_size; @@ -541,7 +541,7 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) { const uint8_t *ptr = (const uint8_t *)data; CHECK(size >= 7); - CHECK_EQ(ptr[0], 1); // configurationVersion == 1 + CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1 uint8_t profile = ptr[1]; uint8_t level = ptr[3]; @@ -730,7 +730,7 @@ void OMXCodec::setMinBufferSize(OMX_U32 portIndex, OMX_U32 size) { status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); if ((portIndex == kPortIndexInput && (mQuirks & kInputBufferSizesAreBogus)) || (def.nBufferSize < size)) { @@ -739,11 +739,11 @@ void OMXCodec::setMinBufferSize(OMX_U32 portIndex, OMX_U32 size) { err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); // Make sure the setting actually stuck. if (portIndex == kPortIndexInput @@ -923,7 +923,7 @@ void OMXCodec::setVideoInputFormat( } OMX_COLOR_FORMATTYPE colorFormat; - CHECK_EQ(OK, findTargetColorFormat(meta, &colorFormat)); + CHECK_EQ((status_t)OK, findTargetColorFormat(meta, &colorFormat)); status_t err; OMX_PARAM_PORTDEFINITIONTYPE def; @@ -932,19 +932,19 @@ void OMXCodec::setVideoInputFormat( //////////////////////// Input port ///////////////////////// CHECK_EQ(setVideoPortFormatType( kPortIndexInput, OMX_VIDEO_CodingUnused, - colorFormat), OK); + colorFormat), (status_t)OK); InitOMXParams(&def); def.nPortIndex = kPortIndexInput; err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); def.nBufferSize = getFrameSize(colorFormat, stride > 0? stride: -stride, sliceHeight); - CHECK_EQ(def.eDomain, OMX_PortDomainVideo); + CHECK_EQ((int)def.eDomain, (int)OMX_PortDomainVideo); video_def->nFrameWidth = width; video_def->nFrameHeight = height; @@ -956,20 +956,20 @@ void OMXCodec::setVideoInputFormat( err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); //////////////////////// Output port ///////////////////////// CHECK_EQ(setVideoPortFormatType( kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused), - OK); + (status_t)OK); InitOMXParams(&def); def.nPortIndex = kPortIndexOutput; err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); - CHECK_EQ(def.eDomain, OMX_PortDomainVideo); + CHECK_EQ(err, (status_t)OK); + CHECK_EQ((int)def.eDomain, (int)OMX_PortDomainVideo); video_def->nFrameWidth = width; video_def->nFrameHeight = height; @@ -984,23 +984,23 @@ void OMXCodec::setVideoInputFormat( err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); /////////////////// Codec-specific //////////////////////// switch (compressionFormat) { case OMX_VIDEO_CodingMPEG4: { - CHECK_EQ(setupMPEG4EncoderParameters(meta), OK); + CHECK_EQ(setupMPEG4EncoderParameters(meta), (status_t)OK); break; } case OMX_VIDEO_CodingH263: - CHECK_EQ(setupH263EncoderParameters(meta), OK); + CHECK_EQ(setupH263EncoderParameters(meta), (status_t)OK); break; case OMX_VIDEO_CodingAVC: { - CHECK_EQ(setupAVCEncoderParameters(meta), OK); + CHECK_EQ(setupAVCEncoderParameters(meta), (status_t)OK); break; } @@ -1059,7 +1059,7 @@ status_t OMXCodec::setupBitRate(int32_t bitRate) { status_t err = mOMX->getParameter( mNode, OMX_IndexParamVideoBitrate, &bitrateType, sizeof(bitrateType)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); bitrateType.eControlRate = OMX_Video_ControlRateVariable; bitrateType.nTargetBitrate = bitRate; @@ -1067,7 +1067,7 @@ status_t OMXCodec::setupBitRate(int32_t bitRate) { err = mOMX->setParameter( mNode, OMX_IndexParamVideoBitrate, &bitrateType, sizeof(bitrateType)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); return OK; } @@ -1132,7 +1132,7 @@ status_t OMXCodec::setupH263EncoderParameters(const sp<MetaData>& meta) { status_t err = mOMX->getParameter( mNode, OMX_IndexParamVideoH263, &h263type, sizeof(h263type)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); h263type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP; @@ -1159,10 +1159,10 @@ status_t OMXCodec::setupH263EncoderParameters(const sp<MetaData>& meta) { err = mOMX->setParameter( mNode, OMX_IndexParamVideoH263, &h263type, sizeof(h263type)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); - CHECK_EQ(setupBitRate(bitRate), OK); - CHECK_EQ(setupErrorCorrectionParameters(), OK); + CHECK_EQ(setupBitRate(bitRate), (status_t)OK); + CHECK_EQ(setupErrorCorrectionParameters(), (status_t)OK); return OK; } @@ -1179,7 +1179,7 @@ status_t OMXCodec::setupMPEG4EncoderParameters(const sp<MetaData>& meta) { status_t err = mOMX->getParameter( mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); mpeg4type.nSliceHeaderSpacing = 0; mpeg4type.bSVH = OMX_FALSE; @@ -1211,10 +1211,10 @@ status_t OMXCodec::setupMPEG4EncoderParameters(const sp<MetaData>& meta) { err = mOMX->setParameter( mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); - CHECK_EQ(setupBitRate(bitRate), OK); - CHECK_EQ(setupErrorCorrectionParameters(), OK); + CHECK_EQ(setupBitRate(bitRate), (status_t)OK); + CHECK_EQ(setupErrorCorrectionParameters(), (status_t)OK); return OK; } @@ -1232,7 +1232,7 @@ status_t OMXCodec::setupAVCEncoderParameters(const sp<MetaData>& meta) { status_t err = mOMX->getParameter( mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); h264type.nAllowedPictureTypes = OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP; @@ -1284,9 +1284,9 @@ status_t OMXCodec::setupAVCEncoderParameters(const sp<MetaData>& meta) { err = mOMX->setParameter( mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); - CHECK_EQ(setupBitRate(bitRate), OK); + CHECK_EQ(setupBitRate(bitRate), (status_t)OK); return OK; } @@ -1324,8 +1324,8 @@ status_t OMXCodec::setVideoOutputFormat( status_t err = mOMX->getParameter( mNode, OMX_IndexParamVideoPortFormat, &format, sizeof(format)); - CHECK_EQ(err, OK); - CHECK_EQ(format.eCompressionFormat, OMX_VIDEO_CodingUnused); + CHECK_EQ(err, (status_t)OK); + CHECK_EQ((int)format.eCompressionFormat, (int)OMX_VIDEO_CodingUnused); static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00; @@ -1353,7 +1353,7 @@ status_t OMXCodec::setVideoOutputFormat( err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); #if 1 // XXX Need a (much) better heuristic to compute input buffer sizes. @@ -1363,7 +1363,7 @@ status_t OMXCodec::setVideoOutputFormat( } #endif - CHECK_EQ(def.eDomain, OMX_PortDomainVideo); + CHECK_EQ((int)def.eDomain, (int)OMX_PortDomainVideo); video_def->nFrameWidth = width; video_def->nFrameHeight = height; @@ -1385,8 +1385,8 @@ status_t OMXCodec::setVideoOutputFormat( err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); - CHECK_EQ(def.eDomain, OMX_PortDomainVideo); + CHECK_EQ(err, (status_t)OK); + CHECK_EQ((int)def.eDomain, (int)OMX_PortDomainVideo); #if 0 def.nBufferSize = @@ -1510,7 +1510,7 @@ OMXCodec::~OMXCodec() { CHECK(mState == LOADED || mState == ERROR); status_t err = mOMX->freeNode(mNode); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); mNode = NULL; setState(DEAD); @@ -1527,21 +1527,21 @@ OMXCodec::~OMXCodec() { status_t OMXCodec::init() { // mLock is held. - CHECK_EQ(mState, LOADED); + CHECK_EQ((int)mState, (int)LOADED); status_t err; if (!(mQuirks & kRequiresLoadedToIdleAfterAllocation)) { err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); setState(LOADED_TO_IDLE); } err = allocateBuffers(); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); if (mQuirks & kRequiresLoadedToIdleAfterAllocation) { err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); setState(LOADED_TO_IDLE); } @@ -1896,10 +1896,10 @@ void OMXCodec::on_message(const omx_message &msg) { CODEC_LOGV("Port is disabled, freeing buffer %p", buffer); status_t err = freeBuffer(kPortIndexInput, i); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); } else if (mState != ERROR && mPortStatus[kPortIndexInput] != SHUTTING_DOWN) { - CHECK_EQ(mPortStatus[kPortIndexInput], ENABLED); + CHECK_EQ((int)mPortStatus[kPortIndexInput], (int)ENABLED); drainInputBuffer(&buffers->editItemAt(i)); } break; @@ -1937,7 +1937,7 @@ void OMXCodec::on_message(const omx_message &msg) { CODEC_LOGV("Port is disabled, freeing buffer %p", buffer); status_t err = freeBuffer(kPortIndexOutput, i); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); #if 0 } else if (mPortStatus[kPortIndexOutput] == ENABLED @@ -1947,7 +1947,7 @@ void OMXCodec::on_message(const omx_message &msg) { mBufferFilled.signal(); #endif } else if (mPortStatus[kPortIndexOutput] != SHUTTING_DOWN) { - CHECK_EQ(mPortStatus[kPortIndexOutput], ENABLED); + CHECK_EQ((int)mPortStatus[kPortIndexOutput], (int)ENABLED); if (info->mMediaBuffer == NULL) { CHECK(mOMXLivesLocally); @@ -2045,84 +2045,6 @@ void OMXCodec::on_message(const omx_message &msg) { } } -void OMXCodec::onEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { - switch (event) { - case OMX_EventCmdComplete: - { - onCmdComplete((OMX_COMMANDTYPE)data1, data2); - break; - } - - case OMX_EventError: - { - CODEC_LOGE("ERROR(0x%08lx, %ld)", data1, data2); - - setState(ERROR); - break; - } - - case OMX_EventPortSettingsChanged: - { - CODEC_LOGV("OMX_EventPortSettingsChanged(port=%ld, data2=0x%08lx)", - data1, data2); - - if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) { - onPortSettingsChanged(data1); - } else if (data1 == kPortIndexOutput - && data2 == OMX_IndexConfigCommonOutputCrop) { - - OMX_CONFIG_RECTTYPE rect; - rect.nPortIndex = kPortIndexOutput; - InitOMXParams(&rect); - - status_t err = - mOMX->getConfig( - mNode, OMX_IndexConfigCommonOutputCrop, - &rect, sizeof(rect)); - - if (err == OK) { - CODEC_LOGV( - "output crop (%ld, %ld, %ld, %ld)", - rect.nLeft, rect.nTop, rect.nWidth, rect.nHeight); - - if (mNativeWindow != NULL) { - android_native_rect_t crop; - crop.left = rect.nLeft; - crop.top = rect.nTop; - crop.right = crop.left + rect.nWidth - 1; - crop.bottom = crop.top + rect.nHeight - 1; - - CHECK_EQ(0, native_window_set_crop( - mNativeWindow.get(), &crop)); - } - } else { - CODEC_LOGE("getConfig(OMX_IndexConfigCommonOutputCrop) " - "returned error 0x%08x", err); - } - } - break; - } - -#if 0 - case OMX_EventBufferFlag: - { - CODEC_LOGV("EVENT_BUFFER_FLAG(%ld)", data1); - - if (data1 == kPortIndexOutput) { - mNoMoreOutputData = true; - } - break; - } -#endif - - default: - { - CODEC_LOGV("EVENT(%d, %ld, %ld)", event, data1, data2); - break; - } - } -} - // Has the format changed in any way that the client would have to be aware of? static bool formatHasNotablyChanged( const sp<MetaData> &from, const sp<MetaData> &to) { @@ -2167,6 +2089,21 @@ static bool formatHasNotablyChanged( if (height_from != height_to) { return true; } + + int32_t left_from, top_from, right_from, bottom_from; + CHECK(from->findRect( + kKeyCropRect, + &left_from, &top_from, &right_from, &bottom_from)); + + int32_t left_to, top_to, right_to, bottom_to; + CHECK(to->findRect( + kKeyCropRect, + &left_to, &top_to, &right_to, &bottom_to)); + + if (left_to != left_from || top_to != top_from + || right_to != right_from || bottom_to != bottom_from) { + return true; + } } else if (!strcasecmp(mime_from, MEDIA_MIMETYPE_AUDIO_RAW)) { int32_t numChannels_from, numChannels_to; CHECK(from->findInt32(kKeyChannelCount, &numChannels_from)); @@ -2188,6 +2125,78 @@ static bool formatHasNotablyChanged( return false; } +void OMXCodec::onEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + switch (event) { + case OMX_EventCmdComplete: + { + onCmdComplete((OMX_COMMANDTYPE)data1, data2); + break; + } + + case OMX_EventError: + { + CODEC_LOGE("ERROR(0x%08lx, %ld)", data1, data2); + + setState(ERROR); + break; + } + + case OMX_EventPortSettingsChanged: + { + CODEC_LOGV("OMX_EventPortSettingsChanged(port=%ld, data2=0x%08lx)", + data1, data2); + + if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) { + onPortSettingsChanged(data1); + } else if (data1 == kPortIndexOutput + && data2 == OMX_IndexConfigCommonOutputCrop) { + + sp<MetaData> oldOutputFormat = mOutputFormat; + initOutputFormat(mSource->getFormat()); + + if (formatHasNotablyChanged(oldOutputFormat, mOutputFormat)) { + mOutputPortSettingsHaveChanged = true; + + if (mNativeWindow != NULL) { + int32_t left, top, right, bottom; + CHECK(mOutputFormat->findRect( + kKeyCropRect, + &left, &top, &right, &bottom)); + + android_native_rect_t crop; + crop.left = left; + crop.top = top; + crop.right = right; + crop.bottom = bottom; + + CHECK_EQ(0, native_window_set_crop( + mNativeWindow.get(), &crop)); + } + } + } + break; + } + +#if 0 + case OMX_EventBufferFlag: + { + CODEC_LOGV("EVENT_BUFFER_FLAG(%ld)", data1); + + if (data1 == kPortIndexOutput) { + mNoMoreOutputData = true; + } + break; + } +#endif + + default: + { + CODEC_LOGV("EVENT(%d, %ld, %ld)", event, data1, data2); + break; + } + } +} + void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { switch (cmd) { case OMX_CommandStateSet: @@ -2202,13 +2211,13 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { CODEC_LOGV("PORT_DISABLED(%ld)", portIndex); CHECK(mState == EXECUTING || mState == RECONFIGURING); - CHECK_EQ(mPortStatus[portIndex], DISABLING); - CHECK_EQ(mPortBuffers[portIndex].size(), 0); + CHECK_EQ((int)mPortStatus[portIndex], (int)DISABLING); + CHECK_EQ(mPortBuffers[portIndex].size(), 0u); mPortStatus[portIndex] = DISABLED; if (mState == RECONFIGURING) { - CHECK_EQ(portIndex, kPortIndexOutput); + CHECK_EQ(portIndex, (OMX_U32)kPortIndexOutput); sp<MetaData> oldOutputFormat = mOutputFormat; initOutputFormat(mSource->getFormat()); @@ -2222,7 +2231,7 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { enablePortAsync(portIndex); status_t err = allocateBuffersOnPort(portIndex); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); } break; } @@ -2233,12 +2242,12 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { CODEC_LOGV("PORT_ENABLED(%ld)", portIndex); CHECK(mState == EXECUTING || mState == RECONFIGURING); - CHECK_EQ(mPortStatus[portIndex], ENABLING); + CHECK_EQ((int)mPortStatus[portIndex], (int)ENABLING); mPortStatus[portIndex] = ENABLED; if (mState == RECONFIGURING) { - CHECK_EQ(portIndex, kPortIndexOutput); + CHECK_EQ(portIndex, (OMX_U32)kPortIndexOutput); setState(EXECUTING); @@ -2253,14 +2262,14 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { CODEC_LOGV("FLUSH_DONE(%ld)", portIndex); - CHECK_EQ(mPortStatus[portIndex], SHUTTING_DOWN); + CHECK_EQ((int)mPortStatus[portIndex], (int)SHUTTING_DOWN); mPortStatus[portIndex] = ENABLED; CHECK_EQ(countBuffersWeOwn(mPortBuffers[portIndex]), mPortBuffers[portIndex].size()); if (mState == RECONFIGURING) { - CHECK_EQ(portIndex, kPortIndexOutput); + CHECK_EQ(portIndex, (OMX_U32)kPortIndexOutput); disablePortAsync(portIndex); } else if (mState == EXECUTING_TO_IDLE) { @@ -2274,7 +2283,7 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { status_t err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); } } else { // We're flushing both ports in preparation for seeking. @@ -2314,11 +2323,11 @@ void OMXCodec::onStateChange(OMX_STATETYPE newState) { status_t err = mOMX->sendCommand( mNode, OMX_CommandStateSet, OMX_StateExecuting); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); setState(IDLE_TO_EXECUTING); } else { - CHECK_EQ(mState, EXECUTING_TO_IDLE); + CHECK_EQ((int)mState, (int)EXECUTING_TO_IDLE); CHECK_EQ( countBuffersWeOwn(mPortBuffers[kPortIndexInput]), @@ -2331,13 +2340,13 @@ void OMXCodec::onStateChange(OMX_STATETYPE newState) { status_t err = mOMX->sendCommand( mNode, OMX_CommandStateSet, OMX_StateLoaded); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); err = freeBuffersOnPort(kPortIndexInput); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); err = freeBuffersOnPort(kPortIndexOutput); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); mPortStatus[kPortIndexInput] = ENABLED; mPortStatus[kPortIndexOutput] = ENABLED; @@ -2349,7 +2358,7 @@ void OMXCodec::onStateChange(OMX_STATETYPE newState) { case OMX_StateExecuting: { - CHECK_EQ(mState, IDLE_TO_EXECUTING); + CHECK_EQ((int)mState, (int)IDLE_TO_EXECUTING); CODEC_LOGV("Now Executing."); @@ -2365,7 +2374,7 @@ void OMXCodec::onStateChange(OMX_STATETYPE newState) { case OMX_StateLoaded: { - CHECK_EQ(mState, IDLE_TO_LOADED); + CHECK_EQ((int)mState, (int)IDLE_TO_LOADED); CODEC_LOGV("Now Loaded."); @@ -2412,7 +2421,7 @@ status_t OMXCodec::freeBuffersOnPort( continue; } - CHECK_EQ(info->mOwnedByComponent, false); + CHECK_EQ((int)info->mOwnedByComponent, (int)false); CODEC_LOGV("freeing buffer %p on port %ld", info->mBuffer, portIndex); @@ -2437,7 +2446,7 @@ status_t OMXCodec::freeBuffer(OMX_U32 portIndex, size_t bufIndex) { status_t err = mOMX->freeBuffer(mNode, portIndex, info->mBuffer); if (err == OK && info->mMediaBuffer != NULL) { - CHECK_EQ(portIndex, kPortIndexOutput); + CHECK_EQ(portIndex, (OMX_U32)kPortIndexOutput); info->mMediaBuffer->setObserver(NULL); // Make sure nobody but us owns this buffer at this point. @@ -2463,8 +2472,8 @@ status_t OMXCodec::freeBuffer(OMX_U32 portIndex, size_t bufIndex) { void OMXCodec::onPortSettingsChanged(OMX_U32 portIndex) { CODEC_LOGV("PORT_SETTINGS_CHANGED(%ld)", portIndex); - CHECK_EQ(mState, EXECUTING); - CHECK_EQ(portIndex, kPortIndexOutput); + CHECK_EQ((int)mState, (int)EXECUTING); + CHECK_EQ(portIndex, (OMX_U32)kPortIndexOutput); setState(RECONFIGURING); if (mQuirks & kNeedsFlushBeforeDisable) { @@ -2484,7 +2493,7 @@ bool OMXCodec::flushPortAsync(OMX_U32 portIndex) { portIndex, countBuffersWeOwn(mPortBuffers[portIndex]), mPortBuffers[portIndex].size()); - CHECK_EQ(mPortStatus[portIndex], ENABLED); + CHECK_EQ((int)mPortStatus[portIndex], (int)ENABLED); mPortStatus[portIndex] = SHUTTING_DOWN; if ((mQuirks & kRequiresFlushCompleteEmulation) @@ -2498,7 +2507,7 @@ bool OMXCodec::flushPortAsync(OMX_U32 portIndex) { status_t err = mOMX->sendCommand(mNode, OMX_CommandFlush, portIndex); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); return true; } @@ -2506,13 +2515,13 @@ bool OMXCodec::flushPortAsync(OMX_U32 portIndex) { void OMXCodec::disablePortAsync(OMX_U32 portIndex) { CHECK(mState == EXECUTING || mState == RECONFIGURING); - CHECK_EQ(mPortStatus[portIndex], ENABLED); + CHECK_EQ((int)mPortStatus[portIndex], (int)ENABLED); mPortStatus[portIndex] = DISABLING; CODEC_LOGV("sending OMX_CommandPortDisable(%ld)", portIndex); status_t err = mOMX->sendCommand(mNode, OMX_CommandPortDisable, portIndex); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); freeBuffersOnPort(portIndex, true); } @@ -2520,17 +2529,17 @@ void OMXCodec::disablePortAsync(OMX_U32 portIndex) { void OMXCodec::enablePortAsync(OMX_U32 portIndex) { CHECK(mState == EXECUTING || mState == RECONFIGURING); - CHECK_EQ(mPortStatus[portIndex], DISABLED); + CHECK_EQ((int)mPortStatus[portIndex], (int)DISABLED); mPortStatus[portIndex] = ENABLING; CODEC_LOGV("sending OMX_CommandPortEnable(%ld)", portIndex); status_t err = mOMX->sendCommand(mNode, OMX_CommandPortEnable, portIndex); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); } void OMXCodec::fillOutputBuffers() { - CHECK_EQ(mState, EXECUTING); + CHECK_EQ((int)mState, (int)EXECUTING); // This is a workaround for some decoders not properly reporting // end-of-output-stream. If we own all input buffers and also own @@ -2566,7 +2575,7 @@ void OMXCodec::drainInputBuffers() { } void OMXCodec::drainInputBuffer(BufferInfo *info) { - CHECK_EQ(info->mOwnedByComponent, false); + CHECK_EQ((int)info->mOwnedByComponent, (int)false); if (mSignalledEOS) { return; @@ -2603,7 +2612,7 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { mNode, info->mBuffer, 0, size, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG, 0); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); info->mOwnedByComponent = true; @@ -2771,7 +2780,7 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { } void OMXCodec::fillOutputBuffer(BufferInfo *info) { - CHECK_EQ(info->mOwnedByComponent, false); + CHECK_EQ((int)info->mOwnedByComponent, (int)false); if (mNoMoreOutputData) { CODEC_LOGV("There is no more output data available, not " @@ -2779,19 +2788,21 @@ void OMXCodec::fillOutputBuffer(BufferInfo *info) { return; } - sp<GraphicBuffer> graphicBuffer = info->mMediaBuffer->graphicBuffer(); - if (graphicBuffer != 0) { - // When using a native buffer we need to lock the buffer before giving - // it to OMX. - CHECK(!info->mOwnedByNativeWindow); - CODEC_LOGV("Calling lockBuffer on %p", info->mBuffer); - int err = mNativeWindow->lockBuffer(mNativeWindow.get(), - graphicBuffer.get()); - if (err != 0) { - CODEC_LOGE("lockBuffer failed w/ error 0x%08x", err); + if (info->mMediaBuffer != NULL) { + sp<GraphicBuffer> graphicBuffer = info->mMediaBuffer->graphicBuffer(); + if (graphicBuffer != 0) { + // When using a native buffer we need to lock the buffer before + // giving it to OMX. + CHECK(!info->mOwnedByNativeWindow); + CODEC_LOGV("Calling lockBuffer on %p", info->mBuffer); + int err = mNativeWindow->lockBuffer(mNativeWindow.get(), + graphicBuffer.get()); + if (err != 0) { + CODEC_LOGE("lockBuffer failed w/ error 0x%08x", err); - setState(ERROR); - return; + setState(ERROR); + return; + } } } @@ -2850,10 +2861,10 @@ void OMXCodec::setRawAudioFormat( def.nPortIndex = portIndex; status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); def.format.audio.eEncoding = OMX_AUDIO_CodingPCM; CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamPortDefinition, - &def, sizeof(def)), OK); + &def, sizeof(def)), (status_t)OK); // pcm param OMX_AUDIO_PARAM_PCMMODETYPE pcmParams; @@ -2863,7 +2874,7 @@ void OMXCodec::setRawAudioFormat( err = mOMX->getParameter( mNode, OMX_IndexParamAudioPcm, &pcmParams, sizeof(pcmParams)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); pcmParams.nChannels = numChannels; pcmParams.eNumData = OMX_NumericalDataSigned; @@ -2884,7 +2895,7 @@ void OMXCodec::setRawAudioFormat( err = mOMX->setParameter( mNode, OMX_IndexParamAudioPcm, &pcmParams, sizeof(pcmParams)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); } static OMX_AUDIO_AMRBANDMODETYPE pickModeFromBitRate(bool isAMRWB, int32_t bps) { @@ -2941,13 +2952,13 @@ void OMXCodec::setAMRFormat(bool isWAMR, int32_t bitRate) { status_t err = mOMX->getParameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF; def.eAMRBandMode = pickModeFromBitRate(isWAMR, bitRate); err = mOMX->setParameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); //////////////////////// @@ -2976,33 +2987,33 @@ void OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t bit status_t err = OMX_ErrorNone; while (OMX_ErrorNone == err) { CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamAudioPortFormat, - &format, sizeof(format)), OK); + &format, sizeof(format)), (status_t)OK); if (format.eEncoding == OMX_AUDIO_CodingAAC) { break; } format.nIndex++; } - CHECK_EQ(OK, err); + CHECK_EQ((status_t)OK, err); CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamAudioPortFormat, - &format, sizeof(format)), OK); + &format, sizeof(format)), (status_t)OK); // port definition OMX_PARAM_PORTDEFINITIONTYPE def; InitOMXParams(&def); def.nPortIndex = kPortIndexOutput; CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamPortDefinition, - &def, sizeof(def)), OK); + &def, sizeof(def)), (status_t)OK); def.format.audio.bFlagErrorConcealment = OMX_TRUE; def.format.audio.eEncoding = OMX_AUDIO_CodingAAC; CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamPortDefinition, - &def, sizeof(def)), OK); + &def, sizeof(def)), (status_t)OK); // profile OMX_AUDIO_PARAM_AACPROFILETYPE profile; InitOMXParams(&profile); profile.nPortIndex = kPortIndexOutput; CHECK_EQ(mOMX->getParameter(mNode, OMX_IndexParamAudioAac, - &profile, sizeof(profile)), OK); + &profile, sizeof(profile)), (status_t)OK); profile.nChannels = numChannels; profile.eChannelMode = (numChannels == 1? OMX_AUDIO_ChannelModeMono: OMX_AUDIO_ChannelModeStereo); @@ -3015,7 +3026,7 @@ void OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t bit profile.eAACProfile = OMX_AUDIO_AACObjectLC; profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4FF; CHECK_EQ(mOMX->setParameter(mNode, OMX_IndexParamAudioAac, - &profile, sizeof(profile)), OK); + &profile, sizeof(profile)), (status_t)OK); } else { OMX_AUDIO_PARAM_AACPROFILETYPE profile; @@ -3024,7 +3035,7 @@ void OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t bit status_t err = mOMX->getParameter( mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); profile.nChannels = numChannels; profile.nSampleRate = sampleRate; @@ -3032,7 +3043,7 @@ void OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t bit err = mOMX->setParameter( mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); } } @@ -3044,10 +3055,10 @@ void OMXCodec::setImageOutputFormat( OMX_INDEXTYPE index; status_t err = mOMX->get_extension_index( mNode, "OMX.TI.JPEG.decode.Config.OutputColorFormat", &index); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); err = mOMX->set_config(mNode, index, &format, sizeof(format)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); #endif OMX_PARAM_PORTDEFINITIONTYPE def; @@ -3056,13 +3067,13 @@ void OMXCodec::setImageOutputFormat( status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); - CHECK_EQ(def.eDomain, OMX_PortDomainImage); + CHECK_EQ((int)def.eDomain, (int)OMX_PortDomainImage); OMX_IMAGE_PORTDEFINITIONTYPE *imageDef = &def.format.image; - CHECK_EQ(imageDef->eCompressionFormat, OMX_IMAGE_CodingUnused); + CHECK_EQ((int)imageDef->eCompressionFormat, (int)OMX_IMAGE_CodingUnused); imageDef->eColorFormat = format; imageDef->nFrameWidth = width; imageDef->nFrameHeight = height; @@ -3105,7 +3116,7 @@ void OMXCodec::setImageOutputFormat( err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); } void OMXCodec::setJPEGInputFormat( @@ -3116,12 +3127,12 @@ void OMXCodec::setJPEGInputFormat( status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); - CHECK_EQ(def.eDomain, OMX_PortDomainImage); + CHECK_EQ((int)def.eDomain, (int)OMX_PortDomainImage); OMX_IMAGE_PORTDEFINITIONTYPE *imageDef = &def.format.image; - CHECK_EQ(imageDef->eCompressionFormat, OMX_IMAGE_CodingJPEG); + CHECK_EQ((int)imageDef->eCompressionFormat, (int)OMX_IMAGE_CodingJPEG); imageDef->nFrameWidth = width; imageDef->nFrameHeight = height; @@ -3130,7 +3141,7 @@ void OMXCodec::setJPEGInputFormat( err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); } void OMXCodec::addCodecSpecificData(const void *data, size_t size) { @@ -3231,7 +3242,7 @@ status_t OMXCodec::stop() { status_t err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); } while (mState != LOADED && mState != ERROR) { @@ -3323,7 +3334,7 @@ status_t OMXCodec::read( mFilledBuffers.clear(); - CHECK_EQ(mState, EXECUTING); + CHECK_EQ((int)mState, (int)EXECUTING); bool emulateInputFlushCompletion = !flushPortAsync(kPortIndexInput); bool emulateOutputFlushCompletion = !flushPortAsync(kPortIndexOutput); @@ -3377,7 +3388,7 @@ void OMXCodec::signalBufferReturned(MediaBuffer *buffer) { BufferInfo *info = &buffers->editItemAt(i); if (info->mMediaBuffer == buffer) { - CHECK_EQ(mPortStatus[kPortIndexOutput], ENABLED); + CHECK_EQ((int)mPortStatus[kPortIndexOutput], (int)ENABLED); if (buffer->graphicBuffer() == 0) { fillOutputBuffer(info); } else { @@ -3629,7 +3640,7 @@ void OMXCodec::dumpPortStatus(OMX_U32 portIndex) { status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); printf("%s Port = {\n", portIndex == kPortIndexInput ? "Input" : "Output"); @@ -3695,7 +3706,7 @@ void OMXCodec::dumpPortStatus(OMX_U32 portIndex) { err = mOMX->getParameter( mNode, OMX_IndexParamAudioPcm, ¶ms, sizeof(params)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); printf(" nSamplingRate = %ld\n", params.nSamplingRate); printf(" nChannels = %ld\n", params.nChannels); @@ -3714,7 +3725,7 @@ void OMXCodec::dumpPortStatus(OMX_U32 portIndex) { err = mOMX->getParameter( mNode, OMX_IndexParamAudioAmr, &amr, sizeof(amr)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); printf(" nChannels = %ld\n", amr.nChannels); printf(" eAMRBandMode = %s\n", @@ -3764,13 +3775,14 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { status_t err = mOMX->getParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); switch (def.eDomain) { case OMX_PortDomainImage: { OMX_IMAGE_PORTDEFINITIONTYPE *imageDef = &def.format.image; - CHECK_EQ(imageDef->eCompressionFormat, OMX_IMAGE_CodingUnused); + CHECK_EQ((int)imageDef->eCompressionFormat, + (int)OMX_IMAGE_CodingUnused); mOutputFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW); mOutputFormat->setInt32(kKeyColorFormat, imageDef->eColorFormat); @@ -3790,11 +3802,11 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { err = mOMX->getParameter( mNode, OMX_IndexParamAudioPcm, ¶ms, sizeof(params)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); - CHECK_EQ(params.eNumData, OMX_NumericalDataSigned); - CHECK_EQ(params.nBitPerSample, 16); - CHECK_EQ(params.ePCMMode, OMX_AUDIO_PCMModeLinear); + CHECK_EQ((int)params.eNumData, (int)OMX_NumericalDataSigned); + CHECK_EQ(params.nBitPerSample, 16u); + CHECK_EQ((int)params.ePCMMode, (int)OMX_AUDIO_PCMModeLinear); int32_t numChannels, sampleRate; inputFormat->findInt32(kKeyChannelCount, &numChannels); @@ -3828,9 +3840,9 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { err = mOMX->getParameter( mNode, OMX_IndexParamAudioAmr, &amr, sizeof(amr)); - CHECK_EQ(err, OK); + CHECK_EQ(err, (status_t)OK); - CHECK_EQ(amr.nChannels, 1); + CHECK_EQ(amr.nChannels, 1u); mOutputFormat->setInt32(kKeyChannelCount, 1); if (amr.eAMRBandMode >= OMX_AUDIO_AMRBandModeNB0 @@ -3885,6 +3897,37 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { mOutputFormat->setInt32(kKeyWidth, video_def->nFrameWidth); mOutputFormat->setInt32(kKeyHeight, video_def->nFrameHeight); mOutputFormat->setInt32(kKeyColorFormat, video_def->eColorFormat); + + if (!mIsEncoder) { + OMX_CONFIG_RECTTYPE rect; + status_t err = + mOMX->getConfig( + mNode, OMX_IndexConfigCommonOutputCrop, + &rect, sizeof(rect)); + + if (err == OK) { + CHECK_GE(rect.nLeft, 0); + CHECK_GE(rect.nTop, 0); + CHECK_GE(rect.nWidth, 0u); + CHECK_GE(rect.nHeight, 0u); + CHECK_LE(rect.nLeft + rect.nWidth - 1, video_def->nFrameWidth); + CHECK_LE(rect.nTop + rect.nHeight - 1, video_def->nFrameHeight); + + mOutputFormat->setRect( + kKeyCropRect, + rect.nLeft, + rect.nTop, + rect.nLeft + rect.nWidth - 1, + rect.nTop + rect.nHeight - 1); + } else { + mOutputFormat->setRect( + kKeyCropRect, + 0, 0, + video_def->nFrameWidth - 1, + video_def->nFrameHeight - 1); + } + } + break; } @@ -3988,7 +4031,7 @@ status_t QueryCodecs( caps->mColorFormats.push(portFormat.eColorFormat); } - CHECK_EQ(omx->freeNode(node), OK); + CHECK_EQ(omx->freeNode(node), (status_t)OK); } } diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp index e922c73..a9163fc 100644 --- a/media/libstagefright/SampleTable.cpp +++ b/media/libstagefright/SampleTable.cpp @@ -419,8 +419,10 @@ status_t SampleTable::findSyncSampleNear( ++left; } + if (left > 0) { + --left; + } - --left; uint32_t x; if (mDataSource->readAt( mSyncSampleOffset + 8 + left * 4, &x, 4) != 4) { diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp index 9b2dec9..763a914 100644 --- a/media/libstagefright/StagefrightMetadataRetriever.cpp +++ b/media/libstagefright/StagefrightMetadataRetriever.cpp @@ -191,17 +191,26 @@ static VideoFrame *extractVideoFrameWithCodecFlags( CHECK(meta->findInt32(kKeyWidth, &width)); CHECK(meta->findInt32(kKeyHeight, &height)); + int32_t crop_left, crop_top, crop_right, crop_bottom; + if (!meta->findRect( + kKeyCropRect, + &crop_left, &crop_top, &crop_right, &crop_bottom)) { + crop_left = crop_top = 0; + crop_right = width - 1; + crop_bottom = height - 1; + } + int32_t rotationAngle; if (!trackMeta->findInt32(kKeyRotation, &rotationAngle)) { rotationAngle = 0; // By default, no rotation } VideoFrame *frame = new VideoFrame; - frame->mWidth = width; - frame->mHeight = height; - frame->mDisplayWidth = width; - frame->mDisplayHeight = height; - frame->mSize = width * height * 2; + frame->mWidth = crop_right - crop_left + 1; + frame->mHeight = crop_bottom - crop_top + 1; + frame->mDisplayWidth = frame->mWidth; + frame->mDisplayHeight = frame->mHeight; + frame->mSize = frame->mWidth * frame->mHeight * 2; frame->mData = new uint8_t[frame->mSize]; frame->mRotationAngle = rotationAngle; @@ -213,10 +222,13 @@ static VideoFrame *extractVideoFrameWithCodecFlags( CHECK(converter.isValid()); converter.convert( - width, height, (const uint8_t *)buffer->data() + buffer->range_offset(), - 0, - frame->mData, width * 2); + width, height, + crop_left, crop_top, crop_right, crop_bottom, + frame->mData, + frame->mWidth, + frame->mHeight, + 0, 0, frame->mWidth - 1, frame->mHeight - 1); buffer->release(); buffer = NULL; @@ -418,5 +430,4 @@ void StagefrightMetadataRetriever::parseMetaData() { } } - } // namespace android diff --git a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp index 868c514..5bbba35 100644 --- a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp +++ b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp @@ -73,6 +73,7 @@ AVCDecoder::AVCDecoder(const sp<MediaSource> &source) CHECK(mSource->getFormat()->findInt32(kKeyHeight, &height)); mFormat->setInt32(kKeyWidth, width); mFormat->setInt32(kKeyHeight, height); + mFormat->setRect(kKeyCropRect, 0, 0, width - 1, height - 1); mFormat->setInt32(kKeyColorFormat, OMX_COLOR_FormatYUV420Planar); mFormat->setCString(kKeyDecoderComponent, "AVCDecoder"); @@ -418,16 +419,32 @@ status_t AVCDecoder::read( crop_top = crop_left = 0; } - int32_t aligned_width = (crop_right - crop_left + 1 + 15) & ~15; - int32_t aligned_height = (crop_bottom - crop_top + 1 + 15) & ~15; + int32_t prevCropLeft, prevCropTop; + int32_t prevCropRight, prevCropBottom; + if (!mFormat->findRect( + kKeyCropRect, + &prevCropLeft, &prevCropTop, + &prevCropRight, &prevCropBottom)) { + prevCropLeft = prevCropTop = 0; + prevCropRight = width - 1; + prevCropBottom = height - 1; + } int32_t oldWidth, oldHeight; CHECK(mFormat->findInt32(kKeyWidth, &oldWidth)); CHECK(mFormat->findInt32(kKeyHeight, &oldHeight)); - if (oldWidth != aligned_width || oldHeight != aligned_height) { - mFormat->setInt32(kKeyWidth, aligned_width); - mFormat->setInt32(kKeyHeight, aligned_height); + if (oldWidth != width || oldHeight != height + || prevCropLeft != crop_left + || prevCropTop != crop_top + || prevCropRight != crop_right + || prevCropBottom != crop_bottom) { + mFormat->setRect( + kKeyCropRect, + crop_left, crop_top, crop_right, crop_bottom); + + mFormat->setInt32(kKeyWidth, width); + mFormat->setInt32(kKeyHeight, height); err = INFO_FORMAT_CHANGED; } else { diff --git a/media/libstagefright/colorconversion/Android.mk b/media/libstagefright/colorconversion/Android.mk index ef2dba0..62ba40f 100644 --- a/media/libstagefright/colorconversion/Android.mk +++ b/media/libstagefright/colorconversion/Android.mk @@ -9,20 +9,6 @@ LOCAL_C_INCLUDES := \ $(TOP)/frameworks/base/include/media/stagefright/openmax \ $(TOP)/hardware/msm7k -LOCAL_SHARED_LIBRARIES := \ - libbinder \ - libmedia \ - libutils \ - libui \ - libcutils \ - libsurfaceflinger_client\ - libcamera_client - -# ifeq ($(TARGET_BOARD_PLATFORM),msm7k) -ifeq ($(TARGET_PRODUCT),passion) - LOCAL_CFLAGS += -DHAS_YCBCR420_SP_ADRENO -endif - LOCAL_MODULE:= libstagefright_color_conversion -include $(BUILD_SHARED_LIBRARY) +include $(BUILD_STATIC_LIBRARY) diff --git a/media/libstagefright/colorconversion/ColorConverter.cpp b/media/libstagefright/colorconversion/ColorConverter.cpp index 5b16997..600f040 100644 --- a/media/libstagefright/colorconversion/ColorConverter.cpp +++ b/media/libstagefright/colorconversion/ColorConverter.cpp @@ -50,31 +50,64 @@ bool ColorConverter::isValid() const { } } -void ColorConverter::convert( +ColorConverter::BitmapParams::BitmapParams( + void *bits, size_t width, size_t height, - const void *srcBits, size_t srcSkip, - void *dstBits, size_t dstSkip) { + size_t cropLeft, size_t cropTop, + size_t cropRight, size_t cropBottom) + : mBits(bits), + mWidth(width), + mHeight(height), + mCropLeft(cropLeft), + mCropTop(cropTop), + mCropRight(cropRight), + mCropBottom(cropBottom) { +} + +size_t ColorConverter::BitmapParams::cropWidth() const { + return mCropRight - mCropLeft + 1; +} + +size_t ColorConverter::BitmapParams::cropHeight() const { + return mCropBottom - mCropTop + 1; +} + +void ColorConverter::convert( + const void *srcBits, + size_t srcWidth, size_t srcHeight, + size_t srcCropLeft, size_t srcCropTop, + size_t srcCropRight, size_t srcCropBottom, + void *dstBits, + size_t dstWidth, size_t dstHeight, + size_t dstCropLeft, size_t dstCropTop, + size_t dstCropRight, size_t dstCropBottom) { CHECK_EQ(mDstFormat, OMX_COLOR_Format16bitRGB565); + BitmapParams src( + const_cast<void *>(srcBits), + srcWidth, srcHeight, + srcCropLeft, srcCropTop, srcCropRight, srcCropBottom); + + BitmapParams dst( + dstBits, + dstWidth, dstHeight, + dstCropLeft, dstCropTop, dstCropRight, dstCropBottom); + switch (mSrcFormat) { case OMX_COLOR_FormatYUV420Planar: - convertYUV420Planar( - width, height, srcBits, srcSkip, dstBits, dstSkip); + convertYUV420Planar(src, dst); break; case OMX_COLOR_FormatCbYCrY: - convertCbYCrY( - width, height, srcBits, srcSkip, dstBits, dstSkip); + convertCbYCrY(src, dst); break; case OMX_QCOM_COLOR_FormatYVU420SemiPlanar: - convertQCOMYUV420SemiPlanar( - width, height, srcBits, srcSkip, dstBits, dstSkip); + convertQCOMYUV420SemiPlanar(src, dst); break; case OMX_COLOR_FormatYUV420SemiPlanar: - convertYUV420SemiPlanar( - width, height, srcBits, srcSkip, dstBits, dstSkip); + convertYUV420SemiPlanar(src, dst); break; default: @@ -86,25 +119,27 @@ void ColorConverter::convert( } void ColorConverter::convertCbYCrY( - size_t width, size_t height, - const void *srcBits, size_t srcSkip, - void *dstBits, size_t dstSkip) { - CHECK_EQ(srcSkip, 0); // Doesn't really make sense for YUV formats. - CHECK(dstSkip >= width * 2); - CHECK((dstSkip & 3) == 0); + const BitmapParams &src, const BitmapParams &dst) { + // XXX Untested uint8_t *kAdjustedClip = initClip(); - uint32_t *dst_ptr = (uint32_t *)dstBits; + CHECK((src.mCropLeft & 1) == 0); + CHECK_EQ(src.cropWidth(), dst.cropWidth()); + CHECK_EQ(src.cropHeight(), dst.cropHeight()); + + uint32_t *dst_ptr = (uint32_t *)dst.mBits + + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2; - const uint8_t *src = (const uint8_t *)srcBits; + const uint8_t *src_ptr = (const uint8_t *)src.mBits + + (src.mCropTop * dst.mWidth + src.mCropLeft) * 2; - for (size_t y = 0; y < height; ++y) { - for (size_t x = 0; x < width; x += 2) { - signed y1 = (signed)src[2 * x + 1] - 16; - signed y2 = (signed)src[2 * x + 3] - 16; - signed u = (signed)src[2 * x] - 128; - signed v = (signed)src[2 * x + 2] - 128; + for (size_t y = 0; y < src.cropHeight(); ++y) { + for (size_t x = 0; x < src.cropWidth(); x += 2) { + signed y1 = (signed)src_ptr[2 * x + 1] - 16; + signed y2 = (signed)src_ptr[2 * x + 3] - 16; + signed u = (signed)src_ptr[2 * x] - 128; + signed v = (signed)src_ptr[2 * x + 2] - 128; signed u_b = u * 517; signed u_g = -u * 100; @@ -134,32 +169,35 @@ void ColorConverter::convertCbYCrY( dst_ptr[x / 2] = (rgb2 << 16) | rgb1; } - src += width * 2; - dst_ptr += dstSkip / 4; + src_ptr += src.mWidth * 2; + dst_ptr += dst.mWidth / 2; } } void ColorConverter::convertYUV420Planar( - size_t width, size_t height, - const void *srcBits, size_t srcSkip, - void *dstBits, size_t dstSkip) { - CHECK_EQ(srcSkip, 0); // Doesn't really make sense for YUV formats. - CHECK(dstSkip >= width * 2); - CHECK((dstSkip & 3) == 0); - + const BitmapParams &src, const BitmapParams &dst) { uint8_t *kAdjustedClip = initClip(); - uint32_t *dst_ptr = (uint32_t *)dstBits; - const uint8_t *src_y = (const uint8_t *)srcBits; + CHECK((dst.mWidth & 3) == 0); + CHECK((src.mCropLeft & 1) == 0); + CHECK_EQ(src.cropWidth(), dst.cropWidth()); + CHECK_EQ(src.cropHeight(), dst.cropHeight()); + + uint32_t *dst_ptr = (uint32_t *)dst.mBits + + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2; + + const uint8_t *src_y = + (const uint8_t *)src.mBits + src.mCropTop * src.mWidth + src.mCropLeft; const uint8_t *src_u = - (const uint8_t *)src_y + width * height; + (const uint8_t *)src_y + src.mWidth * src.mHeight + + src.mCropTop * (src.mWidth / 2) + src.mCropLeft / 2; const uint8_t *src_v = - (const uint8_t *)src_u + (width / 2) * (height / 2); + src_u + (src.mWidth / 2) * (src.mHeight / 2); - for (size_t y = 0; y < height; ++y) { - for (size_t x = 0; x < width; x += 2) { + for (size_t y = 0; y < src.cropHeight(); ++y) { + for (size_t x = 0; x < src.cropWidth(); x += 2) { // B = 1.164 * (Y - 16) + 2.018 * (U - 128) // G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128) // R = 1.164 * (Y - 16) + 1.596 * (V - 128) @@ -212,35 +250,38 @@ void ColorConverter::convertYUV420Planar( dst_ptr[x / 2] = (rgb2 << 16) | rgb1; } - src_y += width; + src_y += src.mWidth; if (y & 1) { - src_u += width / 2; - src_v += width / 2; + src_u += src.mWidth / 2; + src_v += src.mWidth / 2; } - dst_ptr += dstSkip / 4; + dst_ptr += dst.mWidth / 2; } } void ColorConverter::convertQCOMYUV420SemiPlanar( - size_t width, size_t height, - const void *srcBits, size_t srcSkip, - void *dstBits, size_t dstSkip) { - CHECK_EQ(srcSkip, 0); // Doesn't really make sense for YUV formats. - CHECK(dstSkip >= width * 2); - CHECK((dstSkip & 3) == 0); - + const BitmapParams &src, const BitmapParams &dst) { uint8_t *kAdjustedClip = initClip(); - uint32_t *dst_ptr = (uint32_t *)dstBits; - const uint8_t *src_y = (const uint8_t *)srcBits; + CHECK((dst.mWidth & 3) == 0); + CHECK((src.mCropLeft & 1) == 0); + CHECK_EQ(src.cropWidth(), dst.cropWidth()); + CHECK_EQ(src.cropHeight(), dst.cropHeight()); + + uint32_t *dst_ptr = (uint32_t *)dst.mBits + + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2; + + const uint8_t *src_y = + (const uint8_t *)src.mBits + src.mCropTop * src.mWidth + src.mCropLeft; const uint8_t *src_u = - (const uint8_t *)src_y + width * height; + (const uint8_t *)src_y + src.mWidth * src.mHeight + + src.mCropTop * src.mWidth + src.mCropLeft; - for (size_t y = 0; y < height; ++y) { - for (size_t x = 0; x < width; x += 2) { + for (size_t y = 0; y < src.cropHeight(); ++y) { + for (size_t x = 0; x < src.cropWidth(); x += 2) { signed y1 = (signed)src_y[x] - 16; signed y2 = (signed)src_y[x + 1] - 16; @@ -275,34 +316,39 @@ void ColorConverter::convertQCOMYUV420SemiPlanar( dst_ptr[x / 2] = (rgb2 << 16) | rgb1; } - src_y += width; + src_y += src.mWidth; if (y & 1) { - src_u += width; + src_u += src.mWidth; } - dst_ptr += dstSkip / 4; + dst_ptr += dst.mWidth / 2; } } void ColorConverter::convertYUV420SemiPlanar( - size_t width, size_t height, - const void *srcBits, size_t srcSkip, - void *dstBits, size_t dstSkip) { - CHECK_EQ(srcSkip, 0); // Doesn't really make sense for YUV formats. - CHECK(dstSkip >= width * 2); - CHECK((dstSkip & 3) == 0); + const BitmapParams &src, const BitmapParams &dst) { + // XXX Untested uint8_t *kAdjustedClip = initClip(); - uint32_t *dst_ptr = (uint32_t *)dstBits; - const uint8_t *src_y = (const uint8_t *)srcBits; + CHECK((dst.mWidth & 3) == 0); + CHECK((src.mCropLeft & 1) == 0); + CHECK_EQ(src.cropWidth(), dst.cropWidth()); + CHECK_EQ(src.cropHeight(), dst.cropHeight()); + + uint32_t *dst_ptr = (uint32_t *)dst.mBits + + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2; + + const uint8_t *src_y = + (const uint8_t *)src.mBits + src.mCropTop * src.mWidth + src.mCropLeft; const uint8_t *src_u = - (const uint8_t *)src_y + width * height; + (const uint8_t *)src_y + src.mWidth * src.mHeight + + src.mCropTop * src.mWidth + src.mCropLeft; - for (size_t y = 0; y < height; ++y) { - for (size_t x = 0; x < width; x += 2) { + for (size_t y = 0; y < src.cropHeight(); ++y) { + for (size_t x = 0; x < src.cropWidth(); x += 2) { signed y1 = (signed)src_y[x] - 16; signed y2 = (signed)src_y[x + 1] - 16; @@ -337,13 +383,13 @@ void ColorConverter::convertYUV420SemiPlanar( dst_ptr[x / 2] = (rgb2 << 16) | rgb1; } - src_y += width; + src_y += src.mWidth; if (y & 1) { - src_u += width; + src_u += src.mWidth; } - dst_ptr += dstSkip / 4; + dst_ptr += dst.mWidth / 2; } } diff --git a/media/libstagefright/colorconversion/SoftwareRenderer.cpp b/media/libstagefright/colorconversion/SoftwareRenderer.cpp index acbea05..70408d7 100644 --- a/media/libstagefright/colorconversion/SoftwareRenderer.cpp +++ b/media/libstagefright/colorconversion/SoftwareRenderer.cpp @@ -21,55 +21,41 @@ #include <binder/MemoryHeapBase.h> #include <binder/MemoryHeapPmem.h> -#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MetaData.h> #include <surfaceflinger/Surface.h> #include <ui/android_native_buffer.h> #include <ui/GraphicBufferMapper.h> -// XXX: Temporary hack to allow referencing the _ADRENO pixel format here. -#include <libgralloc-qsd8k/gralloc_priv.h> - namespace android { SoftwareRenderer::SoftwareRenderer( - OMX_COLOR_FORMATTYPE colorFormat, - const sp<Surface> &surface, - size_t displayWidth, size_t displayHeight, - size_t decodedWidth, size_t decodedHeight, - int32_t rotationDegrees) - : mColorFormat(colorFormat), - mConverter(NULL), + const sp<Surface> &surface, const sp<MetaData> &meta) + : mConverter(NULL), mYUVMode(None), - mSurface(surface), - mDisplayWidth(displayWidth), - mDisplayHeight(displayHeight), - mDecodedWidth(decodedWidth), - mDecodedHeight(decodedHeight) { - LOGI("input format = %d", mColorFormat); - LOGI("display = %d x %d, decoded = %d x %d", - mDisplayWidth, mDisplayHeight, mDecodedWidth, mDecodedHeight); - - mDecodedWidth = mDisplayWidth; - mDecodedHeight = mDisplayHeight; + mSurface(surface) { + int32_t tmp; + CHECK(meta->findInt32(kKeyColorFormat, &tmp)); + mColorFormat = (OMX_COLOR_FORMATTYPE)tmp; + + CHECK(meta->findInt32(kKeyWidth, &mWidth)); + CHECK(meta->findInt32(kKeyHeight, &mHeight)); + + if (!meta->findRect( + kKeyCropRect, + &mCropLeft, &mCropTop, &mCropRight, &mCropBottom)) { + mCropLeft = mCropTop = 0; + mCropRight = mWidth - 1; + mCropBottom = mHeight - 1; + } + + int32_t rotationDegrees; + if (!meta->findInt32(kKeyRotation, &rotationDegrees)) { + rotationDegrees = 0; + } int halFormat; switch (mColorFormat) { -#if HAS_YCBCR420_SP_ADRENO - case OMX_COLOR_FormatYUV420Planar: - { - halFormat = HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO; - mYUVMode = YUV420ToYUV420sp; - break; - } - - case 0x7fa30c00: - { - halFormat = HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO; - mYUVMode = YUV420spToYUV420sp; - break; - } -#endif - default: halFormat = HAL_PIXEL_FORMAT_RGB_565; @@ -80,8 +66,8 @@ SoftwareRenderer::SoftwareRenderer( } CHECK(mSurface.get() != NULL); - CHECK(mDecodedWidth > 0); - CHECK(mDecodedHeight > 0); + CHECK(mWidth > 0); + CHECK(mHeight > 0); CHECK(mConverter == NULL || mConverter->isValid()); CHECK_EQ(0, @@ -94,7 +80,9 @@ SoftwareRenderer::SoftwareRenderer( // Width must be multiple of 32??? CHECK_EQ(0, native_window_set_buffers_geometry( - mSurface.get(), mDecodedWidth, mDecodedHeight, + mSurface.get(), + mCropRight - mCropLeft + 1, + mCropBottom - mCropTop + 1, halFormat)); uint32_t transform; @@ -117,10 +105,6 @@ SoftwareRenderer::~SoftwareRenderer() { mConverter = NULL; } -static inline size_t ALIGN(size_t x, size_t alignment) { - return (x + alignment - 1) & ~(alignment - 1); -} - void SoftwareRenderer::render( const void *data, size_t size, void *platformPrivate) { android_native_buffer_t *buf; @@ -134,7 +118,7 @@ void SoftwareRenderer::render( GraphicBufferMapper &mapper = GraphicBufferMapper::get(); - Rect bounds(mDecodedWidth, mDecodedHeight); + Rect bounds(mWidth, mHeight); void *dst; CHECK_EQ(0, mapper.lock( @@ -142,69 +126,16 @@ void SoftwareRenderer::render( if (mConverter) { mConverter->convert( - mDecodedWidth, mDecodedHeight, - data, 0, dst, buf->stride * 2); - } else if (mYUVMode == YUV420spToYUV420sp) { - // Input and output are both YUV420sp, but the alignment requirements - // are different. - size_t srcYStride = mDecodedWidth; - const uint8_t *srcY = (const uint8_t *)data; - uint8_t *dstY = (uint8_t *)dst; - for (size_t i = 0; i < mDecodedHeight; ++i) { - memcpy(dstY, srcY, mDecodedWidth); - srcY += srcYStride; - dstY += buf->stride; - } - - size_t srcUVStride = (mDecodedWidth + 1) & ~1; - size_t dstUVStride = ALIGN(mDecodedWidth / 2, 32) * 2; - - const uint8_t *srcUV = (const uint8_t *)data - + mDecodedHeight * mDecodedWidth; - - size_t dstUVOffset = ALIGN(ALIGN(mDecodedHeight, 32) * buf->stride, 4096); - uint8_t *dstUV = (uint8_t *)dst + dstUVOffset; - - for (size_t i = 0; i < (mDecodedHeight + 1) / 2; ++i) { - memcpy(dstUV, srcUV, (mDecodedWidth + 1) & ~1); - srcUV += srcUVStride; - dstUV += dstUVStride; - } - } else if (mYUVMode == YUV420ToYUV420sp) { - // Input is YUV420 planar, output is YUV420sp, adhere to proper - // alignment requirements. - size_t srcYStride = mDecodedWidth; - const uint8_t *srcY = (const uint8_t *)data; - uint8_t *dstY = (uint8_t *)dst; - for (size_t i = 0; i < mDecodedHeight; ++i) { - memcpy(dstY, srcY, mDecodedWidth); - srcY += srcYStride; - dstY += buf->stride; - } - - size_t srcUVStride = (mDecodedWidth + 1) / 2; - size_t dstUVStride = ALIGN(mDecodedWidth / 2, 32) * 2; - - const uint8_t *srcU = (const uint8_t *)data - + mDecodedHeight * mDecodedWidth; - - const uint8_t *srcV = - srcU + ((mDecodedWidth + 1) / 2) * ((mDecodedHeight + 1) / 2); - - size_t dstUVOffset = ALIGN(ALIGN(mDecodedHeight, 32) * buf->stride, 4096); - uint8_t *dstUV = (uint8_t *)dst + dstUVOffset; - - for (size_t i = 0; i < (mDecodedHeight + 1) / 2; ++i) { - for (size_t j = 0; j < (mDecodedWidth + 1) / 2; ++j) { - dstUV[2 * j + 1] = srcU[j]; - dstUV[2 * j] = srcV[j]; - } - srcU += srcUVStride; - srcV += srcUVStride; - dstUV += dstUVStride; - } + data, + mWidth, mHeight, + mCropLeft, mCropTop, mCropRight, mCropBottom, + dst, + buf->stride, buf->height, + 0, 0, + mCropRight - mCropLeft, + mCropBottom - mCropTop); } else { - memcpy(dst, data, size); + TRESPASS(); } CHECK_EQ(0, mapper.unlock(buf->handle)); diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index 4e63b7a..e33f467 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -88,11 +88,6 @@ struct AwesomePlayer { status_t seekTo(int64_t timeUs); - status_t getVideoDimensions(int32_t *width, int32_t *height) const; - - status_t suspend(); - status_t resume(); - // This is a mask of MediaExtractor::Flags. uint32_t flags() const; @@ -153,7 +148,6 @@ private: uint32_t mFlags; uint32_t mExtractorFlags; - int32_t mVideoWidth, mVideoHeight; int64_t mTimeSourceDeltaUs; int64_t mVideoTimeUs; @@ -187,7 +181,6 @@ private: void postCheckAudioStatusEvent_l(); status_t play_l(); - MediaBuffer *mLastVideoBuffer; MediaBuffer *mVideoBuffer; sp<NuHTTPDataSource> mConnectingDataSource; @@ -198,32 +191,6 @@ private: sp<ARTPSession> mRTPSession; sp<UDPPusher> mRTPPusher, mRTCPPusher; - struct SuspensionState { - String8 mUri; - KeyedVector<String8, String8> mUriHeaders; - sp<DataSource> mFileSource; - - uint32_t mFlags; - int64_t mPositionUs; - - void *mLastVideoFrame; - size_t mLastVideoFrameSize; - int32_t mColorFormat; - int32_t mVideoWidth, mVideoHeight; - int32_t mDecodedWidth, mDecodedHeight; - - SuspensionState() - : mLastVideoFrame(NULL) { - } - - ~SuspensionState() { - if (mLastVideoFrame) { - free(mLastVideoFrame); - mLastVideoFrame = NULL; - } - } - } *mSuspensionState; - DrmManagerClient *mDrmManagerClient; DecryptHandle *mDecryptHandle; diff --git a/media/libstagefright/include/SoftwareRenderer.h b/media/libstagefright/include/SoftwareRenderer.h index 9cafc68..90d3fe1 100644 --- a/media/libstagefright/include/SoftwareRenderer.h +++ b/media/libstagefright/include/SoftwareRenderer.h @@ -23,16 +23,13 @@ namespace android { +struct MetaData; class Surface; class SoftwareRenderer { public: SoftwareRenderer( - OMX_COLOR_FORMATTYPE colorFormat, - const sp<Surface> &surface, - size_t displayWidth, size_t displayHeight, - size_t decodedWidth, size_t decodedHeight, - int32_t rotationDegrees); + const sp<Surface> &surface, const sp<MetaData> &meta); ~SoftwareRenderer(); @@ -42,16 +39,14 @@ public: private: enum YUVMode { None, - YUV420ToYUV420sp, - YUV420spToYUV420sp, }; OMX_COLOR_FORMATTYPE mColorFormat; ColorConverter *mConverter; YUVMode mYUVMode; sp<Surface> mSurface; - size_t mDisplayWidth, mDisplayHeight; - size_t mDecodedWidth, mDecodedHeight; + int32_t mWidth, mHeight; + int32_t mCropLeft, mCropTop, mCropRight, mCropBottom; SoftwareRenderer(const SoftwareRenderer &); SoftwareRenderer &operator=(const SoftwareRenderer &); diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk index ead1675..6e069c8 100644 --- a/media/libstagefright/omx/Android.mk +++ b/media/libstagefright/omx/Android.mk @@ -31,7 +31,6 @@ LOCAL_SHARED_LIBRARIES := \ libutils \ libui \ libcutils \ - libstagefright_color_conversion ifneq ($(BUILD_WITHOUT_PV),true) LOCAL_SHARED_LIBRARIES += \ diff --git a/media/mtp/Android.mk b/media/mtp/Android.mk index 7502f6e..b7e1a2a 100644 --- a/media/mtp/Android.mk +++ b/media/mtp/Android.mk @@ -22,7 +22,6 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ MtpClient.cpp \ - MtpCursor.cpp \ MtpDataPacket.cpp \ MtpDebug.cpp \ MtpDevice.cpp \ @@ -38,6 +37,7 @@ LOCAL_SRC_FILES:= \ MtpStringBuffer.cpp \ MtpStorage.cpp \ MtpUtils.cpp \ + PtpCursor.cpp \ LOCAL_MODULE:= libmtp @@ -47,32 +47,3 @@ include $(BUILD_STATIC_LIBRARY) endif -ifeq ($(HOST_OS),linux) - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - MtpClient.cpp \ - MtpCursor.cpp \ - MtpDataPacket.cpp \ - MtpDebug.cpp \ - MtpDevice.cpp \ - MtpEventPacket.cpp \ - MtpDeviceInfo.cpp \ - MtpObjectInfo.cpp \ - MtpPacket.cpp \ - MtpProperty.cpp \ - MtpRequestPacket.cpp \ - MtpResponsePacket.cpp \ - MtpStorageInfo.cpp \ - MtpStringBuffer.cpp \ - MtpStorage.cpp \ - MtpUtils.cpp \ - -LOCAL_MODULE:= libmtp - -LOCAL_CFLAGS := -DMTP_HOST - -include $(BUILD_HOST_STATIC_LIBRARY) - -endif diff --git a/media/mtp/MtpPacket.cpp b/media/mtp/MtpPacket.cpp index 42bf8ba..bd6196f 100644 --- a/media/mtp/MtpPacket.cpp +++ b/media/mtp/MtpPacket.cpp @@ -123,7 +123,7 @@ void MtpPacket::setTransactionID(MtpTransactionID id) { uint32_t MtpPacket::getParameter(int index) const { if (index < 1 || index > 5) { - LOGE("index %d out of range in MtpRequestPacket::getParameter", index); + LOGE("index %d out of range in MtpPacket::getParameter", index); return 0; } return getUInt32(MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t)); @@ -131,7 +131,7 @@ uint32_t MtpPacket::getParameter(int index) const { void MtpPacket::setParameter(int index, uint32_t value) { if (index < 1 || index > 5) { - LOGE("index %d out of range in MtpResponsePacket::setParameter", index); + LOGE("index %d out of range in MtpPacket::setParameter", index); return; } int offset = MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t); diff --git a/media/mtp/MtpProperty.cpp b/media/mtp/MtpProperty.cpp index 86889c3..42945f5 100644 --- a/media/mtp/MtpProperty.cpp +++ b/media/mtp/MtpProperty.cpp @@ -312,6 +312,10 @@ void MtpProperty::setFormEnum(const int* values, int count) { } } +void MtpProperty::setFormDateTime() { + mFormFlag = kFormDateTime; +} + void MtpProperty::print() { LOGV("MtpProperty %04X\n", mCode); LOGV(" type %04X\n", mType); diff --git a/media/mtp/MtpProperty.h b/media/mtp/MtpProperty.h index c12399c..f783a87 100644 --- a/media/mtp/MtpProperty.h +++ b/media/mtp/MtpProperty.h @@ -58,6 +58,7 @@ public: kFormNone = 0, kFormRange = 1, kFormEnum = 2, + kFormDateTime = 3, }; uint32_t mGroupCode; @@ -90,6 +91,7 @@ public: void setFormRange(int min, int max, int step); void setFormEnum(const int* values, int count); + void setFormDateTime(); void print(); diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp index ca13636..c3755f3 100644 --- a/media/mtp/MtpServer.cpp +++ b/media/mtp/MtpServer.cpp @@ -66,7 +66,7 @@ static const MtpOperationCode kSupportedOperationCodes[] = { // MTP_OPERATION_TERMINATE_OPEN_CAPTURE, // MTP_OPERATION_MOVE_OBJECT, // MTP_OPERATION_COPY_OBJECT, -// MTP_OPERATION_GET_PARTIAL_OBJECT, + MTP_OPERATION_GET_PARTIAL_OBJECT, // MTP_OPERATION_INITIATE_OPEN_CAPTURE, MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED, MTP_OPERATION_GET_OBJECT_PROP_DESC, @@ -289,6 +289,9 @@ bool MtpServer::handleRequest() { case MTP_OPERATION_GET_OBJECT: response = doGetObject(); break; + case MTP_OPERATION_GET_PARTIAL_OBJECT: + response = doGetPartialObject(); + break; case MTP_OPERATION_SEND_OBJECT_INFO: response = doSendObjectInfo(); break; @@ -536,7 +539,7 @@ MtpResponseCode MtpServer::doGetObjectPropList() { MtpObjectFormat format = mRequest.getParameter(2); MtpDeviceProperty property = mRequest.getParameter(3); int groupCode = mRequest.getParameter(4); - int depth = mRequest.getParameter(4); + int depth = mRequest.getParameter(5); LOGD("GetObjectPropList %d format: %s property: %s group: %d depth: %d\n", handle, MtpDebug::getFormatCodeName(format), MtpDebug::getObjectPropCodeName(property), groupCode, depth); @@ -583,6 +586,45 @@ MtpResponseCode MtpServer::doGetObject() { return MTP_RESPONSE_OK; } +MtpResponseCode MtpServer::doGetPartialObject() { + MtpObjectHandle handle = mRequest.getParameter(1); + uint32_t offset = mRequest.getParameter(2); + uint32_t length = mRequest.getParameter(3); + MtpString pathBuf; + int64_t fileLength; + int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength); + if (result != MTP_RESPONSE_OK) + return result; + if (offset + length > fileLength) + length = fileLength - offset; + + const char* filePath = (const char *)pathBuf; + mtp_file_range mfr; + mfr.fd = open(filePath, O_RDONLY); + if (mfr.fd < 0) { + return MTP_RESPONSE_GENERAL_ERROR; + } + mfr.offset = offset; + mfr.length = length; + mResponse.setParameter(1, length); + + // send data header + mData.setOperationCode(mRequest.getOperationCode()); + mData.setTransactionID(mRequest.getTransactionID()); + mData.writeDataHeader(mFD, length + MTP_CONTAINER_HEADER_SIZE); + + // then transfer the file + int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr); + close(mfr.fd); + if (ret < 0) { + if (errno == ECANCELED) + return MTP_RESPONSE_TRANSACTION_CANCELLED; + else + return MTP_RESPONSE_GENERAL_ERROR; + } + return MTP_RESPONSE_OK; +} + MtpResponseCode MtpServer::doSendObjectInfo() { MtpString path; MtpStorageID storageID = mRequest.getParameter(1); diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h index e65ddb0..5aee4ea 100644 --- a/media/mtp/MtpServer.h +++ b/media/mtp/MtpServer.h @@ -96,6 +96,7 @@ private: MtpResponseCode doGetObjectPropList(); MtpResponseCode doGetObjectInfo(); MtpResponseCode doGetObject(); + MtpResponseCode doGetPartialObject(); MtpResponseCode doSendObjectInfo(); MtpResponseCode doSendObject(); MtpResponseCode doDeleteObject(); diff --git a/media/mtp/MtpCursor.cpp b/media/mtp/PtpCursor.cpp index 35d90dc..7294cef 100644 --- a/media/mtp/MtpCursor.cpp +++ b/media/mtp/PtpCursor.cpp @@ -14,11 +14,11 @@ * limitations under the License. */ -#define LOG_TAG "MtpCursor" +#define LOG_TAG "PtpCursor" #include "MtpDebug.h" #include "MtpClient.h" -#include "MtpCursor.h" +#include "PtpCursor.h" #include "MtpDevice.h" #include "MtpDeviceInfo.h" #include "MtpObjectInfo.h" @@ -30,19 +30,19 @@ namespace android { /* Device Column IDs */ -/* These must match the values in MtpCursor.java */ +/* These must match the values in PtpCursor.java */ #define DEVICE_ROW_ID 1 #define DEVICE_MANUFACTURER 2 #define DEVICE_MODEL 3 /* Storage Column IDs */ -/* These must match the values in MtpCursor.java */ +/* These must match the values in PtpCursor.java */ #define STORAGE_ROW_ID 101 #define STORAGE_IDENTIFIER 102 #define STORAGE_DESCRIPTION 103 /* Object Column IDs */ -/* These must match the values in MtpCursor.java */ +/* These must match the values in PtpCursor.java */ #define OBJECT_ROW_ID 201 #define OBJECT_STORAGE_ID 202 #define OBJECT_FORMAT 203 @@ -65,7 +65,7 @@ namespace android { #define OBJECT_KEYWORDS 220 #define OBJECT_THUMB 221 -MtpCursor::MtpCursor(MtpClient* client, int queryType, int deviceID, +PtpCursor::PtpCursor(MtpClient* client, int queryType, int deviceID, MtpStorageID storageID, MtpObjectHandle objectID, int columnCount, int* columns) : mClient(client), @@ -82,12 +82,12 @@ MtpCursor::MtpCursor(MtpClient* client, int queryType, int deviceID, } } -MtpCursor::~MtpCursor() { +PtpCursor::~PtpCursor() { delete[] mColumns; } -int MtpCursor::fillWindow(CursorWindow* window, int startPos) { - LOGD("MtpCursor::fillWindow mQueryType: %d\n", mQueryType); +int PtpCursor::fillWindow(CursorWindow* window, int startPos) { + LOGD("PtpCursor::fillWindow mQueryType: %d\n", mQueryType); switch (mQueryType) { case DEVICE: @@ -107,12 +107,12 @@ int MtpCursor::fillWindow(CursorWindow* window, int startPos) { case OBJECT_CHILDREN: return fillObjects(window, mQbjectID, startPos); default: - LOGE("MtpCursor::fillWindow: unknown query type %d\n", mQueryType); + LOGE("PtpCursor::fillWindow: unknown query type %d\n", mQueryType); return 0; } } -int MtpCursor::fillDevices(CursorWindow* window, int startPos) { +int PtpCursor::fillDevices(CursorWindow* window, int startPos) { int count = 0; MtpDeviceList& deviceList = mClient->getDeviceList(); for (int i = 0; i < deviceList.size(); i++) { @@ -127,7 +127,7 @@ int MtpCursor::fillDevices(CursorWindow* window, int startPos) { return count; } -int MtpCursor::fillDevice(CursorWindow* window, int startPos) { +int PtpCursor::fillDevice(CursorWindow* window, int startPos) { MtpDevice* device = mClient->getDevice(mDeviceID); if (device && fillDevice(window, device, startPos)) return 1; @@ -135,7 +135,7 @@ int MtpCursor::fillDevice(CursorWindow* window, int startPos) { return 0; } -int MtpCursor::fillStorages(CursorWindow* window, int startPos) { +int PtpCursor::fillStorages(CursorWindow* window, int startPos) { int count = 0; MtpDevice* device = mClient->getDevice(mDeviceID); if (!device) @@ -157,7 +157,7 @@ int MtpCursor::fillStorages(CursorWindow* window, int startPos) { return count; } -int MtpCursor::fillStorage(CursorWindow* window, int startPos) { +int PtpCursor::fillStorage(CursorWindow* window, int startPos) { MtpDevice* device = mClient->getDevice(mDeviceID); if (device && fillStorage(window, device, mStorageID, startPos)) return 1; @@ -165,7 +165,7 @@ int MtpCursor::fillStorage(CursorWindow* window, int startPos) { return 0; } -int MtpCursor::fillObjects(CursorWindow* window, int parent, int startPos) { +int PtpCursor::fillObjects(CursorWindow* window, int parent, int startPos) { int count = 0; MtpDevice* device = mClient->getDevice(mDeviceID); if (!device) @@ -187,7 +187,7 @@ int MtpCursor::fillObjects(CursorWindow* window, int parent, int startPos) { return count; } -int MtpCursor::fillObject(CursorWindow* window, int startPos) { +int PtpCursor::fillObject(CursorWindow* window, int startPos) { MtpDevice* device = mClient->getDevice(mDeviceID); if (device && fillObject(window, device, mQbjectID, startPos)) return 1; @@ -195,7 +195,7 @@ int MtpCursor::fillObject(CursorWindow* window, int startPos) { return 0; } -bool MtpCursor::fillDevice(CursorWindow* window, MtpDevice* device, int row) { +bool PtpCursor::fillDevice(CursorWindow* window, MtpDevice* device, int row) { MtpDeviceInfo* deviceInfo = device->getDeviceInfo(); if (!deviceInfo) return false; @@ -225,7 +225,7 @@ bool MtpCursor::fillDevice(CursorWindow* window, MtpDevice* device, int row) { return true; } -bool MtpCursor::fillStorage(CursorWindow* window, MtpDevice* device, +bool PtpCursor::fillStorage(CursorWindow* window, MtpDevice* device, MtpStorageID storageID, int row) { LOGD("fillStorage %d\n", storageID); @@ -273,7 +273,7 @@ fail: return false; } -bool MtpCursor::fillObject(CursorWindow* window, MtpDevice* device, +bool PtpCursor::fillObject(CursorWindow* window, MtpDevice* device, MtpObjectHandle objectID, int row) { MtpObjectInfo* objectInfo = device->getObjectInfo(objectID); @@ -385,7 +385,7 @@ fail: return false; } -bool MtpCursor::prepareRow(CursorWindow* window) { +bool PtpCursor::prepareRow(CursorWindow* window) { if (!window->setNumColumns(mColumnCount)) { LOGE("Failed to change column count from %d to %d", window->getNumColumns(), mColumnCount); return false; @@ -399,7 +399,7 @@ bool MtpCursor::prepareRow(CursorWindow* window) { } -bool MtpCursor::putLong(CursorWindow* window, int64_t value, int row, int column) { +bool PtpCursor::putLong(CursorWindow* window, int64_t value, int row, int column) { if (!window->putLong(row, column, value)) { window->freeLastRow(); LOGE("Failed allocating space for a long in column %d", column); @@ -408,7 +408,7 @@ bool MtpCursor::putLong(CursorWindow* window, int64_t value, int row, int column return true; } -bool MtpCursor::putString(CursorWindow* window, const char* text, int row, int column) { +bool PtpCursor::putString(CursorWindow* window, const char* text, int row, int column) { int size = strlen(text) + 1; int offset = window->alloc(size); if (!offset) { @@ -427,7 +427,7 @@ bool MtpCursor::putString(CursorWindow* window, const char* text, int row, int c return true; } -bool MtpCursor::putThumbnail(CursorWindow* window, MtpObjectHandle objectID, +bool PtpCursor::putThumbnail(CursorWindow* window, MtpObjectHandle objectID, MtpObjectFormat format, int row, int column) { MtpDevice* device = mClient->getDevice(mDeviceID); void* thumbnail; diff --git a/media/mtp/MtpCursor.h b/media/mtp/PtpCursor.h index 2e03c29..38a1d47 100644 --- a/media/mtp/MtpCursor.h +++ b/media/mtp/PtpCursor.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef _MTP_CURSOR_H -#define _MTP_CURSOR_H +#ifndef _PTP_CURSOR_H +#define _PTP_CURSOR_H #include "MtpTypes.h" @@ -23,7 +23,7 @@ namespace android { class CursorWindow; -class MtpCursor { +class PtpCursor { private: enum { DEVICE = 1, @@ -45,10 +45,10 @@ private: int* mColumns; public: - MtpCursor(MtpClient* client, int queryType, int deviceID, + PtpCursor(MtpClient* client, int queryType, int deviceID, MtpStorageID storageID, MtpObjectHandle objectID, int columnCount, int* columns); - virtual ~MtpCursor(); + virtual ~PtpCursor(); int fillWindow(CursorWindow* window, int startPos); @@ -75,4 +75,4 @@ private: }; // namespace android -#endif // _MTP_CURSOR_H +#endif // _PTP_CURSOR_H diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java index c04873a..0942d1f 100644 --- a/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java @@ -24,7 +24,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.provider.Mtp; +import android.provider.Ptp; import android.util.Log; import android.view.View; import android.widget.ListAdapter; @@ -58,7 +58,7 @@ public class CameraBrowser extends ListActivity { } private static final String[] DEVICE_COLUMNS = - new String[] { Mtp.Device._ID, Mtp.Device.MANUFACTURER, Mtp.Device.MODEL }; + new String[] { Ptp.Device._ID, Ptp.Device.MANUFACTURER, Ptp.Device.MODEL }; @Override protected void onCreate(Bundle savedInstanceState) { @@ -71,7 +71,7 @@ public class CameraBrowser extends ListActivity { protected void onResume() { super.onResume(); - Cursor c = getContentResolver().query(Mtp.Device.CONTENT_URI, + Cursor c = getContentResolver().query(Ptp.Device.CONTENT_URI, DEVICE_COLUMNS, null, null, null); Log.d(TAG, "query returned " + c); startManagingCursor(c); @@ -80,12 +80,12 @@ public class CameraBrowser extends ListActivity { // Map Cursor columns to views defined in simple_list_item_2.xml mAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, c, - new String[] { Mtp.Device.MANUFACTURER, Mtp.Device.MODEL }, + new String[] { Ptp.Device.MANUFACTURER, Ptp.Device.MODEL }, new int[] { android.R.id.text1, android.R.id.text2 }); setListAdapter(mAdapter); // register for changes to the device list - mResolver.registerContentObserver(Mtp.Device.CONTENT_URI, true, mDeviceObserver); + mResolver.registerContentObserver(Ptp.Device.CONTENT_URI, true, mDeviceObserver); } @Override diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java index 2060657..40c5978 100644 --- a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java @@ -25,7 +25,7 @@ import android.graphics.BitmapFactory; import android.media.MtpConstants; import android.net.Uri; import android.os.Bundle; -import android.provider.Mtp; +import android.provider.Ptp; import android.util.Log; import android.view.View; import android.widget.AdapterView; @@ -49,7 +49,7 @@ public class ObjectBrowser extends ListActivity { private DeviceDisconnectedReceiver mDisconnectedReceiver; private static final String[] OBJECT_COLUMNS = - new String[] { Mtp.Object._ID, Mtp.Object.NAME, Mtp.Object.FORMAT, Mtp.Object.THUMB }; + new String[] { Ptp.Object._ID, Ptp.Object.NAME, Ptp.Object.FORMAT, Ptp.Object.THUMB }; static final int ID_COLUMN = 0; static final int NAME_COLUMN = 1; @@ -74,9 +74,9 @@ public class ObjectBrowser extends ListActivity { Cursor c; Uri uri; if (mObjectID == 0) { - uri = Mtp.Object.getContentUriForStorageChildren(mDeviceID, mStorageID); + uri = Ptp.Object.getContentUriForStorageChildren(mDeviceID, mStorageID); } else { - uri = Mtp.Object.getContentUriForObjectChildren(mDeviceID, mObjectID); + uri = Ptp.Object.getContentUriForObjectChildren(mDeviceID, mObjectID); } Log.d(TAG, "query " + uri); c = getContentResolver().query(uri, OBJECT_COLUMNS, null, null, null); @@ -99,7 +99,7 @@ public class ObjectBrowser extends ListActivity { protected void onListItemClick(ListView l, View v, int position, long id) { long rowID = mAdapter.getItemId(position); Cursor c = getContentResolver().query( - Mtp.Object.getContentUri(mDeviceID, rowID), + Ptp.Object.getContentUri(mDeviceID, rowID), OBJECT_COLUMNS, null, null, null); Log.d(TAG, "query returned " + c + " count: " + c.getCount()); long format = 0; diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java index 4e63128..3a6c6a4 100644 --- a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java @@ -24,7 +24,7 @@ import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import android.os.Environment; -import android.provider.Mtp; +import android.provider.Ptp; import android.util.Log; import android.view.View; import android.widget.Button; @@ -51,21 +51,21 @@ public class ObjectViewer extends Activity implements View.OnClickListener { private DeviceDisconnectedReceiver mDisconnectedReceiver; private static final String[] OBJECT_COLUMNS = - new String[] { Mtp.Object._ID, - Mtp.Object.NAME, - Mtp.Object.SIZE, - Mtp.Object.THUMB_WIDTH, - Mtp.Object.THUMB_HEIGHT, - Mtp.Object.THUMB_SIZE, - Mtp.Object.IMAGE_WIDTH, - Mtp.Object.IMAGE_HEIGHT, - Mtp.Object.IMAGE_DEPTH, - Mtp.Object.SEQUENCE_NUMBER, - Mtp.Object.DATE_CREATED, - Mtp.Object.DATE_MODIFIED, - Mtp.Object.KEYWORDS, - Mtp.Object.THUMB, - Mtp.Object.FORMAT, + new String[] { Ptp.Object._ID, + Ptp.Object.NAME, + Ptp.Object.SIZE, + Ptp.Object.THUMB_WIDTH, + Ptp.Object.THUMB_HEIGHT, + Ptp.Object.THUMB_SIZE, + Ptp.Object.IMAGE_WIDTH, + Ptp.Object.IMAGE_HEIGHT, + Ptp.Object.IMAGE_DEPTH, + Ptp.Object.SEQUENCE_NUMBER, + Ptp.Object.DATE_CREATED, + Ptp.Object.DATE_MODIFIED, + Ptp.Object.KEYWORDS, + Ptp.Object.THUMB, + Ptp.Object.FORMAT, }; @Override @@ -91,7 +91,7 @@ public class ObjectViewer extends Activity implements View.OnClickListener { if (mDeviceID != 0 && mObjectID != 0) { Cursor c = getContentResolver().query( - Mtp.Object.getContentUri(mDeviceID, mObjectID), + Ptp.Object.getContentUri(mDeviceID, mObjectID), OBJECT_COLUMNS, null, null, null); c.moveToFirst(); TextView view = (TextView)findViewById(R.id.name); @@ -147,7 +147,7 @@ public class ObjectViewer extends Activity implements View.OnClickListener { dest.mkdirs(); dest = new File(dest, mFileName); - Uri requestUri = Mtp.Object.getContentUriForImport(mDeviceID, mObjectID, + Uri requestUri = Ptp.Object.getContentUriForImport(mDeviceID, mObjectID, dest.getAbsolutePath()); Uri resultUri = getContentResolver().insert(requestUri, new ContentValues()); Log.d(TAG, "save returned " + resultUri); @@ -162,7 +162,7 @@ public class ObjectViewer extends Activity implements View.OnClickListener { } private void deleteObject() { - Uri uri = Mtp.Object.getContentUri(mDeviceID, mObjectID); + Uri uri = Ptp.Object.getContentUri(mDeviceID, mObjectID); Log.d(TAG, "deleting " + uri); diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java index 4da88d6..62187b0 100644 --- a/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java +++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java @@ -21,7 +21,7 @@ import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.provider.Mtp; +import android.provider.Ptp; import android.util.Log; import android.view.View; import android.widget.ListAdapter; @@ -40,7 +40,7 @@ public class StorageBrowser extends ListActivity { private DeviceDisconnectedReceiver mDisconnectedReceiver; private static final String[] STORAGE_COLUMNS = - new String[] { Mtp.Storage._ID, Mtp.Storage.DESCRIPTION }; + new String[] { Ptp.Storage._ID, Ptp.Storage.DESCRIPTION }; @Override protected void onCreate(Bundle savedInstanceState) { @@ -54,7 +54,7 @@ public class StorageBrowser extends ListActivity { super.onResume(); if (mDeviceID != 0) { - Cursor c = getContentResolver().query(Mtp.Storage.getContentUri(mDeviceID), + Cursor c = getContentResolver().query(Ptp.Storage.getContentUri(mDeviceID), STORAGE_COLUMNS, null, null, null); Log.d(TAG, "query returned " + c); startManagingCursor(c); @@ -62,7 +62,7 @@ public class StorageBrowser extends ListActivity { // Map Cursor columns to views defined in simple_list_item_1.xml mAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, c, - new String[] { Mtp.Storage.DESCRIPTION }, + new String[] { Ptp.Storage.DESCRIPTION }, new int[] { android.R.id.text1, android.R.id.text2 }); setListAdapter(mAdapter); } diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_mini.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_mini.png Binary files differdeleted file mode 100644 index 9ababb7..0000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_mini.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png Binary files differdeleted file mode 100644 index ffbd2d3..0000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png Binary files differindex e81cd7c..9216030 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png Binary files differindex e81cd7c..9216030 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png Binary files differindex c56ae8a..e529f6f 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png Binary files differindex bf720cbb..e529f6f 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1x.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1x.png Binary files differnew file mode 100644 index 0000000..02c27ee --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1x.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png Binary files differindex 38e3b24..57558ad 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png Binary files differindex 3559497..57558ad 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png Binary files differindex 6f98e72..e4425b2 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png Binary files differindex 8b932a6..e4425b2 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3g.png Binary files differnew file mode 100644 index 0000000..84ac927 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3g.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png Binary files differindex 759631c..09de6b0 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png Binary files differindex 45c4509..09de6b0 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_edge.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_edge.png Binary files differnew file mode 100644 index 0000000..13cae40 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_edge.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png Binary files differindex da5b02c..690b5f6 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_gprs.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_gprs.png Binary files differnew file mode 100644 index 0000000..d0a4fd0 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_gprs.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_hsdpa.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_hsdpa.png Binary files differnew file mode 100644 index 0000000..05976bd --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_hsdpa.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_roam.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_roam.png Binary files differnew file mode 100644 index 0000000..2cc3cd6 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_roam.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0.png Binary files differindex 2989939..1c59b2a 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0_fully.png Binary files differnew file mode 100644 index 0000000..1c59b2a --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0_fully.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1.png Binary files differindex 6f40cb4..32e9165 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1_fully.png Binary files differindex 60e3794a..32e9165 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1_fully.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1_fully.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2.png Binary files differindex ab1b0c6..ea71298 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2_fully.png Binary files differindex eb25919..ea71298 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2_fully.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2_fully.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3.png Binary files differindex b574115..869a497 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3_fully.png Binary files differindex 14cd8fa..869a497 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3_fully.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3_fully.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4.png Binary files differindex 3b81ceb..1711c82 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4_fully.png Binary files differindex ef72980..1711c82 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4_fully.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4_fully.png diff --git a/packages/SystemUI/res/layout-xlarge/status_bar.xml b/packages/SystemUI/res/layout-xlarge/status_bar.xml index 4fa306e..488dbba 100644 --- a/packages/SystemUI/res/layout-xlarge/status_bar.xml +++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml @@ -80,21 +80,24 @@ android:layout_width="48dip" android:layout_height="match_parent" android:orientation="horizontal" + android:gravity="center" > <ImageView - android:id="@+id/battery" + android:id="@+id/network" android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_gravity="top" - android:layout_marginTop="18dp" + android:layout_marginTop="19dp" + android:layout_marginRight="4dp" /> <ImageView - android:id="@+id/network" + android:id="@+id/battery" android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_gravity="top" - android:layout_marginTop="14dp" - android:src="@drawable/ic_sysbar_wifi_mini" + android:layout_marginTop="19dp" + android:layout_marginLeft="2dp" + android:layout_marginRight="2dp" /> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml index f9e2d5e..5fa8b3b 100644 --- a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml +++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml @@ -78,7 +78,6 @@ android:layout_below="@id/date" android:layout_marginTop="16dp" android:layout_marginLeft="48dp" - android:src="@drawable/ic_sysbar_battery_mini" android:baseline="17dp" /> @@ -98,7 +97,6 @@ android:layout_width="wrap_content" android:layout_toRightOf="@id/battery_text" android:layout_alignBaseline="@id/battery" - android:src="@drawable/ic_sysbar_wifi_mini" android:baseline="21dp" /> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 3ad199e..18e8273 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -71,6 +71,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:fadingEdge="none" + android:overScrollMode="ifContentScrolls" > <LinearLayout android:id="@+id/notificationLinearLayout" diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java index ff2a4ed..a98ef0b 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java @@ -193,14 +193,13 @@ public class RecentApplicationsActivity extends Activity { ActivityDescription item = mActivityDescriptions.get(n); if (item.id >= 0) { // This is an active task; it should just go to the foreground. - IActivityManager am = ActivityManagerNative.getDefault(); - try { - am.moveTaskToFront(item.id); - } catch (RemoteException e) { - } + final ActivityManager am = (ActivityManager) + getSystemService(Context.ACTIVITY_SERVICE); + am.moveTaskToFront(item.id, ActivityManager.MOVE_TASK_WITH_HOME); } else if (item.intent != null) { // prepare a launch intent and send it - item.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); + item.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY + | Intent.FLAG_ACTIVITY_TASK_ON_HOME); try { if (DBG) Log.v(TAG, "Starting intent " + item.intent); startActivity(item.intent); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index 4ff2429..18003dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -37,6 +37,7 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.util.Slog; +import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -57,8 +58,6 @@ public class NetworkController extends BroadcastReceiver { boolean mHspaDataDistinguishable; final TelephonyManager mPhone; boolean mDataConnected; - int mPhoneSignalIconId; - int mDataIconId; IccCard.State mSimState = IccCard.State.READY; int mPhoneState = TelephonyManager.CALL_STATE_IDLE; int mDataState = TelephonyManager.DATA_DISCONNECTED; @@ -66,6 +65,10 @@ public class NetworkController extends BroadcastReceiver { ServiceState mServiceState; SignalStrength mSignalStrength; int[] mDataIconList = TelephonyIcons.DATA_G[0]; + int mPhoneSignalIconId; + int mDataDirectionIconId; + int mDataSignalIconId; + int mDataTypeIconId; // wifi final WifiManager mWifiManager; @@ -81,11 +84,17 @@ public class NetworkController extends BroadcastReceiver { // our ui Context mContext; - ArrayList<ImageView> mPhoneIconViews = new ArrayList<ImageView>(); - ArrayList<ImageView> mDataIconViews = new ArrayList<ImageView>(); + ArrayList<ImageView> mPhoneSignalIconViews = new ArrayList<ImageView>(); + ArrayList<ImageView> mDataDirectionIconViews = new ArrayList<ImageView>(); + ArrayList<ImageView> mWifiIconViews = new ArrayList<ImageView>(); + ArrayList<ImageView> mCombinedSignalIconViews = new ArrayList<ImageView>(); + ArrayList<ImageView> mDataTypeIconViews = new ArrayList<ImageView>(); ArrayList<TextView> mLabelViews = new ArrayList<TextView>(); int mLastPhoneSignalIconId = -1; - int mLastCombinedDataIconId = -1; + int mLastDataDirectionIconId = -1; + int mLastWifiIconId = -1; + int mLastCombinedSignalIconId = -1; + int mLastDataTypeIconId = -1; String mLastLabel = ""; // yuck -- stop doing this here and put it in the framework @@ -116,7 +125,6 @@ public class NetworkController extends BroadcastReceiver { IntentFilter filter = new IntentFilter(); filter.addAction(WifiManager.RSSI_CHANGED_ACTION); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); context.registerReceiver(this, filter); @@ -125,12 +133,24 @@ public class NetworkController extends BroadcastReceiver { mBatteryStats = BatteryStatsService.getService(); } - public void addPhoneIconView(ImageView v) { - mPhoneIconViews.add(v); + public void addPhoneSignalIconView(ImageView v) { + mPhoneSignalIconViews.add(v); + } + + public void addDataDirectionIconView(ImageView v) { + mDataDirectionIconViews.add(v); + } + + public void addWifiIconView(ImageView v) { + mWifiIconViews.add(v); + } + + public void addCombinedSignalIconView(ImageView v) { + mCombinedSignalIconViews.add(v); } - public void addCombinedDataIconView(ImageView v) { - mDataIconViews.add(v); + public void addDataTypeIconView(ImageView v) { + mDataTypeIconViews.add(v); } public void addLabelView(TextView v) { @@ -141,7 +161,6 @@ public class NetworkController extends BroadcastReceiver { final String action = intent.getAction(); if (action.equals(WifiManager.RSSI_CHANGED_ACTION) || action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION) - || action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION) || action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { updateWifiState(intent); refreshViews(); @@ -317,12 +336,15 @@ public class NetworkController extends BroadcastReceiver { if (Settings.System.getInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 1) { mPhoneSignalIconId = R.drawable.stat_sys_signal_flightmode; + mDataSignalIconId = R.drawable.stat_sys_signal_flightmode; } else { mPhoneSignalIconId = R.drawable.stat_sys_signal_null; + mDataSignalIconId = R.drawable.stat_sys_signal_0; // note we use 0 instead of null } } else { if (mSignalStrength == null) { mPhoneSignalIconId = R.drawable.stat_sys_signal_null; + mDataSignalIconId = R.drawable.stat_sys_signal_0; // note we use 0 instead of null } else if (isCdma()) { // If 3G(EV) and 1x network are available than 3G should be // displayed, displayed RSSI should be from the EV side. @@ -340,6 +362,7 @@ public class NetworkController extends BroadcastReceiver { iconList = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[mInetCondition]; } mPhoneSignalIconId = iconList[iconLevel]; + mDataSignalIconId = TelephonyIcons.DATA_SIGNAL_STRENGTH[mInetCondition][iconLevel]; } else { int asu = mSignalStrength.getGsmSignalStrength(); @@ -362,6 +385,7 @@ public class NetworkController extends BroadcastReceiver { iconList = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[mInetCondition]; } mPhoneSignalIconId = iconList[iconLevel]; + mDataSignalIconId = TelephonyIcons.DATA_SIGNAL_STRENGTH[mInetCondition][iconLevel]; } } } @@ -370,36 +394,47 @@ public class NetworkController extends BroadcastReceiver { switch (net) { case TelephonyManager.NETWORK_TYPE_EDGE: mDataIconList = TelephonyIcons.DATA_E[mInetCondition]; + mDataTypeIconId = R.drawable.stat_sys_signal_edge; break; case TelephonyManager.NETWORK_TYPE_UMTS: mDataIconList = TelephonyIcons.DATA_3G[mInetCondition]; + mDataTypeIconId = R.drawable.stat_sys_signal_3g; break; case TelephonyManager.NETWORK_TYPE_HSDPA: case TelephonyManager.NETWORK_TYPE_HSUPA: case TelephonyManager.NETWORK_TYPE_HSPA: if (mHspaDataDistinguishable) { mDataIconList = TelephonyIcons.DATA_H[mInetCondition]; + mDataTypeIconId = R.drawable.stat_sys_signal_hsdpa; } else { mDataIconList = TelephonyIcons.DATA_3G[mInetCondition]; + mDataTypeIconId = R.drawable.stat_sys_signal_3g; } break; case TelephonyManager.NETWORK_TYPE_CDMA: // display 1xRTT for IS95A/B mDataIconList = TelephonyIcons.DATA_1X[mInetCondition]; + mDataTypeIconId = R.drawable.stat_sys_signal_1x; break; case TelephonyManager.NETWORK_TYPE_1xRTT: mDataIconList = TelephonyIcons.DATA_1X[mInetCondition]; + mDataTypeIconId = R.drawable.stat_sys_signal_1x; break; case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through case TelephonyManager.NETWORK_TYPE_EVDO_A: case TelephonyManager.NETWORK_TYPE_EVDO_B: mDataIconList = TelephonyIcons.DATA_3G[mInetCondition]; + mDataTypeIconId = R.drawable.stat_sys_signal_3g; break; // TODO - add support for NETWORK_TYPE_LTE and NETWORK_TYPE_EHRPD default: mDataIconList = TelephonyIcons.DATA_G[mInetCondition]; + mDataTypeIconId = R.drawable.stat_sys_signal_gprs; break; } + if ((isCdma() && isCdmaEri()) || mPhone.isNetworkRoaming()) { + mDataTypeIconId = R.drawable.stat_sys_signal_roam; + } } boolean isCdmaEri() { @@ -436,7 +471,7 @@ public class NetworkController extends BroadcastReceiver { iconId = mDataIconList[0]; break; } - mDataIconId = iconId; + mDataDirectionIconId = iconId; } else { iconId = 0; visible = false; @@ -477,7 +512,7 @@ public class NetworkController extends BroadcastReceiver { Binder.restoreCallingIdentity(ident); } - mDataIconId = iconId; + mDataDirectionIconId = iconId; mDataConnected = visible; } @@ -489,8 +524,7 @@ public class NetworkController extends BroadcastReceiver { mWifiEnabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED; - } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) - || action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) { + } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { final NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); boolean wasConnected = mWifiConnected; @@ -575,8 +609,10 @@ public class NetworkController extends BroadcastReceiver { void refreshViews() { Context context = mContext; - int combinedDataIconId; + int combinedSignalIconId; + int dataTypeIconId; String label; + int N; if (mWifiConnected) { if (mWifiSsid == null) { @@ -585,35 +621,84 @@ public class NetworkController extends BroadcastReceiver { label = context.getString(R.string.system_panel_signal_meter_wifi_ssid_format, mWifiSsid); } - combinedDataIconId = mWifiIconId; - } else if (mDataConnected) { - label = context.getString(R.string.system_panel_signal_meter_data_connected); - combinedDataIconId = mDataIconId; + combinedSignalIconId = mWifiIconId; + dataTypeIconId = 0; } else { - label = context.getString(R.string.system_panel_signal_meter_disconnected); - combinedDataIconId = 0; + if (mDataConnected) { + label = context.getString(R.string.system_panel_signal_meter_data_connected); + } else { + label = context.getString(R.string.system_panel_signal_meter_disconnected); + } + combinedSignalIconId = mDataSignalIconId; + dataTypeIconId = mDataTypeIconId; } - int N; + if (false) { + Slog.d(TAG, "refreshViews combinedSignalIconId=0x" + + Integer.toHexString(mPhoneSignalIconId) + + " mPhoneSignalIconId=0x" + Integer.toHexString(mPhoneSignalIconId) + + " mDataDirectionIconId=0x" + Integer.toHexString(mDataDirectionIconId) + + " mDataSignalIconId=0x" + Integer.toHexString(mDataSignalIconId) + + " mDataTypeIconId=0x" + Integer.toHexString(mDataTypeIconId) + + " mWifiIconId=0x" + Integer.toHexString(mWifiIconId)); + } + // the phone icon on phones if (mLastPhoneSignalIconId != mPhoneSignalIconId) { mLastPhoneSignalIconId = mPhoneSignalIconId; - N = mPhoneIconViews.size(); + N = mPhoneSignalIconViews.size(); for (int i=0; i<N; i++) { - ImageView v = mPhoneIconViews.get(i); + final ImageView v = mPhoneSignalIconViews.get(i); v.setImageResource(mPhoneSignalIconId); } } - if (mLastCombinedDataIconId != combinedDataIconId) { - mLastCombinedDataIconId = combinedDataIconId; - N = mDataIconViews.size(); + // the data icon on phones + if (mLastDataDirectionIconId != mDataDirectionIconId) { + mLastDataDirectionIconId = mDataDirectionIconId; + N = mDataDirectionIconViews.size(); for (int i=0; i<N; i++) { - ImageView v = mDataIconViews.get(i); - v.setImageResource(combinedDataIconId); + final ImageView v = mDataDirectionIconViews.get(i); + v.setImageResource(mDataDirectionIconId); + } + } + + // the wifi icon on phones + if (mLastWifiIconId != mWifiIconId) { + mLastWifiIconId = mWifiIconId; + N = mWifiIconViews.size(); + for (int i=0; i<N; i++) { + final ImageView v = mWifiIconViews.get(i); + v.setImageResource(mWifiIconId); + } + } + + // the combined data signal icon + if (mLastCombinedSignalIconId != combinedSignalIconId) { + mLastCombinedSignalIconId = combinedSignalIconId; + N = mCombinedSignalIconViews.size(); + for (int i=0; i<N; i++) { + final ImageView v = mCombinedSignalIconViews.get(i); + v.setImageResource(combinedSignalIconId); + } + } + + // the data network type overlay + if (mLastDataTypeIconId != dataTypeIconId) { + mLastDataTypeIconId = dataTypeIconId; + N = mDataTypeIconViews.size(); + for (int i=0; i<N; i++) { + final ImageView v = mDataTypeIconViews.get(i); + if (dataTypeIconId == 0) { + v.setVisibility(View.INVISIBLE); + } else { + v.setVisibility(View.VISIBLE); + v.setImageResource(dataTypeIconId); + } } } + // the label in the notification panel if (!mLastLabel.equals(label)) { mLastLabel = label; N = mLabelViews.size(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java index 050a746..94c68ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java @@ -48,6 +48,8 @@ class TelephonyIcons { R.drawable.stat_sys_r_signal_4_fully } }; + static final int[][] DATA_SIGNAL_STRENGTH = TELEPHONY_SIGNAL_STRENGTH; + //***** Data connection icons //GSM/UMTS diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java index e0b05f9..0c31304 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java @@ -252,13 +252,13 @@ public class RecentAppsPanel extends LinearLayout implements StatusBarPanel, OnC ActivityDescription ad = (ActivityDescription)v.getTag(); if (ad.id >= 0) { // This is an active task; it should just go to the foreground. - IActivityManager am = ActivityManagerNative.getDefault(); - try { - am.moveTaskToFront(ad.id); - } catch (RemoteException e) { - } + final ActivityManager am = (ActivityManager) + getContext().getSystemService(Context.ACTIVITY_SERVICE); + am.moveTaskToFront(ad.id, ActivityManager.MOVE_TASK_WITH_HOME); } else { Intent intent = ad.intent; + intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY + | Intent.FLAG_ACTIVITY_TASK_ON_HOME); if (DEBUG) Log.v(TAG, "Starting activity " + intent); getContext().startActivity(intent); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java index ab509ef..a934cd7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java @@ -157,7 +157,7 @@ public class TabletStatusBar extends StatusBar { mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery)); mBatteryController.addLabelView( (TextView)mNotificationPanel.findViewById(R.id.battery_text)); - mNetworkController.addCombinedDataIconView( + mNetworkController.addCombinedSignalIconView( (ImageView)mNotificationPanel.findViewById(R.id.network)); mNetworkController.addLabelView( (TextView)mNotificationPanel.findViewById(R.id.network_text)); @@ -282,7 +282,7 @@ public class TabletStatusBar extends StatusBar { mBatteryController = new BatteryController(mContext); mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery)); mNetworkController = new NetworkController(mContext); - mNetworkController.addCombinedDataIconView((ImageView)sb.findViewById(R.id.network)); + mNetworkController.addCombinedSignalIconView((ImageView)sb.findViewById(R.id.network)); // The navigation buttons mNavigationArea = sb.findViewById(R.id.navigationArea); diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 5287289..1c1a46e 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -139,6 +139,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; static final int LONG_PRESS_POWER_SHUT_OFF = 2; + static final int LONG_PRESS_HOME_NOTHING = 0; + static final int LONG_PRESS_HOME_RECENT_DIALOG = 1; + static final int LONG_PRESS_HOME_RECENT_ACTIVITY = 2; + // wallpaper is at the bottom, though the window manager may move it. static final int WALLPAPER_LAYER = 2; static final int APPLICATION_LAYER = 2; @@ -327,8 +331,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Nothing to see here, move along... int mFancyRotationAnimation; - // Enable 3D recents based on config settings. - private Boolean mUse3dRecents; + // What we do when the user long presses on home + private int mLongPressOnHomeBehavior = -1; ShortcutManager mShortcutManager; PowerManager.WakeLock mBroadcastWakeLock; @@ -565,8 +569,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { * the user lets go of the home key */ mHomePressed = false; - performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); - sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS); showRecentAppsDialog(); } }; @@ -576,16 +578,32 @@ public class PhoneWindowManager implements WindowManagerPolicy { */ void showRecentAppsDialog() { // We can't initialize this in init() since the configuration hasn't been loaded yet. - if (mUse3dRecents == null) { - mUse3dRecents = mContext.getResources().getBoolean(R.bool.config_enableRecentApps3D); + if (mLongPressOnHomeBehavior < 0) { + mLongPressOnHomeBehavior + = mContext.getResources().getInteger(R.integer.config_longPressOnPowerBehavior); + if (mLongPressOnHomeBehavior < LONG_PRESS_HOME_NOTHING || + mLongPressOnHomeBehavior > LONG_PRESS_HOME_RECENT_ACTIVITY) { + mLongPressOnHomeBehavior = LONG_PRESS_HOME_NOTHING; + } + } + + if (mLongPressOnHomeBehavior != LONG_PRESS_HOME_NOTHING) { + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS); } // Use 3d Recents dialog - if (mUse3dRecents) { + if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_DIALOG) { + // Fallback to dialog if we fail to launch the above. + if (mRecentAppsDialog == null) { + mRecentAppsDialog = new RecentApplicationsDialog(mContext); + } + mRecentAppsDialog.show(); + } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_ACTIVITY) { try { Intent intent = new Intent(); intent.setClassName("com.android.systemui", - "com.android.systemui.statusbar.RecentApplicationsActivity"); + "com.android.systemui.recent.RecentApplicationsActivity"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); mContext.startActivity(intent); @@ -594,12 +612,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { Log.e(TAG, "Failed to launch RecentAppsIntent", e); } } - - // Fallback to dialog if we fail to launch the above. - if (mRecentAppsDialog == null) { - mRecentAppsDialog = new RecentApplicationsDialog(mContext); - } - mRecentAppsDialog.show(); } /** {@inheritDoc} */ diff --git a/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java b/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java index d9e8c2b..f53092d 100644 --- a/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java +++ b/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java @@ -138,13 +138,12 @@ public class RecentApplicationsDialog extends Dialog implements OnClickListener RecentTag tag = (RecentTag)b.getTag(); if (tag.info.id >= 0) { // This is an active task; it should just go to the foreground. - IActivityManager am = ActivityManagerNative.getDefault(); - try { - am.moveTaskToFront(tag.info.id); - } catch (RemoteException e) { - } + final ActivityManager am = (ActivityManager) + getContext().getSystemService(Context.ACTIVITY_SERVICE); + am.moveTaskToFront(tag.info.id, ActivityManager.MOVE_TASK_WITH_HOME); } else if (tag.intent != null) { - tag.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); + tag.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY + | Intent.FLAG_ACTIVITY_TASK_ON_HOME); try { getContext().startActivity(tag.intent); } catch (ActivityNotFoundException e) { diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java index 0e45145..0a28da7 100644 --- a/services/java/com/android/server/DropBoxManagerService.java +++ b/services/java/com/android/server/DropBoxManagerService.java @@ -343,16 +343,17 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { if ((entry.flags & DropBoxManager.IS_TEXT) != 0 && (doPrint || !doFile)) { DropBoxManager.Entry dbe = null; + InputStreamReader isr = null; try { dbe = new DropBoxManager.Entry( entry.tag, entry.timestampMillis, entry.file, entry.flags); if (doPrint) { - InputStreamReader r = new InputStreamReader(dbe.getInputStream()); + isr = new InputStreamReader(dbe.getInputStream()); char[] buf = new char[4096]; boolean newline = false; for (;;) { - int n = r.read(buf); + int n = isr.read(buf); if (n <= 0) break; out.append(buf, 0, n); newline = (buf[n - 1] == '\n'); @@ -376,6 +377,12 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { Slog.e(TAG, "Can't read: " + entry.file, e); } finally { if (dbe != null) dbe.close(); + if (isr != null) { + try { + isr.close(); + } catch (IOException unused) { + } + } } } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 84bc100..723432d 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -55,6 +55,7 @@ import android.os.IBinder; import android.os.IInterface; import android.os.Message; import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -120,6 +121,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // If IME doesn't support the system locale, the default subtype will be the first defined one. private static final int DEFAULT_SUBTYPE_ID = 0; + private static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; + private static final String SUBTYPE_MODE_VOICE = "voice"; + final Context mContext; final Handler mHandler; final InputMethodSettings mSettings; @@ -235,6 +239,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ private InputMethodSubtype mCurrentSubtype; + // This list contains the pairs of InputMethodInfo and InputMethodSubtype. + private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>> + mShortcutInputMethodsAndSubtypes = + new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>(); /** * Set to true if our ServiceConnection is currently actively bound to @@ -983,6 +991,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurMethodId = null; unbindCurrentMethodLocked(true); } + mShortcutInputMethodsAndSubtypes.clear(); } else { // There is no longer an input method set, so stop any current one. mCurMethodId = null; @@ -1291,7 +1300,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } public void setInputMethod(IBinder token, String id) { - setInputMethodWithSubtype(token, id, NOT_A_SUBTYPE_ID); + setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID); + } + + public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { + synchronized (mMethodMap) { + if (subtype != null) { + setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode( + mMethodMap.get(id), subtype.hashCode())); + } else { + setInputMethod(token, id); + } + } } public boolean switchToLastInputMethod(IBinder token) { @@ -1300,7 +1320,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (lastIme != null) { InputMethodInfo imi = mMethodMap.get(lastIme.first); if (imi != null) { - setInputMethodWithSubtype(token, lastIme.first, getSubtypeIdFromHashCode( + setInputMethodWithSubtypeId(token, lastIme.first, getSubtypeIdFromHashCode( imi, Integer.valueOf(lastIme.second))); return true; } @@ -1309,7 +1329,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private void setInputMethodWithSubtype(IBinder token, String id, int subtypeId) { + private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) { synchronized (mMethodMap) { if (token == null) { if (mContext.checkCallingOrSelfPermission( @@ -1900,35 +1920,43 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { - ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes(); - for (int i = 0; i < subtypes.size(); ++i) { - InputMethodSubtype ims = subtypes.get(i); - if (subtypeHashCode == ims.hashCode()) { - return i; + if (imi != null) { + ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes(); + for (int i = 0; i < subtypes.size(); ++i) { + InputMethodSubtype ims = subtypes.get(i); + if (subtypeHashCode == ims.hashCode()) { + return i; + } } } return NOT_A_SUBTYPE_ID; } - // If there are no selected subtypes, tries finding the most applicable one according to the - // current system locale - private int findApplicableSubtype(String id) { - InputMethodInfo imi = mMethodMap.get(id); - if (imi == null) { - return NOT_A_SUBTYPE_ID; - } - ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes(); + /** + * If there are no selected subtypes, tries finding the most applicable one according to the + * given locale. + * @param subtypes this function will search the most applicable subtype in subtypes + * @param mode subtypes will be filtered by mode + * @param locale subtypes will be filtered by locale + * @param defaultSubtypeId if this function can't find the most applicable subtype, it will + * return defaultSubtypeId + * @return the most applicable subtypeId + */ + private int findLastResortApplicableSubtypeLocked( + List<InputMethodSubtype> subtypes, String mode, String locale, int defaultSubtypeId) { if (subtypes == null || subtypes.size() == 0) { return NOT_A_SUBTYPE_ID; } - final String locale = mContext.getResources().getConfiguration().locale.toString(); + if (TextUtils.isEmpty(locale)) { + locale = mContext.getResources().getConfiguration().locale.toString(); + } final String language = locale.substring(0, 2); boolean partialMatchFound = false; - int applicableSubtypeId = DEFAULT_SUBTYPE_ID; + int applicableSubtypeId = defaultSubtypeId; for (int i = 0; i < subtypes.size(); ++i) { final String subtypeLocale = subtypes.get(i).getLocale(); - // An applicable subtype should be a keyboard subtype - if (subtypes.get(i).getMode().equalsIgnoreCase("keyboard")) { + // An applicable subtype should match "mode". + if (subtypes.get(i).getMode().equalsIgnoreCase(mode)) { if (locale.equals(subtypeLocale)) { // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") applicableSubtypeId = i; @@ -1950,6 +1978,69 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return applicableSubtypeId; } + // If there are no selected shortcuts, tries finding the most applicable ones. + private Pair<InputMethodInfo, InputMethodSubtype> + findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) { + List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); + InputMethodInfo mostApplicableIMI = null; + int mostApplicableSubtypeId = NOT_A_SUBTYPE_ID; + boolean foundInSystemIME = false; + + // Search applicable subtype for each InputMethodInfo + for (InputMethodInfo imi: imis) { + int subtypeId = NOT_A_SUBTYPE_ID; + if (mCurrentSubtype != null) { + // 1. Search with the current subtype's locale and the enabled subtypes + subtypeId = findLastResortApplicableSubtypeLocked( + mSettings.getEnabledInputMethodSubtypeListLocked( + imi), mode, mCurrentSubtype.getLocale(), NOT_A_SUBTYPE_ID); + if (subtypeId == NOT_A_SUBTYPE_ID) { + // 2. Search with the current subtype's locale and all subtypes + subtypeId = findLastResortApplicableSubtypeLocked(imi.getSubtypes(), + mode, mCurrentSubtype.getLocale(), NOT_A_SUBTYPE_ID); + } + } + // 3. Search with the system locale and the enabled subtypes + if (subtypeId == NOT_A_SUBTYPE_ID) { + subtypeId = findLastResortApplicableSubtypeLocked( + mSettings.getEnabledInputMethodSubtypeListLocked( + imi), mode, null, NOT_A_SUBTYPE_ID); + } + if (subtypeId == NOT_A_SUBTYPE_ID) { + // 4. Search with the system locale and all subtypes + subtypeId = findLastResortApplicableSubtypeLocked(imi.getSubtypes(), + mode, null, NOT_A_SUBTYPE_ID); + } + if (subtypeId != NOT_A_SUBTYPE_ID) { + if (imi.getId().equals(mCurMethodId)) { + // The current input method is the most applicable IME. + mostApplicableIMI = imi; + mostApplicableSubtypeId = subtypeId; + break; + } else if ((imi.getServiceInfo().applicationInfo.flags + & ApplicationInfo.FLAG_SYSTEM) != 0) { + // The system input method is 2nd applicable IME. + mostApplicableIMI = imi; + mostApplicableSubtypeId = subtypeId; + foundInSystemIME = true; + } else if (!foundInSystemIME) { + mostApplicableIMI = imi; + mostApplicableSubtypeId = subtypeId; + } + } + } + if (DEBUG) { + Slog.w(TAG, "Most applicable shortcut input method subtype was:" + + mostApplicableIMI.getId() + "," + mostApplicableSubtypeId); + } + if (mostApplicableIMI != null && mostApplicableSubtypeId != NOT_A_SUBTYPE_ID) { + return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI, + mostApplicableIMI.getSubtypes().get(mostApplicableSubtypeId)); + } else { + return null; + } + } + /** * @return Return the current subtype of this input method. */ @@ -1960,18 +2051,65 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID; } catch (SettingNotFoundException e) { } - if (!subtypeIsSelected || mCurrentSubtype == null) { - String lastInputMethodId = Settings.Secure.getString(mContext - .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); - int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); - if (subtypeId == NOT_A_SUBTYPE_ID) { - subtypeId = findApplicableSubtype(lastInputMethodId); + synchronized (mMethodMap) { + if (!subtypeIsSelected || mCurrentSubtype == null) { + String lastInputMethodId = Settings.Secure.getString( + mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); + if (subtypeId == NOT_A_SUBTYPE_ID) { + InputMethodInfo imi = mMethodMap.get(lastInputMethodId); + if (imi != null) { + // If there are no selected subtypes, the framework will try to find + // the most applicable subtype from all subtypes whose mode is + // SUBTYPE_MODE_KEYBOARD. This is an exceptional case, so we will hardcode + // the mode. + subtypeId = findLastResortApplicableSubtypeLocked(imi.getSubtypes(), + SUBTYPE_MODE_KEYBOARD, null, DEFAULT_SUBTYPE_ID); + } + } + if (subtypeId != NOT_A_SUBTYPE_ID) { + mCurrentSubtype = + mMethodMap.get(lastInputMethodId).getSubtypes().get(subtypeId); + } else { + mCurrentSubtype = null; + } } - if (subtypeId != NOT_A_SUBTYPE_ID) { - mCurrentSubtype = mMethodMap.get(lastInputMethodId).getSubtypes().get(subtypeId); + return mCurrentSubtype; + } + } + + private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi, + InputMethodSubtype subtype) { + if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) { + mShortcutInputMethodsAndSubtypes.get(imi).add(subtype); + } else { + ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); + subtypes.add(subtype); + mShortcutInputMethodsAndSubtypes.put(imi, subtypes); + } + } + + // TODO: We should change the return type from List to List<Parcelable> + public List getShortcutInputMethodsAndSubtypes() { + synchronized (mMethodMap) { + if (mShortcutInputMethodsAndSubtypes.size() == 0) { + // If there are no selected shortcut subtypes, the framework will try to find + // the most applicable subtype from all subtypes whose mode is + // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode. + Pair<InputMethodInfo, InputMethodSubtype> info = + findLastResortApplicableShortcutInputMethodAndSubtypeLocked( + SUBTYPE_MODE_VOICE); + addShortcutInputMethodAndSubtypes(info.first, info.second); + } + ArrayList ret = new ArrayList<Object>(); + for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) { + ret.add(imi); + for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) { + ret.add(subtype); + } } + return ret; } - return mCurrentSubtype; } public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { diff --git a/services/java/com/android/server/StrictModeFlash.java b/services/java/com/android/server/StrictModeFlash.java new file mode 100644 index 0000000..0a6c625 --- /dev/null +++ b/services/java/com/android/server/StrictModeFlash.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; // TODO: use com.android.server.wm, once things move there + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Region; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.view.Display; +import android.view.Surface; +import android.view.SurfaceSession; + +class StrictModeFlash { + private static final String TAG = "StrictModeFlash"; + + Surface mSurface; + int mLastDW; + int mLastDH; + boolean mDrawNeeded; + final int mThickness = 20; + + public StrictModeFlash(Display display, SurfaceSession session) { + final DisplayMetrics dm = new DisplayMetrics(); + display.getMetrics(dm); + + try { + mSurface = new Surface(session, 0, "StrictModeFlash", -1, 1, 1, PixelFormat.TRANSLUCENT, 0); + } catch (Surface.OutOfResourcesException e) { + return; + } + + mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary. + mSurface.setPosition(0, 0); + mDrawNeeded = true; + } + + private void drawIfNeeded() { + if (!mDrawNeeded) { + return; + } + mDrawNeeded = false; + final int dw = mLastDW; + final int dh = mLastDH; + + Rect dirty = new Rect(0, 0, dw, dh); + Canvas c = null; + try { + c = mSurface.lockCanvas(dirty); + } catch (IllegalArgumentException e) { + } catch (Surface.OutOfResourcesException e) { + } + if (c == null) { + return; + } + + // Top + c.clipRect(new Rect(0, 0, dw, mThickness), Region.Op.REPLACE); + c.drawColor(Color.RED); + // Left + c.clipRect(new Rect(0, 0, mThickness, dh), Region.Op.REPLACE); + c.drawColor(Color.RED); + // Right + c.clipRect(new Rect(dw - mThickness, 0, dw, dh), Region.Op.REPLACE); + c.drawColor(Color.RED); + // Bottom + c.clipRect(new Rect(0, dh - mThickness, dw, dh), Region.Op.REPLACE); + c.drawColor(Color.RED); + + mSurface.unlockCanvasAndPost(c); + } + + // Note: caller responsible for being inside + // Surface.openTransaction() / closeTransaction() + public void setVisibility(boolean on) { + if (mSurface == null) { + return; + } + drawIfNeeded(); + if (on) { + mSurface.show(); + } else { + mSurface.hide(); + } + } + + void positionSurface(int dw, int dh) { + if (mLastDW == dw && mLastDH == dh) { + return; + } + mLastDW = dw; + mLastDH = dh; + mSurface.setSize(dw, dh); + mDrawNeeded = true; + } + +} diff --git a/services/java/com/android/server/UsbObserver.java b/services/java/com/android/server/UsbObserver.java index cfa83be..4a7df8f 100644 --- a/services/java/com/android/server/UsbObserver.java +++ b/services/java/com/android/server/UsbObserver.java @@ -24,7 +24,7 @@ import android.net.Uri; import android.os.Handler; import android.os.Message; import android.os.UEventObserver; -import android.provider.Mtp; +import android.provider.Ptp; import android.provider.Settings; import android.util.Log; import android.util.Slog; @@ -155,7 +155,7 @@ class UsbObserver extends UEventObserver { // called from JNI in monitorUsbHostBus() private void usbCameraAdded(int deviceID) { Intent intent = new Intent(Usb.ACTION_USB_CAMERA_ATTACHED, - Mtp.Device.getContentUri(deviceID)); + Ptp.Device.getContentUri(deviceID)); Log.d(TAG, "usbCameraAdded, sending " + intent); mContext.sendBroadcast(intent); } @@ -163,7 +163,7 @@ class UsbObserver extends UEventObserver { // called from JNI in monitorUsbHostBus() private void usbCameraRemoved(int deviceID) { Intent intent = new Intent(Usb.ACTION_USB_CAMERA_DETACHED, - Mtp.Device.getContentUri(deviceID)); + Ptp.Device.getContentUri(deviceID)); Log.d(TAG, "usbCameraRemoved, sending " + intent); mContext.sendBroadcast(intent); } diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 0c20064..b3ea836 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -59,6 +59,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; @@ -200,10 +201,6 @@ public class WindowManagerService extends IWindowManager.Stub */ static final int DEFAULT_FADE_IN_OUT_DURATION = 400; - /** Adjustment to time to perform a dim, to make it more dramatic. - */ - static final int DIM_DURATION_MULTIPLIER = 6; - // Maximum number of milliseconds to wait for input event injection. // FIXME is this value reasonable? private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; @@ -376,6 +373,7 @@ public class WindowManagerService extends IWindowManager.Stub Surface mBlurSurface; boolean mBlurShown; Watermark mWatermark; + StrictModeFlash mStrictModeFlash; int mTransactionSequence = 0; @@ -4883,6 +4881,36 @@ public class WindowManagerService extends IWindowManager.Stub } } + // TODO: more accounting of which pid(s) turned it on, keep count, + // only allow disables from pids which have count on, etc. + public void showStrictModeViolation(boolean on) { + int pid = Binder.getCallingPid(); + synchronized(mWindowMap) { + // Ignoring requests to enable the red border from clients + // which aren't on screen. (e.g. Broadcast Receivers in + // the background..) + if (on) { + boolean isVisible = false; + for (WindowState ws : mWindows) { + if (ws.mSession.mPid == pid && ws.isVisibleLw()) { + isVisible = true; + break; + } + } + if (!isVisible) { + return; + } + } + + Surface.openTransaction(); + if (mStrictModeFlash == null) { + mStrictModeFlash = new StrictModeFlash(mDisplay, mFxSession); + } + mStrictModeFlash.setVisibility(on); + Surface.closeTransaction(); + } + } + public void freezeRotation() { if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION, "setRotation()")) { @@ -6596,6 +6624,7 @@ public class WindowManagerService extends IWindowManager.Stub final Rect mContainingFrame = new Rect(); final Rect mDisplayFrame = new Rect(); final Rect mContentFrame = new Rect(); + final Rect mParentFrame = new Rect(); final Rect mVisibleFrame = new Rect(); boolean mContentChanged; @@ -6801,8 +6830,12 @@ public class WindowManagerService extends IWindowManager.Stub h = mAttrs.height== mAttrs.MATCH_PARENT ? ph : mRequestedHeight; } + if (!mParentFrame.equals(pf)) { + mParentFrame.set(pf); + mContentChanged = true; + } + final Rect content = mContentFrame; - mContentChanged |= !content.equals(cf); content.set(cf); final Rect visible = mVisibleFrame; @@ -7668,6 +7701,21 @@ public class WindowManagerService extends IWindowManager.Stub && !mDrawPending && !mCommitDrawPending; } + /** + * Return whether this window is wanting to have a translation + * animation applied to it for an in-progress move. (Only makes + * sense to call from performLayoutAndPlaceSurfacesLockedInner().) + */ + boolean shouldAnimateMove() { + return mContentChanged && !mAnimating && !mLastHidden && !mDisplayFrozen + && (mFrame.top != mLastFrame.top + || mFrame.left != mLastFrame.left) + && (mAttachedWindow == null + || (mAttachedWindow.mAnimation == null + && !mAttachedWindow.shouldAnimateMove())) + && mPolicy.isScreenOn(); + } + boolean needsBackgroundFiller(int screenWidth, int screenHeight) { return // only if the application is requesting compatible window @@ -7894,6 +7942,8 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(); pw.print(prefix); pw.print("mContainingFrame="); mContainingFrame.printShortString(pw); + pw.print(" mParentFrame="); + mParentFrame.printShortString(pw); pw.print(" mDisplayFrame="); mDisplayFrame.printShortString(pw); pw.println(); @@ -9126,11 +9176,11 @@ public class WindowManagerService extends IWindowManager.Stub || win.mAttachedHidden || win.mExiting || win.mDestroying; - if (!win.mLayoutAttached) { - if (DEBUG_LAYOUT) Slog.v(TAG, "First pass " + win + if (DEBUG_LAYOUT && !win.mLayoutAttached) { + Slog.v(TAG, "First pass " + win + ": gone=" + gone + " mHaveFrame=" + win.mHaveFrame + " mLayoutAttached=" + win.mLayoutAttached); - if (DEBUG_LAYOUT && gone) Slog.v(TAG, " (mViewVisibility=" + if (gone) Slog.v(TAG, " (mViewVisibility=" + win.mViewVisibility + " mRelayoutCalled=" + win.mRelayoutCalled + " hidden=" + win.mRootToken.hidden + " hiddenRequested=" @@ -9167,16 +9217,16 @@ public class WindowManagerService extends IWindowManager.Stub for (i = topAttached; i >= 0; i--) { WindowState win = mWindows.get(i); - // If this view is GONE, then skip it -- keep the current - // frame, and let the caller know so they can ignore it - // if they want. (We do the normal layout for INVISIBLE - // windows, since that means "perform layout as normal, - // just don't display"). if (win.mLayoutAttached) { if (DEBUG_LAYOUT) Slog.v(TAG, "Second pass " + win + " mHaveFrame=" + win.mHaveFrame + " mViewVisibility=" + win.mViewVisibility + " mRelayoutCalled=" + win.mRelayoutCalled); + // If this view is GONE, then skip it -- keep the current + // frame, and let the caller know so they can ignore it + // if they want. (We do the normal layout for INVISIBLE + // windows, since that means "perform layout as normal, + // just don't display"). if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled) || !win.mHaveFrame) { if (initial) { @@ -9198,6 +9248,7 @@ public class WindowManagerService extends IWindowManager.Stub return mPolicy.finishLayoutLw(); } + // "Something has changed! Let's make it correct now." private final void performLayoutAndPlaceSurfacesLockedInner( boolean recoveringMemory) { if (mDisplay == null) { @@ -9249,6 +9300,9 @@ public class WindowManagerService extends IWindowManager.Stub if (mWatermark != null) { mWatermark.positionSurface(dw, dh); } + if (mStrictModeFlash != null) { + mStrictModeFlash.positionSurface(dw, dh); + } try { boolean wallpaperForceHidingChanged = false; @@ -9355,10 +9409,7 @@ public class WindowManagerService extends IWindowManager.Stub // content frame changing, then we'd like to animate // it. The checks here are ordered by what is least // likely to be true first. - if (w.mContentChanged && !wasAnimating && !w.mLastHidden && !mDisplayFrozen - && (w.mFrame.top != w.mLastFrame.top - || w.mFrame.left != w.mLastFrame.left) - && mPolicy.isScreenOn()) { + if (w.shouldAnimateMove()) { // Frame has moved, containing content frame // has also moved, and we're not currently animating... // let's do something. @@ -9367,6 +9418,7 @@ public class WindowManagerService extends IWindowManager.Stub w.setAnimation(a); animDw = w.mLastFrame.left - w.mFrame.left; animDh = w.mLastFrame.top - w.mFrame.top; + w.mContentChanged = false; } // Execute animation. @@ -10230,7 +10282,8 @@ public class WindowManagerService extends IWindowManager.Stub mDimAnimator = new DimAnimator(mFxSession); } mDimAnimator.show(dw, dh); - mDimAnimator.updateParameters(w, currentTime); + mDimAnimator.updateParameters(mContext.getResources(), + w, currentTime); } } if ((attrFlags&FLAG_BLUR_BEHIND) != 0) { @@ -11292,7 +11345,7 @@ public class WindowManagerService extends IWindowManager.Stub * Set's the dim surface's layer and update dim parameters that will be used in * {@link updateSurface} after all windows are examined. */ - void updateParameters(WindowState w, long currentTime) { + void updateParameters(Resources res, WindowState w, long currentTime) { mDimSurface.setLayer(w.mAnimLayer-1); final float target = w.mExiting ? 0 : w.mAttrs.dimAmount; @@ -11306,11 +11359,15 @@ public class WindowManagerService extends IWindowManager.Stub ? w.mAnimation.computeDurationHint() : DEFAULT_DIM_DURATION; if (target > mDimTargetAlpha) { - // This is happening behind the activity UI, - // so we can make it run a little longer to - // give a stronger impression without disrupting - // the user. - duration *= DIM_DURATION_MULTIPLIER; + TypedValue tv = new TypedValue(); + res.getValue(com.android.internal.R.fraction.config_dimBehindFadeDuration, + tv, true); + if (tv.type == TypedValue.TYPE_FRACTION) { + duration = (long)tv.getFraction((float)duration, (float)duration); + } else if (tv.type >= TypedValue.TYPE_FIRST_INT + && tv.type <= TypedValue.TYPE_LAST_INT) { + duration = tv.data; + } } if (duration < 1) { // Don't divide by zero diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index e815524..1a10cff 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -557,7 +557,7 @@ public final class ActivityManagerService extends ActivityManagerNative = new HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>(); /** - * Fingerprints (String.hashCode()) of stack traces that we've + * Fingerprints (hashCode()) of stack traces that we've * already logged DropBox entries for. Guarded by itself. If * something (rogue user app) forces this over * MAX_DUP_SUPPRESSED_STACKS entries, the contents are cleared. @@ -2026,7 +2026,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo, - null, null, 0, 0, 0, false, false); + null, null, 0, 0, 0, false, false, null); } } @@ -2082,7 +2082,7 @@ public final class ActivityManagerService extends ActivityManagerNative intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); mMainStack.startActivityLocked(null, intent, null, null, 0, ri.activityInfo, - null, null, 0, 0, 0, false, false); + null, null, 0, 0, 0, false, false, null); } } } @@ -2121,13 +2121,13 @@ public final class ActivityManagerService extends ActivityManagerNative } mPendingActivityLaunches.clear(); } - + public final int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { - return mMainStack.startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, null); } @@ -2138,7 +2138,7 @@ public final class ActivityManagerService extends ActivityManagerNative String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { WaitResult res = new WaitResult(); - mMainStack.startActivityMayWait(caller, intent, resolvedType, + mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, res, null); return res; @@ -2149,12 +2149,12 @@ public final class ActivityManagerService extends ActivityManagerNative int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, Configuration config) { - return mMainStack.startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, config); } - public int startActivityIntentSender(IApplicationThread caller, + public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues) { @@ -2267,7 +2267,7 @@ public final class ActivityManagerService extends ActivityManagerNative // those are not yet exposed to user code, so there is no need. int res = mMainStack.startActivityLocked(r.app.thread, intent, r.resolvedType, null, 0, aInfo, resultTo, resultWho, - requestCode, -1, r.launchedFromUid, false, false); + requestCode, -1, r.launchedFromUid, false, false, null); Binder.restoreCallingIdentity(origId); r.finishing = wasFinishing; @@ -2289,38 +2289,28 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException( "startActivityInPackage only available to the system"); } - - final boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - // Collect information about the target of the Intent. - ActivityInfo aInfo; - try { - ResolveInfo rInfo = - AppGlobals.getPackageManager().resolveIntent( - intent, resolvedType, - PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS); - aInfo = rInfo != null ? rInfo.activityInfo : null; - } catch (RemoteException e) { - aInfo = null; - } + return mMainStack.startActivityMayWait(null, uid, intent, resolvedType, + null, 0, resultTo, resultWho, requestCode, onlyIfNeeded, false, null, null); + } - if (aInfo != null) { - // Store the found target back into the intent, because now that - // we have it we never want to do this again. For example, if the - // user navigates back to this point in the history, we should - // always restart the exact same activity. - intent.setComponent(new ComponentName( - aInfo.applicationInfo.packageName, aInfo.name)); - } + public final int startActivities(IApplicationThread caller, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + return mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo); + } - synchronized(this) { - return mMainStack.startActivityLocked(null, intent, resolvedType, - null, 0, aInfo, resultTo, resultWho, requestCode, -1, uid, - onlyIfNeeded, componentSpecified); + public final int startActivitiesInPackage(int uid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + + // This is so super not safe, that only the system (or okay root) + // can do it. + final int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.myUid()) { + throw new SecurityException( + "startActivityInPackage only available to the system"); } + + return mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo); } final void addRecentTaskLocked(TaskRecord task) { @@ -3890,16 +3880,30 @@ public final class ActivityManagerService extends ActivityManagerNative public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) { + int requestCode, Intent[] intents, String[] resolvedTypes, int flags) { // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors() == true) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - if (type == INTENT_SENDER_BROADCAST) { - if ((intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + if (intents != null) { + if (intents.length < 1) { + throw new IllegalArgumentException("Intents array length must be >= 1"); + } + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + throw new IllegalArgumentException("Null intent at index " + i); + } + if (intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + if (type == INTENT_SENDER_BROADCAST && + (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + throw new IllegalArgumentException( + "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + } + intents[i] = new Intent(intent); + } + if (resolvedTypes != null && resolvedTypes.length != intents.length) { throw new IllegalArgumentException( - "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + "Intent array length does not match resolvedTypes length"); } } @@ -3922,7 +3926,7 @@ public final class ActivityManagerService extends ActivityManagerNative } return getIntentSenderLocked(type, packageName, callingUid, - token, resultWho, requestCode, intent, resolvedType, flags); + token, resultWho, requestCode, intents, resolvedTypes, flags); } catch (RemoteException e) { throw new SecurityException(e); @@ -3932,7 +3936,7 @@ public final class ActivityManagerService extends ActivityManagerNative IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) { + int requestCode, Intent[] intents, String[] resolvedTypes, int flags) { ActivityRecord activity = null; if (type == INTENT_SENDER_ACTIVITY_RESULT) { int index = mMainStack.indexOfTokenLocked(token); @@ -3953,14 +3957,24 @@ public final class ActivityManagerService extends ActivityManagerNative PendingIntentRecord.Key key = new PendingIntentRecord.Key( type, packageName, activity, resultWho, - requestCode, intent, resolvedType, flags); + requestCode, intents, resolvedTypes, flags); WeakReference<PendingIntentRecord> ref; ref = mIntentSenderRecords.get(key); PendingIntentRecord rec = ref != null ? ref.get() : null; if (rec != null) { if (!cancelCurrent) { if (updateCurrent) { - rec.key.requestIntent.replaceExtras(intent); + if (rec.key.requestIntent != null) { + rec.key.requestIntent.replaceExtras(intents != null ? intents[0] : null); + } + if (intents != null) { + intents[intents.length-1] = rec.key.requestIntent; + rec.key.allIntents = intents; + rec.key.allResolvedTypes = resolvedTypes; + } else { + rec.key.allIntents = null; + rec.key.allResolvedTypes = null; + } } return rec; } @@ -5006,7 +5020,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * TODO: Add mController hook */ - public void moveTaskToFront(int task) { + public void moveTaskToFront(int task, int flags) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToFront()"); @@ -5021,6 +5035,11 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); if (tr.taskId == task) { + if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just move the home task to the top first. + mMainStack.moveHomeToFrontLocked(); + } mMainStack.moveTaskToFrontLocked(tr, null); return; } @@ -5028,6 +5047,11 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i); if (hr.task.taskId == task) { + if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just move the home task to the top first. + mMainStack.moveHomeToFrontLocked(); + } mMainStack.moveTaskToFrontLocked(hr.task, null); return; } @@ -6647,7 +6671,7 @@ public final class ActivityManagerService extends ActivityManagerNative ProcessRecord r = findAppProcess(app); if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) { - Integer stackFingerprint = info.crashInfo.stackTrace.hashCode(); + Integer stackFingerprint = info.hashCode(); boolean logIt = true; synchronized (mAlreadyLoggedViolatedStacks) { if (mAlreadyLoggedViolatedStacks.contains(stackFingerprint)) { diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 51dc84e..b4ea036 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -99,8 +99,8 @@ public class ActivityStack { static final int DESTROY_TIMEOUT = 10*1000; // How long until we reset a task when the user returns to it. Currently - // 30 minutes. - static final long ACTIVITY_INACTIVE_RESET_TIME = 1000*60*30; + // disabled. + static final long ACTIVITY_INACTIVE_RESET_TIME = 0; // How long between activity launches that we consider safe to not warn // the user about an unexpected activity being launched on top. @@ -1487,7 +1487,8 @@ public class ActivityStack { ActivityRecord newActivity) { boolean forceReset = (newActivity.info.flags &ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; - if (taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { + if (ACTIVITY_INACTIVE_RESET_TIME > 0 + && taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { if ((newActivity.info.flags &ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) { forceReset = true; @@ -1573,8 +1574,7 @@ public class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - target.task = new TaskRecord(mService.mCurTask, target.info, null, - (target.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); + target.task = new TaskRecord(mService.mCurTask, target.info, null); target.task.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to new task " + target.task); @@ -1776,11 +1776,11 @@ public class ActivityStack { * activities on top of it and return the instance. * * @param newR Description of the new activity being started. - * @return Returns the old activity that should be continue to be used, + * @return Returns the old activity that should be continued to be used, * or null if none was found. */ private final ActivityRecord performClearTaskLocked(int taskId, - ActivityRecord newR, int launchFlags, boolean doClear) { + ActivityRecord newR, int launchFlags) { int i = mHistory.size(); // First find the requested task. @@ -1806,17 +1806,18 @@ public class ActivityStack { if (r.realActivity.equals(newR.realActivity)) { // Here it is! Now finish everything in front... ActivityRecord ret = r; - if (doClear) { - while (i < (mHistory.size()-1)) { - i++; - r = (ActivityRecord)mHistory.get(i); - if (r.finishing) { - continue; - } - if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear")) { - i--; - } + while (i < (mHistory.size()-1)) { + i++; + r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId != taskId) { + break; + } + if (r.finishing) { + continue; + } + if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "clear")) { + i--; } } @@ -1843,6 +1844,51 @@ public class ActivityStack { } /** + * Completely remove all activities associated with an existing task. + */ + private final void performClearTaskLocked(int taskId) { + int i = mHistory.size(); + + // First find the requested task. + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId == taskId) { + i++; + break; + } + } + + // Now clear it. + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.finishing) { + continue; + } + if (r.task.taskId != taskId) { + // We hit the bottom. Now finish it all... + while (i < (mHistory.size()-1)) { + i++; + r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId != taskId) { + // Whoops hit the end. + return; + } + if (r.finishing) { + continue; + } + if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "clear")) { + i--; + } + } + return; + } + } + } + + /** * Find the activity in the history stack within the given task. Returns * the index within the history at which it's found, or < 0 if not found. */ @@ -1882,7 +1928,7 @@ public class ActivityStack { int grantedMode, ActivityInfo aInfo, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, boolean onlyIfNeeded, - boolean componentSpecified) { + boolean componentSpecified, ActivityRecord[] outActivity) { int err = START_SUCCESS; @@ -2004,6 +2050,9 @@ public class ActivityStack { ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified); + if (outActivity != null) { + outActivity[0] = r; + } if (mMainStack) { if (mResumedActivity == null @@ -2038,6 +2087,16 @@ public class ActivityStack { grantedUriPermissions, grantedMode, onlyIfNeeded, true); } + final void moveHomeToFrontFromLaunchLocked(int launchFlags) { + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) + == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) { + // Caller wants to appear on home activity, so before starting + // their own activity we will bring home to the front. + moveHomeToFrontLocked(); + } + } + final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord, Uri[] grantedUriPermissions, int grantedMode, boolean onlyIfNeeded, boolean doResume) { @@ -2111,6 +2170,7 @@ public class ActivityStack { } boolean addingToTask = false; + TaskRecord reuseTask = null; if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK @@ -2148,6 +2208,7 @@ public class ActivityStack { if (callerAtFront) { // We really do want to push this one into the // user's face, right now. + moveHomeToFrontFromLaunchLocked(launchFlags); moveTaskToFrontLocked(taskTop.task, r); } } @@ -2166,7 +2227,16 @@ public class ActivityStack { } return START_RETURN_INTENT_TO_CALLER; } - if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) + == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) { + // The caller has requested to completely replace any + // exising task with its new activity. Well that should + // not be too hard... + reuseTask = taskTop.task; + performClearTaskLocked(taskTop.task.taskId); + reuseTask.setIntent(r.intent, r.info); + } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { // In this situation we want to remove all activities @@ -2174,7 +2244,7 @@ public class ActivityStack { // cases this means we are resetting the task to its // initial state. ActivityRecord top = performClearTaskLocked( - taskTop.task.taskId, r, launchFlags, true); + taskTop.task.taskId, r, launchFlags); if (top != null) { if (top.frontOfTask) { // Activity aliases may mean we use different @@ -2235,7 +2305,7 @@ public class ActivityStack { // for now we'll just drop it. taskTop.task.setIntent(r.intent, r.info); } - if (!addingToTask) { + if (!addingToTask && reuseTask == null) { // We didn't do anything... but it was needed (a.k.a., client // don't use that intent!) And for paranoia, make // sure we have correctly resumed the top activity. @@ -2298,19 +2368,23 @@ public class ActivityStack { // Should this be considered a new task? if (r.resultTo == null && !addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { - // todo: should do better management of integers. - mService.mCurTask++; - if (mService.mCurTask <= 0) { - mService.mCurTask = 1; + if (reuseTask == null) { + // todo: should do better management of integers. + mService.mCurTask++; + if (mService.mCurTask <= 0) { + mService.mCurTask = 1; + } + r.task = new TaskRecord(mService.mCurTask, r.info, intent); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in new task " + r.task); + } else { + r.task = reuseTask; } - r.task = new TaskRecord(mService.mCurTask, r.info, intent, - (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r - + " in new task " + r.task); newTask = true; if (mMainStack) { mService.addRecentTaskLocked(r.task); } + moveHomeToFrontFromLaunchLocked(launchFlags); } else if (sourceRecord != null) { if (!addingToTask && @@ -2319,7 +2393,7 @@ public class ActivityStack { // task, but the caller has asked to clear that task if the // activity is already running. ActivityRecord top = performClearTaskLocked( - sourceRecord.task.taskId, r, launchFlags, true); + sourceRecord.task.taskId, r, launchFlags); if (top != null) { logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); top.deliverNewIntentLocked(callingUid, r.intent); @@ -2361,9 +2435,8 @@ public class ActivityStack { ActivityRecord prev = N > 0 ? (ActivityRecord)mHistory.get(N-1) : null; r.task = prev != null - ? prev.task - : new TaskRecord(mService.mCurTask, r.info, intent, - (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); + ? prev.task + : new TaskRecord(mService.mCurTask, r.info, intent); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new guessed " + r.task); } @@ -2386,21 +2459,7 @@ public class ActivityStack { return START_SUCCESS; } - final int startActivityMayWait(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, - boolean debug, WaitResult outResult, Configuration config) { - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - + ActivityInfo resolveActivity(Intent intent, String resolvedType, boolean debug) { // Collect information about the target of the Intent. ActivityInfo aInfo; try { @@ -2429,11 +2488,32 @@ public class ActivityStack { } } } + return aInfo; + } + + final int startActivityMayWait(IApplicationThread caller, int callingUid, + Intent intent, String resolvedType, Uri[] grantedUriPermissions, + int grantedMode, IBinder resultTo, + String resultWho, int requestCode, boolean onlyIfNeeded, + boolean debug, WaitResult outResult, Configuration config) { + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedType, debug); synchronized (mService) { int callingPid; - int callingUid; - if (caller == null) { + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { callingPid = Binder.getCallingPid(); callingUid = Binder.getCallingUid(); } else { @@ -2472,8 +2552,8 @@ public class ActivityStack { IIntentSender target = mService.getIntentSenderLocked( IActivityManager.INTENT_SENDER_ACTIVITY, "android", - realCallingUid, null, null, 0, intent, - resolvedType, PendingIntent.FLAG_CANCEL_CURRENT + realCallingUid, null, null, 0, new Intent[] { intent }, + new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); Intent newIntent = new Intent(); @@ -2518,7 +2598,7 @@ public class ActivityStack { int res = startActivityLocked(caller, intent, resolvedType, grantedUriPermissions, grantedMode, aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, - onlyIfNeeded, componentSpecified); + onlyIfNeeded, componentSpecified, null); if (mConfigWillChange && mMainStack) { // If the caller also wants to switch to a new configuration, @@ -2569,6 +2649,75 @@ public class ActivityStack { } } + final int startActivities(IApplicationThread caller, int callingUid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + if (intents == null) { + throw new NullPointerException("intents is null"); + } + if (resolvedTypes == null) { + throw new NullPointerException("resolvedTypes is null"); + } + if (intents.length != resolvedTypes.length) { + throw new IllegalArgumentException("intents are length different than resolvedTypes"); + } + + ActivityRecord[] outActivity = new ActivityRecord[1]; + + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mService) { + + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + continue; + } + + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], false); + + if (mMainStack && aInfo != null && (aInfo.applicationInfo.flags + & ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { + throw new IllegalArgumentException( + "FLAG_CANT_SAVE_STATE not supported here"); + } + + int res = startActivityLocked(caller, intent, resolvedTypes[i], + null, 0, aInfo, resultTo, null, -1, callingPid, callingUid, + false, componentSpecified, outActivity); + if (res < 0) { + return res; + } + + resultTo = outActivity[0]; + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return IActivityManager.START_SUCCESS; + } + void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, long thisTime, long totalTime) { for (int i=mWaitingActivityLaunched.size()-1; i>=0; i--) { @@ -3252,6 +3401,24 @@ public class ActivityStack { removeHistoryRecordsForAppLocked(mFinishingActivities, app); } + /** + * Move the current home activity's task (if one exists) to the front + * of the stack. + */ + final void moveHomeToFrontLocked() { + TaskRecord homeTask = null; + for (int i=mHistory.size()-1; i>=0; i--) { + ActivityRecord hr = (ActivityRecord)mHistory.get(i); + if (hr.isHomeActivity) { + homeTask = hr.task; + } + } + if (homeTask != null) { + moveTaskToFrontLocked(homeTask, null); + } + } + + final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason) { if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index 7a85eb8..ee6e420 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -47,20 +47,24 @@ class PendingIntentRecord extends IIntentSender.Stub { final int requestCode; final Intent requestIntent; final String requestResolvedType; + Intent[] allIntents; + String[] allResolvedTypes; final int flags; final int hashCode; private static final int ODD_PRIME_NUMBER = 37; Key(int _t, String _p, ActivityRecord _a, String _w, - int _r, Intent _i, String _it, int _f) { + int _r, Intent[] _i, String[] _it, int _f) { type = _t; packageName = _p; activity = _a; who = _w; requestCode = _r; - requestIntent = _i; - requestResolvedType = _it; + requestIntent = _i != null ? _i[_i.length-1] : null; + requestResolvedType = _it != null ? _it[_it.length-1] : null; + allIntents = _i; + allResolvedTypes = _it; flags = _f; int hash = 23; @@ -72,11 +76,11 @@ class PendingIntentRecord extends IIntentSender.Stub { if (_a != null) { hash = (ODD_PRIME_NUMBER*hash) + _a.hashCode(); } - if (_i != null) { - hash = (ODD_PRIME_NUMBER*hash) + _i.filterHashCode(); + if (requestIntent != null) { + hash = (ODD_PRIME_NUMBER*hash) + requestIntent.filterHashCode(); } - if (_it != null) { - hash = (ODD_PRIME_NUMBER*hash) + _it.hashCode(); + if (requestResolvedType != null) { + hash = (ODD_PRIME_NUMBER*hash) + requestResolvedType.hashCode(); } hash = (ODD_PRIME_NUMBER*hash) + _p.hashCode(); hash = (ODD_PRIME_NUMBER*hash) + _t; @@ -209,9 +213,24 @@ class PendingIntentRecord extends IIntentSender.Stub { switch (key.type) { case IActivityManager.INTENT_SENDER_ACTIVITY: try { - owner.startActivityInPackage(uid, - finalIntent, resolvedType, - resultTo, resultWho, requestCode, false); + if (key.allIntents != null && key.allIntents.length > 1) { + Intent[] allIntents = new Intent[key.allIntents.length]; + String[] allResolvedTypes = new String[key.allIntents.length]; + System.arraycopy(key.allIntents, 0, allIntents, 0, + key.allIntents.length); + if (key.allResolvedTypes != null) { + System.arraycopy(key.allResolvedTypes, 0, allResolvedTypes, 0, + key.allResolvedTypes.length); + } + allIntents[allIntents.length-1] = finalIntent; + allResolvedTypes[allResolvedTypes.length-1] = resolvedType; + owner.startActivitiesInPackage(uid, allIntents, + allResolvedTypes, resultTo); + } else { + owner.startActivityInPackage(uid, + finalIntent, resolvedType, + resultTo, resultWho, requestCode, false); + } } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Unable to send startActivity intent", e); diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index bcb8f54..09d9c3b6 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -26,7 +26,6 @@ import java.io.PrintWriter; class TaskRecord { final int taskId; // Unique identifier for this task. final String affinity; // The affinity name for this task, or null. - final boolean clearOnBackground; // As per the original activity. Intent intent; // The original intent that started the task. Intent affinityIntent; // Intent of affinity-moved activity that started this task. ComponentName origActivity; // The non-alias activity component of the intent. @@ -38,11 +37,9 @@ class TaskRecord { String stringName; // caching of toString() result. - TaskRecord(int _taskId, ActivityInfo info, Intent _intent, - boolean _clearOnBackground) { + TaskRecord(int _taskId, ActivityInfo info, Intent _intent) { taskId = _taskId; affinity = info.taskAffinity; - clearOnBackground = _clearOnBackground; setIntent(_intent, info); } @@ -86,9 +83,8 @@ class TaskRecord { } void dump(PrintWriter pw, String prefix) { - if (clearOnBackground || numActivities != 0 || rootWasReset) { - pw.print(prefix); pw.print("clearOnBackground="); pw.print(clearOnBackground); - pw.print(" numActivities="); pw.print(numActivities); + if (numActivities != 0 || rootWasReset) { + pw.print(prefix); pw.print("numActivities="); pw.print(numActivities); pw.print(" rootWasReset="); pw.println(rootWasReset); } if (affinity != null) { diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java new file mode 100644 index 0000000..3543275 --- /dev/null +++ b/telephony/java/android/telephony/SmsCbMessage.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.gsm.SmsCbHeader; + +import java.io.UnsupportedEncodingException; + +/** + * Describes an SMS-CB message. + * + * {@hide} + */ +public class SmsCbMessage { + + /** + * Cell wide immediate geographical scope + */ + public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0; + + /** + * PLMN wide geographical scope + */ + public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1; + + /** + * Location / service area wide geographical scope + */ + public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2; + + /** + * Cell wide geographical scope + */ + public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3; + + /** + * Create an instance of this class from a received PDU + * + * @param pdu PDU bytes + * @return An instance of this class, or null if invalid pdu + */ + public static SmsCbMessage createFromPdu(byte[] pdu) { + try { + return new SmsCbMessage(pdu); + } catch (IllegalArgumentException e) { + return null; + } + } + + /** + * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5. + */ + private static final String[] LANGUAGE_CODES_GROUP_0 = { + "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu", + "pl", null + }; + + /** + * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5. + */ + private static final String[] LANGUAGE_CODES_GROUP_2 = { + "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null, + null, null + }; + + private static final char CARRIAGE_RETURN = 0x0d; + + private SmsCbHeader mHeader; + + private String mLanguage; + + private String mBody; + + private SmsCbMessage(byte[] pdu) throws IllegalArgumentException { + mHeader = new SmsCbHeader(pdu); + parseBody(pdu); + } + + /** + * Return the geographical scope of this message, one of + * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE}, + * {@link #GEOGRAPHICAL_SCOPE_PLMN_WIDE}, + * {@link #GEOGRAPHICAL_SCOPE_LA_WIDE}, + * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE} + * + * @return Geographical scope + */ + public int getGeographicalScope() { + return mHeader.geographicalScope; + } + + /** + * Get the ISO-639-1 language code for this message, or null if unspecified + * + * @return Language code + */ + public String getLanguageCode() { + return mLanguage; + } + + /** + * Get the body of this message, or null if no body available + * + * @return Body, or null + */ + public String getMessageBody() { + return mBody; + } + + /** + * Get the message identifier of this message (0-65535) + * + * @return Message identifier + */ + public int getMessageIdentifier() { + return mHeader.messageIdentifier; + } + + /** + * Get the message code of this message (0-1023) + * + * @return Message code + */ + public int getMessageCode() { + return mHeader.messageCode; + } + + /** + * Get the update number of this message (0-15) + * + * @return Update number + */ + public int getUpdateNumber() { + return mHeader.updateNumber; + } + + private void parseBody(byte[] pdu) { + int encoding; + boolean hasLanguageIndicator = false; + + // Extract encoding and language from DCS, as defined in 3gpp TS 23.038, + // section 5. + switch ((mHeader.dataCodingScheme & 0xf0) >> 4) { + case 0x00: + encoding = SmsMessage.ENCODING_7BIT; + mLanguage = LANGUAGE_CODES_GROUP_0[mHeader.dataCodingScheme & 0x0f]; + break; + + case 0x01: + hasLanguageIndicator = true; + if ((mHeader.dataCodingScheme & 0x0f) == 0x01) { + encoding = SmsMessage.ENCODING_16BIT; + } else { + encoding = SmsMessage.ENCODING_7BIT; + } + break; + + case 0x02: + encoding = SmsMessage.ENCODING_7BIT; + mLanguage = LANGUAGE_CODES_GROUP_2[mHeader.dataCodingScheme & 0x0f]; + break; + + case 0x03: + encoding = SmsMessage.ENCODING_7BIT; + break; + + case 0x04: + case 0x05: + switch ((mHeader.dataCodingScheme & 0x0c) >> 2) { + case 0x01: + encoding = SmsMessage.ENCODING_8BIT; + break; + + case 0x02: + encoding = SmsMessage.ENCODING_16BIT; + break; + + case 0x00: + default: + encoding = SmsMessage.ENCODING_7BIT; + break; + } + break; + + case 0x06: + case 0x07: + // Compression not supported + case 0x09: + // UDH structure not supported + case 0x0e: + // Defined by the WAP forum not supported + encoding = SmsMessage.ENCODING_UNKNOWN; + break; + + case 0x0f: + if (((mHeader.dataCodingScheme & 0x04) >> 2) == 0x01) { + encoding = SmsMessage.ENCODING_8BIT; + } else { + encoding = SmsMessage.ENCODING_7BIT; + } + break; + + default: + // Reserved values are to be treated as 7-bit + encoding = SmsMessage.ENCODING_7BIT; + break; + } + + switch (encoding) { + case SmsMessage.ENCODING_7BIT: + mBody = GsmAlphabet.gsm7BitPackedToString(pdu, SmsCbHeader.PDU_HEADER_LENGTH, + (pdu.length - SmsCbHeader.PDU_HEADER_LENGTH) * 8 / 7); + + if (hasLanguageIndicator && mBody != null && mBody.length() > 2) { + mLanguage = mBody.substring(0, 2); + mBody = mBody.substring(3); + } + break; + + case SmsMessage.ENCODING_16BIT: + int offset = SmsCbHeader.PDU_HEADER_LENGTH; + + if (hasLanguageIndicator && pdu.length >= SmsCbHeader.PDU_HEADER_LENGTH + 2) { + mLanguage = GsmAlphabet.gsm7BitPackedToString(pdu, + SmsCbHeader.PDU_HEADER_LENGTH, 2); + offset += 2; + } + + try { + mBody = new String(pdu, offset, (pdu.length & 0xfffe) - offset, "utf-16"); + } catch (UnsupportedEncodingException e) { + // Eeeek + } + break; + + default: + break; + } + + if (mBody != null) { + // Remove trailing carriage return + for (int i = mBody.length() - 1; i >= 0; i--) { + if (mBody.charAt(i) != CARRIAGE_RETURN) { + mBody = mBody.substring(0, i + 1); + break; + } + } + } else { + mBody = ""; + } + } +} diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 953696b..6a346af 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -338,7 +338,67 @@ public final class SmsManager { } return createMessageListFromRawRecords(records); - } + } + + /** + * Enable reception of cell broadcast (SMS-CB) messages with the given + * message identifier. Note that if two different clients enable the same + * message identifier, they must both disable it for the device to stop + * receiving those messages. All received messages will be broadcast in an + * intent with the action "android.provider.telephony.SMS_CB_RECEIVED". + * Note: This call is blocking, callers may want to avoid calling it from + * the main thread of an application. + * + * @param messageIdentifier Message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * @see #disableCellBroadcast(int) + * + * {@hide} + */ + public boolean enableCellBroadcast(int messageIdentifier) { + boolean success = false; + + try { + ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); + if (iccISms != null) { + success = iccISms.enableCellBroadcast(messageIdentifier); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } + + /** + * Disable reception of cell broadcast (SMS-CB) messages with the given + * message identifier. Note that if two different clients enable the same + * message identifier, they must both disable it for the device to stop + * receiving those messages. + * Note: This call is blocking, callers may want to avoid calling it from + * the main thread of an application. + * + * @param messageIdentifier Message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * + * @see #enableCellBroadcast(int) + * + * {@hide} + */ + public boolean disableCellBroadcast(int messageIdentifier) { + boolean success = false; + + try { + ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); + if (iccISms != null) { + success = iccISms.disableCellBroadcast(messageIdentifier); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } /** * Create a list of <code>SmsMessage</code>s from a list of RawSmsData diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl index 65bad96..90de5e1 100644 --- a/telephony/java/com/android/internal/telephony/ISms.aidl +++ b/telephony/java/com/android/internal/telephony/ISms.aidl @@ -144,4 +144,30 @@ interface ISms { in List<String> parts, in List<PendingIntent> sentIntents, in List<PendingIntent> deliveryIntents); + /** + * Enable reception of cell broadcast (SMS-CB) messages with the given + * message identifier. Note that if two different clients enable the same + * message identifier, they must both disable it for the device to stop + * receiving those messages. + * + * @param messageIdentifier Message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * + * @see #disableCellBroadcast(int) + */ + boolean enableCellBroadcast(int messageIdentifier); + + /** + * Disable reception of cell broadcast (SMS-CB) messages with the given + * message identifier. Note that if two different clients enable the same + * message identifier, they must both disable it for the device to stop + * receiving those messages. + * + * @param messageIdentifier Message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * + * @see #enableCellBroadcast(int) + */ + boolean disableCellBroadcast(int messageIdentifier); + } diff --git a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java index 1910a9c..5049249 100644 --- a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java +++ b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java @@ -68,4 +68,12 @@ public class IccSmsInterfaceManagerProxy extends ISms.Stub { parts, sentIntents, deliveryIntents); } + public boolean enableCellBroadcast(int messageIdentifier) throws android.os.RemoteException { + return mIccSmsInterfaceManager.enableCellBroadcast(messageIdentifier); + } + + public boolean disableCellBroadcast(int messageIdentifier) throws android.os.RemoteException { + return mIccSmsInterfaceManager.disableCellBroadcast(messageIdentifier); + } + } diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java index 3a7ce47..ec49a19 100644 --- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java @@ -112,6 +112,9 @@ public abstract class SMSDispatcher extends Handler { /** Radio is ON */ static final protected int EVENT_RADIO_ON = 12; + /** New broadcast SMS */ + static final protected int EVENT_NEW_BROADCAST_SMS = 13; + protected Phone mPhone; protected Context mContext; protected ContentResolver mResolver; @@ -390,6 +393,9 @@ public abstract class SMSDispatcher extends Handler { mCm.reportSmsMemoryStatus(mStorageAvailable, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE)); } + + case EVENT_NEW_BROADCAST_SMS: + handleBroadcastSms((AsyncResult)msg.obj); break; } } @@ -985,4 +991,17 @@ public abstract class SMSDispatcher extends Handler { } } }; + + protected abstract void handleBroadcastSms(AsyncResult ar); + + protected void dispatchBroadcastPdus(byte[][] pdus) { + Intent intent = new Intent("android.provider.telephony.SMS_CB_RECEIVED"); + intent.putExtra("pdus", pdus); + + if (Config.LOGD) + Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus"); + + dispatch(intent, "android.permission.RECEIVE_SMS"); + } + } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java index 37a33bc..53555d8 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java @@ -494,6 +494,11 @@ final class CdmaSMSDispatcher extends SMSDispatcher { mCm.setCdmaBroadcastConfig(configValuesArray, response); } + protected void handleBroadcastSms(AsyncResult ar) { + // Not supported + Log.e(TAG, "Error! Not implemented for CDMA."); + } + private int resultToCause(int rc) { switch (rc) { case Activity.RESULT_OK: diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java index d84b6ab..29f3bc1 100644 --- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java +++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java @@ -191,6 +191,18 @@ public class RuimSmsInterfaceManager extends IccSmsInterfaceManager { return mSms; } + public boolean enableCellBroadcast(int messageIdentifier) { + // Not implemented + Log.e(LOG_TAG, "Error! Not implemented for CDMA."); + return false; + } + + public boolean disableCellBroadcast(int messageIdentifier) { + // Not implemented + Log.e(LOG_TAG, "Error! Not implemented for CDMA."); + return false; + } + protected void log(String msg) { Log.d(LOG_TAG, "[RuimSmsInterfaceManager] " + msg); } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java index ed7066b..70f8f86 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java @@ -22,20 +22,26 @@ import android.app.PendingIntent.CanceledException; import android.content.Intent; import android.os.AsyncResult; import android.os.Message; +import android.os.SystemProperties; import android.provider.Telephony.Sms.Intents; import android.telephony.ServiceState; +import android.telephony.SmsCbMessage; +import android.telephony.gsm.GsmCellLocation; import android.util.Config; import android.util.Log; +import com.android.internal.telephony.BaseCommands; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; +import com.android.internal.telephony.TelephonyProperties; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import static android.telephony.SmsMessage.MessageClass; @@ -47,6 +53,8 @@ final class GsmSMSDispatcher extends SMSDispatcher { GsmSMSDispatcher(GSMPhone phone) { super(phone); mGsmPhone = phone; + + ((BaseCommands)mCm).setOnNewGsmBroadcastSms(this, EVENT_NEW_BROADCAST_SMS, null); } /** @@ -394,4 +402,162 @@ final class GsmSMSDispatcher extends SMSDispatcher { return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR; } } + + /** + * Holds all info about a message page needed to assemble a complete + * concatenated message + */ + private static final class SmsCbConcatInfo { + private final SmsCbHeader mHeader; + + private final String mPlmn; + + private final int mLac; + + private final int mCid; + + public SmsCbConcatInfo(SmsCbHeader header, String plmn, int lac, int cid) { + mHeader = header; + mPlmn = plmn; + mLac = lac; + mCid = cid; + } + + @Override + public int hashCode() { + return mHeader.messageIdentifier * 31 + mHeader.updateNumber; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SmsCbConcatInfo) { + SmsCbConcatInfo other = (SmsCbConcatInfo)obj; + + // Two pages match if all header attributes (except the page + // index) are identical, and both pages belong to the same + // location (which is also determined by the scope parameter) + if (mHeader.geographicalScope == other.mHeader.geographicalScope + && mHeader.messageCode == other.mHeader.messageCode + && mHeader.updateNumber == other.mHeader.updateNumber + && mHeader.messageIdentifier == other.mHeader.messageIdentifier + && mHeader.dataCodingScheme == other.mHeader.dataCodingScheme + && mHeader.nrOfPages == other.mHeader.nrOfPages) { + return matchesLocation(other.mPlmn, other.mLac, other.mCid); + } + } + + return false; + } + + /** + * Checks if this concatenation info matches the given location. The + * granularity of the match depends on the geographical scope. + * + * @param plmn PLMN + * @param lac Location area code + * @param cid Cell ID + * @return true if matching, false otherwise + */ + public boolean matchesLocation(String plmn, int lac, int cid) { + switch (mHeader.geographicalScope) { + case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: + case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: + if (mCid != cid) { + return false; + } + // deliberate fall-through + case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE: + if (mLac != lac) { + return false; + } + // deliberate fall-through + case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: + return mPlmn != null && mPlmn.equals(plmn); + } + + return false; + } + } + + // This map holds incomplete concatenated messages waiting for assembly + private HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap = + new HashMap<SmsCbConcatInfo, byte[][]>(); + + protected void handleBroadcastSms(AsyncResult ar) { + try { + byte[][] pdus = null; + byte[] receivedPdu = (byte[])ar.result; + + if (Config.LOGD) { + for (int i = 0; i < receivedPdu.length; i += 8) { + StringBuilder sb = new StringBuilder("SMS CB pdu data: "); + for (int j = i; j < i + 8 && j < receivedPdu.length; j++) { + int b = receivedPdu[j] & 0xff; + if (b < 0x10) { + sb.append("0"); + } + sb.append(Integer.toHexString(b)).append(" "); + } + Log.d(TAG, sb.toString()); + } + } + + SmsCbHeader header = new SmsCbHeader(receivedPdu); + String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC); + GsmCellLocation cellLocation = (GsmCellLocation)mGsmPhone.getCellLocation(); + int lac = cellLocation.getLac(); + int cid = cellLocation.getCid(); + + if (header.nrOfPages > 1) { + // Multi-page message + SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, plmn, lac, cid); + + // Try to find other pages of the same message + pdus = mSmsCbPageMap.get(concatInfo); + + if (pdus == null) { + // This it the first page of this message, make room for all + // pages and keep until complete + pdus = new byte[header.nrOfPages][]; + + mSmsCbPageMap.put(concatInfo, pdus); + } + + // Page parameter is one-based + pdus[header.pageIndex - 1] = receivedPdu; + + for (int i = 0; i < pdus.length; i++) { + if (pdus[i] == null) { + // Still missing pages, exit + return; + } + } + + // Message complete, remove and dispatch + mSmsCbPageMap.remove(concatInfo); + } else { + // Single page message + pdus = new byte[1][]; + pdus[0] = receivedPdu; + } + + dispatchBroadcastPdus(pdus); + + // Remove messages that are out of scope to prevent the map from + // growing indefinitely, containing incomplete messages that were + // never assembled + Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator(); + + while (iter.hasNext()) { + SmsCbConcatInfo info = iter.next(); + + if (!info.matchesLocation(plmn, lac, cid)) { + iter.remove(); + } + } + } catch (RuntimeException e) { + Log.e(TAG, "Error in decoding SMS CB pdu", e); + } + } + } diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java index aab359f..5abba6e 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java +++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java @@ -17,7 +17,9 @@ package com.android.internal.telephony.gsm; import android.content.Context; +import android.content.pm.PackageManager; import android.os.AsyncResult; +import android.os.Binder; import android.os.Handler; import android.os.Message; import android.util.Log; @@ -30,7 +32,10 @@ import com.android.internal.telephony.SmsRawData; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static android.telephony.SmsManager.STATUS_ON_ICC_FREE; @@ -45,9 +50,15 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { private final Object mLock = new Object(); private boolean mSuccess; private List<SmsRawData> mSms; + private HashMap<Integer, HashSet<String>> mCellBroadcastSubscriptions = + new HashMap<Integer, HashSet<String>>(); private static final int EVENT_LOAD_DONE = 1; private static final int EVENT_UPDATE_DONE = 2; + private static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3; + private static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4; + private static final int SMS_CB_CODE_SCHEME_MIN = 0; + private static final int SMS_CB_CODE_SCHEME_MAX = 255; Handler mHandler = new Handler() { @Override @@ -75,6 +86,14 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { mLock.notifyAll(); } break; + case EVENT_SET_BROADCAST_ACTIVATION_DONE: + case EVENT_SET_BROADCAST_CONFIG_DONE: + ar = (AsyncResult) msg.obj; + synchronized (mLock) { + mSuccess = (ar.exception == null); + mLock.notifyAll(); + } + break; } } }; @@ -193,6 +212,126 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { return mSms; } + public boolean enableCellBroadcast(int messageIdentifier) { + if (DBG) log("enableCellBroadcast"); + + Context context = mPhone.getContext(); + + context.enforceCallingPermission( + "android.permission.RECEIVE_SMS", + "Enabling cell broadcast SMS"); + + String client = context.getPackageManager().getNameForUid( + Binder.getCallingUid()); + HashSet<String> clients = mCellBroadcastSubscriptions.get(messageIdentifier); + + if (clients == null) { + // This is a new message identifier + clients = new HashSet<String>(); + mCellBroadcastSubscriptions.put(messageIdentifier, clients); + + if (!updateCellBroadcastConfig()) { + mCellBroadcastSubscriptions.remove(messageIdentifier); + return false; + } + } + + clients.add(client); + + if (DBG) + log("Added cell broadcast subscription for MID " + messageIdentifier + + " from client " + client); + + return true; + } + + public boolean disableCellBroadcast(int messageIdentifier) { + if (DBG) log("disableCellBroadcast"); + + Context context = mPhone.getContext(); + + context.enforceCallingPermission( + "android.permission.RECEIVE_SMS", + "Disabling cell broadcast SMS"); + + String client = context.getPackageManager().getNameForUid( + Binder.getCallingUid()); + HashSet<String> clients = mCellBroadcastSubscriptions.get(messageIdentifier); + + if (clients != null && clients.remove(client)) { + if (DBG) + log("Removed cell broadcast subscription for MID " + messageIdentifier + + " from client " + client); + + if (clients.isEmpty()) { + mCellBroadcastSubscriptions.remove(messageIdentifier); + updateCellBroadcastConfig(); + } + return true; + } + + return false; + } + + private boolean updateCellBroadcastConfig() { + Set<Integer> messageIdentifiers = mCellBroadcastSubscriptions.keySet(); + + if (messageIdentifiers.size() > 0) { + SmsBroadcastConfigInfo[] configs = + new SmsBroadcastConfigInfo[messageIdentifiers.size()]; + int i = 0; + + for (int messageIdentifier : messageIdentifiers) { + configs[i++] = new SmsBroadcastConfigInfo(messageIdentifier, messageIdentifier, + SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, true); + } + + return setCellBroadcastConfig(configs) && setCellBroadcastActivation(true); + } else { + return setCellBroadcastActivation(false); + } + } + + private boolean setCellBroadcastConfig(SmsBroadcastConfigInfo[] configs) { + if (DBG) + log("Calling setGsmBroadcastConfig with " + configs.length + " configurations"); + + synchronized (mLock) { + Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE); + + mSuccess = false; + mPhone.mCM.setGsmBroadcastConfig(configs, response); + + try { + mLock.wait(); + } catch (InterruptedException e) { + log("interrupted while trying to set cell broadcast config"); + } + } + + return mSuccess; + } + + private boolean setCellBroadcastActivation(boolean activate) { + if (DBG) + log("Calling setCellBroadcastActivation(" + activate + ")"); + + synchronized (mLock) { + Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE); + + mSuccess = false; + mPhone.mCM.setGsmBroadcastActivation(activate, response); + + try { + mLock.wait(); + } catch (InterruptedException e) { + log("interrupted while trying to set cell broadcast activation"); + } + } + + return mSuccess; + } + @Override protected void log(String msg) { Log.d(LOG_TAG, "[SimSmsInterfaceManager] " + msg); diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java new file mode 100644 index 0000000..5f27cfc --- /dev/null +++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.gsm; + +public class SmsCbHeader { + public static final int PDU_HEADER_LENGTH = 6; + + public final int geographicalScope; + + public final int messageCode; + + public final int updateNumber; + + public final int messageIdentifier; + + public final int dataCodingScheme; + + public final int pageIndex; + + public final int nrOfPages; + + public SmsCbHeader(byte[] pdu) throws IllegalArgumentException { + if (pdu == null || pdu.length < PDU_HEADER_LENGTH) { + throw new IllegalArgumentException("Illegal PDU"); + } + + geographicalScope = (pdu[0] & 0xc0) >> 6; + messageCode = ((pdu[0] & 0x3f) << 4) | ((pdu[1] & 0xf0) >> 4); + updateNumber = pdu[1] & 0x0f; + messageIdentifier = (pdu[2] << 8) | pdu[3]; + dataCodingScheme = pdu[4]; + + // Check for invalid page parameter + int pageIndex = (pdu[5] & 0xf0) >> 4; + int nrOfPages = pdu[5] & 0x0f; + + if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) { + pageIndex = 1; + nrOfPages = 1; + } + + this.pageIndex = pageIndex; + this.nrOfPages = nrOfPages; + } +} diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java new file mode 100644 index 0000000..7136ea0 --- /dev/null +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import android.telephony.SmsCbMessage; +import android.test.AndroidTestCase; + +/** + * Test cases for basic SmsCbMessage operations + */ +public class GsmSmsCbTest extends AndroidTestCase { + + private void doTestGeographicalScopeValue(byte[] pdu, byte b, int expectedGs) { + pdu[0] = b; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected geographical scope decoded", expectedGs, msg + .getGeographicalScope()); + } + + public void testCreateNullPdu() { + SmsCbMessage msg = SmsCbMessage.createFromPdu(null); + + assertNull("createFromPdu(byte[] with null pdu should return null", msg); + } + + public void testCreateTooShortPdu() { + byte[] pdu = new byte[4]; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertNull("createFromPdu(byte[] with too short pdu should return null", msg); + } + + public void testGetGeographicalScope() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + + doTestGeographicalScopeValue(pdu, (byte)0x00, + SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE); + doTestGeographicalScopeValue(pdu, (byte)0x40, SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE); + doTestGeographicalScopeValue(pdu, (byte)0x80, SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE); + doTestGeographicalScopeValue(pdu, (byte)0xC0, SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE); + } + + public void testGetMessageBody7Bit() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected 7-bit string decoded", + "A GSM default alphabet message with carriage return padding", + msg.getMessageBody()); + } + + public void testGetMessageBody7BitFull() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xC4, (byte)0xE5, + (byte)0xB4, (byte)0xFB, (byte)0x0C, (byte)0x2A, (byte)0xE3, (byte)0xC3, (byte)0x63, + (byte)0x3A, (byte)0x3B, (byte)0x0F, (byte)0xCA, (byte)0xCD, (byte)0x40, (byte)0x63, + (byte)0x74, (byte)0x58, (byte)0x1E, (byte)0x1E, (byte)0xD3, (byte)0xCB, (byte)0xF2, + (byte)0x39, (byte)0x88, (byte)0xFD, (byte)0x76, (byte)0x9F, (byte)0x59, (byte)0xA0, + (byte)0x76, (byte)0x39, (byte)0xEC, (byte)0x4E, (byte)0xBB, (byte)0xCF, (byte)0x20, + (byte)0x3A, (byte)0xBA, (byte)0x2C, (byte)0x2F, (byte)0x83, (byte)0xD2, (byte)0x73, + (byte)0x90, (byte)0xFB, (byte)0x0D, (byte)0x82, (byte)0x87, (byte)0xC9, (byte)0xE4, + (byte)0xB4, (byte)0xFB, (byte)0x1C, (byte)0x02 + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals( + "Unexpected 7-bit string decoded", + "A GSM default alphabet message being exactly 93 characters long, " + + "meaning there is no padding!", + msg.getMessageBody()); + } + + public void testGetMessageBody7BitWithLanguage() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x04, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected 7-bit string decoded", + "A GSM default alphabet message with carriage return padding", + msg.getMessageBody()); + + assertEquals("Unexpected language indicator decoded", "es", msg.getLanguageCode()); + } + + public void testGetMessageBody7BitWithLanguageInBody() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x10, (byte)0x11, (byte)0x73, + (byte)0x7B, (byte)0x23, (byte)0x08, (byte)0x3A, (byte)0x4E, (byte)0x9B, (byte)0x20, + (byte)0x72, (byte)0xD9, (byte)0x1C, (byte)0xAE, (byte)0xB3, (byte)0xE9, (byte)0xA0, + (byte)0x30, (byte)0x1B, (byte)0x8E, (byte)0x0E, (byte)0x8B, (byte)0xCB, (byte)0x74, + (byte)0x50, (byte)0xBB, (byte)0x3C, (byte)0x9F, (byte)0x87, (byte)0xCF, (byte)0x65, + (byte)0xD0, (byte)0x3D, (byte)0x4D, (byte)0x47, (byte)0x83, (byte)0xC6, (byte)0x61, + (byte)0xB9, (byte)0x3C, (byte)0x1D, (byte)0x3E, (byte)0x97, (byte)0x41, (byte)0xF2, + (byte)0x32, (byte)0xBD, (byte)0x2E, (byte)0x77, (byte)0x83, (byte)0xE0, (byte)0x61, + (byte)0x32, (byte)0x39, (byte)0xED, (byte)0x3E, (byte)0x37, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected 7-bit string decoded", + "A GSM default alphabet message with carriage return padding", + msg.getMessageBody()); + + assertEquals("Unexpected language indicator decoded", "sv", msg.getLanguageCode()); + } + + public void testGetMessageBody8Bit() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x44, (byte)0x11, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, + (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45 + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("8-bit message body should be empty", "", msg.getMessageBody()); + } + + public void testGetMessageBodyUcs2() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x48, (byte)0x11, (byte)0x00, + (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55, (byte)0x00, (byte)0x43, + (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32, (byte)0x00, (byte)0x20, (byte)0x00, + (byte)0x6D, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x73, + (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x65, (byte)0x00, + (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F, (byte)0x00, (byte)0x6E, + (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x69, (byte)0x00, + (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x67, + (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x20, (byte)0x04, + (byte)0x34, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x68, + (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x61, (byte)0x00, + (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72, + (byte)0x00, (byte)0x0D, (byte)0x00, (byte)0x0D + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected 7-bit string decoded", + "A UCS2 message containing a \u0434 character", msg.getMessageBody()); + } + + public void testGetMessageBodyUcs2WithLanguageInBody() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x11, (byte)0x11, (byte)0x78, + (byte)0x3C, (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55, + (byte)0x00, (byte)0x43, (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32, (byte)0x00, + (byte)0x20, (byte)0x00, (byte)0x6D, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73, + (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00, + (byte)0x65, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F, + (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00, + (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E, + (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61, (byte)0x00, + (byte)0x20, (byte)0x04, (byte)0x34, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, + (byte)0x00, (byte)0x68, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00, + (byte)0x61, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, + (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D + }; + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected 7-bit string decoded", + "A UCS2 message containing a \u0434 character", msg.getMessageBody()); + + assertEquals("Unexpected language indicator decoded", "xx", msg.getLanguageCode()); + } + + public void testGetMessageIdentifier() { + byte[] pdu = { + (byte)0xC0, (byte)0x00, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected message identifier decoded", 12345, msg.getMessageIdentifier()); + } + + public void testGetMessageCode() { + byte[] pdu = { + (byte)0x2A, (byte)0xA5, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected message code decoded", 682, msg.getMessageCode()); + } + + public void testGetUpdateNumber() { + byte[] pdu = { + (byte)0x2A, (byte)0xA5, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41, + (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6, + (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70, + (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, + (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, + (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9, + (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75, + (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69, + (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, + (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 + }; + + SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + + assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber()); + } +} diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index c31c9cc..3b52252 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -246,6 +246,11 @@ public class MockContext extends Context { } @Override + public void startActivities(Intent[] intents) { + throw new UnsupportedOperationException(); + } + + @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { diff --git a/tests/StatusBar/AndroidManifest.xml b/tests/StatusBar/AndroidManifest.xml index b1734bb..ddb756b 100644 --- a/tests/StatusBar/AndroidManifest.xml +++ b/tests/StatusBar/AndroidManifest.xml @@ -21,7 +21,11 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> - <activity android:name="NotificationBuilderTest" android:label="_Notify Builder"> + <activity android:name="NotificationBuilderTest" + android:label="_Notify Builder" + android:theme="@android:style/Theme.Holo" + android:hardwareAccelerated="true" + > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> diff --git a/tests/StatusBar/res/drawable-mdpi/emo_im_kissing.png b/tests/StatusBar/res/drawable-mdpi/emo_im_kissing.png Binary files differnew file mode 100644 index 0000000..56378f6 --- /dev/null +++ b/tests/StatusBar/res/drawable-mdpi/emo_im_kissing.png diff --git a/tests/StatusBar/res/layout/notification_builder_test.xml b/tests/StatusBar/res/layout/notification_builder_test.xml new file mode 100644 index 0000000..58c4fbb --- /dev/null +++ b/tests/StatusBar/res/layout/notification_builder_test.xml @@ -0,0 +1,819 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:paddingLeft="40dp" + android:paddingTop="12dp" + android:paddingRight="24dp" + android:paddingBottom="12dp" + > + + <LinearLayout + android:layout_width="220sp" + android:layout_height="match_parent" + android:layout_marginRight="24dp" + android:orientation="vertical" + > + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <Button + style="@style/IdButton.Minus" + android:id="@+id/clear_1" + /> + <TextView + style="@style/IdTitle" + android:text="1" + /> + <Button + style="@style/IdButton.Plus" + android:id="@+id/notify_1" + /> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <Button + style="@style/IdButton.Minus" + android:id="@+id/clear_2" + /> + <TextView + style="@style/IdTitle" + android:text="2" + /> + <Button + style="@style/IdButton.Plus" + android:id="@+id/notify_2" + /> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <Button + style="@style/IdButton.Minus" + android:id="@+id/clear_3" + /> + <TextView + style="@style/IdTitle" + android:text="3" + /> + <Button + style="@style/IdButton.Plus" + android:id="@+id/notify_3" + /> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <Button + style="@style/IdButton.Minus" + android:id="@+id/clear_4" + /> + <TextView + style="@style/IdTitle" + android:text="4" + /> + <Button + style="@style/IdButton.Plus" + android:id="@+id/notify_4" + /> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <Button + style="@style/IdButton.Minus" + android:id="@+id/clear_5" + /> + <TextView + style="@style/IdTitle" + android:text="5" + /> + <Button + style="@style/IdButton.Plus" + android:id="@+id/notify_5" + /> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <Button + style="@style/IdButton.Minus" + android:id="@+id/clear_6" + /> + <TextView + style="@style/IdTitle" + android:text="6" + /> + <Button + style="@style/IdButton.Plus" + android:id="@+id/notify_6" + /> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <Button + style="@style/IdButton.Minus" + android:id="@+id/clear_7" + /> + <TextView + style="@style/IdTitle" + android:text="7" + /> + <Button + style="@style/IdButton.Plus" + android:id="@+id/notify_7" + /> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <Button + style="@style/IdButton.Minus" + android:id="@+id/clear_8" + /> + <TextView + style="@style/IdTitle" + android:text="8" + /> + <Button + style="@style/IdButton.Plus" + android:id="@+id/notify_8" + /> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <Button + style="@style/IdButton.Minus" + android:id="@+id/clear_9" + /> + <TextView + style="@style/IdTitle" + android:text="9" + /> + <Button + style="@style/IdButton.Plus" + android:id="@+id/notify_9" + /> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <Button + style="@style/IdButton.Minus" + android:id="@+id/clear_10" + /> + <TextView + style="@style/IdTitle" + android:text="10" + /> + <Button + style="@style/IdButton.Plus" + android:id="@+id/notify_10" + /> + </LinearLayout> + + <Button + android:id="@+id/clear_all" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:text="Clear All" + /> + <Button + android:id="@+id/ten" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Ten notifications" + /> + + </LinearLayout> + + <ScrollView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="1" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + > + + <!-- setWhen --> + <RadioGroup + android:id="@+id/group_when" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setWhen" + /> + <RadioButton + android:id="@+id/when_midnight" + style="@style/FieldContents.Disabled" + android:text="midnight" + /> + <RadioButton + android:id="@+id/when_now" + style="@style/FieldContents" + android:text="now" + /> + <RadioButton + android:id="@+id/when_now_plus_1h" + style="@style/FieldContents.Disabled" + android:text="now + 1h" + /> + <RadioButton + android:id="@+id/when_tomorrow" + style="@style/FieldContents.Disabled" + android:text="tomorrow" + /> + </RadioGroup> + + <!-- icon --> + <RadioGroup + android:id="@+id/group_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setSmallIcon" + /> + <RadioButton + android:id="@+id/icon_im" + style="@style/FieldContents" + android:text="IM" + /> + <RadioButton + android:id="@+id/icon_alert" + style="@style/FieldContents" + android:text="alert" + /> + <RadioButton + android:id="@+id/icon_surprise" + style="@style/FieldContents" + android:text="surprise" + /> + <RadioButton + android:id="@+id/icon_level0" + style="@style/FieldContents.Disabled" + android:text="level 0" + /> + <RadioButton + android:id="@+id/icon_level50" + style="@style/FieldContents.Disabled" + android:text="level 50" + /> + <RadioButton + android:id="@+id/icon_level100" + style="@style/FieldContents.Disabled" + android:text="level 100" + /> + <!-- todo setSmallIcon(int icon, int level) --> + </RadioGroup> + + <!-- setContentTitle --> + <RadioGroup + android:id="@+id/group_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setContentTitle" + /> + <RadioButton + android:id="@+id/title_short" + style="@style/FieldContents" + android:text="none" + android:tag="" + /> + <RadioButton + android:id="@+id/title_short" + style="@style/FieldContents" + android:text="cwshort" + android:tag="Title" + /> + <RadioButton + android:id="@+id/title_medium" + style="@style/FieldContents" + android:text="medium" + android:tag="Notification Test" + /> + <RadioButton + android:id="@+id/title_long" + style="@style/FieldContents" + android:text="long" + android:tag="This is one heckuva long title for a notification" + /> + </RadioGroup> + + <!-- setContentText --> + <RadioGroup + android:id="@+id/group_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setContentText" + /> + <RadioButton + android:id="@+id/text_none" + style="@style/FieldContents" + android:text="none" + android:tag="" + /> + <RadioButton + android:id="@+id/text_short" + style="@style/FieldContents" + android:tag="short" + android:text="text" + /> + <RadioButton + android:id="@+id/text_medium" + style="@style/FieldContents" + android:text="medium" + android:tag="Something happened" + /> + <RadioButton + android:id="@+id/text_long" + style="@style/FieldContents" + android:text="long" + android:tag="Oh my goodness. SOMETHING HAPPENED!!!!" + /> + <RadioButton + android:id="@+id/text_haiku" + style="@style/FieldContents" + android:text="haiku" + android:tag="sholes final approach\nlanding gear punted to flan\nrunway foam glistens" + /> + </RadioGroup> + + <!-- setContentInfo --> + <RadioGroup + android:id="@+id/group_info" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setContentInfo" + /> + <RadioButton + android:id="@+id/info_none" + style="@style/FieldContents" + android:text="none" + android:tag="" + /> + <RadioButton + android:id="@+id/info_number" + style="@style/FieldContents" + android:text="snoozed" + android:tag="snoozed" + /> + <RadioButton + android:id="@+id/info_long" + style="@style/FieldContents" + android:text="longer" + android:tag="this content info is way too long" + /> + </RadioGroup> + + <!-- setNumber --> + <RadioGroup + android:id="@+id/group_number" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setNumber" + /> + <RadioButton + android:id="@+id/number_0" + style="@style/FieldContents" + android:text="0" + android:tag="0" + /> + <RadioButton + android:id="@+id/number_1" + style="@style/FieldContents" + android:text="1" + android:tag="1" + /> + <RadioButton + android:id="@+id/number_42" + style="@style/FieldContents" + android:text="42" + android:tag="42" + /> + <RadioButton + android:id="@+id/number_334" + style="@style/FieldContents" + android:text="334" + android:tag="334" + /> + <RadioButton + android:id="@+id/number_999" + style="@style/FieldContents" + android:text="999" + android:tag="999" + /> + <RadioButton + android:id="@+id/number_9876" + style="@style/FieldContents" + android:text="9,876" + android:tag="9876" + /> + <RadioButton + android:id="@+id/number_12345" + style="@style/FieldContents" + android:text="12,345" + android:tag="12345" + /> + </RadioGroup> + + <!-- setContentIntent --> + <RadioGroup + android:id="@+id/group_intent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setContentIntent" + /> + <RadioButton + android:id="@+id/intent_none" + style="@style/FieldContents" + android:text="none" + /> + <RadioButton + android:id="@+id/intent_alert" + style="@style/FieldContents" + android:text="alert" + /> + </RadioGroup> + + <!-- setDeleteIntent --> + <RadioGroup + android:id="@+id/group_delete" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setDeleteIntent" + /> + <RadioButton + android:id="@+id/delete_none" + style="@style/FieldContents" + android:text="none" + /> + <RadioButton + android:id="@+id/delete_alert" + style="@style/FieldContents" + android:text="alert" + /> + </RadioGroup> + + + <!-- setFullScreenIntent --> + <RadioGroup + android:id="@+id/group_full_screen" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:visibility="gone" + > + <TextView + style="@style/FieldTitle" + android:text="setFullScreenIntent" + /> + <RadioButton + android:id="@+id/full_screen_none" + style="@style/FieldContents.Disabled" + android:text="none" + /> + <RadioButton + android:id="@+id/full_screen_activity" + style="@style/FieldContents.Disabled" + android:text="full screen" + /> + </RadioGroup> + + + <!-- setTicker --> + <RadioGroup + android:id="@+id/group_ticker" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setTicker" + /> + <RadioButton + android:id="@+id/ticker_none" + style="@style/FieldContents" + android:text="none" + android:tag="" + /> + <RadioButton + android:id="@+id/ticker_short" + style="@style/FieldContents" + android:text="short" + android:tag="tick" + /> + <RadioButton + android:id="@+id/ticker_wrap" + style="@style/FieldContents" + android:text="wrap" + android:tag="tick tick tick tock tock tock something fun has happened but i don't know what it is just yet" + /> + <RadioButton + android:id="@+id/ticker_haiku" + style="@style/FieldContents" + android:text="haiku" + android:tag="sholes final approach\nlanding gear punted to flan\nrunway foam glistens" + /> + <RadioButton + android:id="@+id/ticker_custom" + style="@style/FieldContents.Disabled" + android:text="custom view" + /> + </RadioGroup> + + + <!-- setLargeIcon --> + <RadioGroup + android:id="@+id/group_large_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setLargeIcon" + /> + <RadioButton + android:id="@+id/large_icon_none" + style="@style/FieldContents" + android:text="none" + /> + <RadioButton + android:id="@+id/large_icon_pineapple" + style="@style/FieldContents" + android:text="pineapple" + /> + </RadioGroup> + + + <!-- setSound --> + <RadioGroup + android:id="@+id/group_sound" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:visibility="gone" + > + <TextView + style="@style/FieldTitle" + android:text="setSound" + /> + <RadioButton + android:id="@+id/sound_none" + style="@style/FieldContents.Disabled" + android:text="none" + /> + </RadioGroup> + + + <!-- setVibrate --> + <RadioGroup + android:id="@+id/group_vibrate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setVibrate" + /> + <RadioButton + android:id="@+id/vibrate_none" + style="@style/FieldContents" + android:text="none" + /> + <RadioButton + android:id="@+id/vibrate_short" + style="@style/FieldContents" + android:text="short" + /> + <RadioButton + android:id="@+id/vibrate_medium" + style="@style/FieldContents" + android:text="long" + /> + <RadioButton + android:id="@+id/vibrate_long" + style="@style/FieldContents" + android:text="long" + /> + <RadioButton + android:id="@+id/vibrate_pattern" + style="@style/FieldContents" + android:text="longer" + /> + </RadioGroup> + + + <!-- setLights --> + <RadioGroup + android:id="@+id/group_lights_color" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setLights (color)" + /> + <RadioButton + android:id="@+id/lights_red" + style="@style/FieldContents" + android:text="red" + android:tag="0xff0000" + /> + <RadioButton + android:id="@+id/lights_green" + style="@style/FieldContents" + android:text="green" + android:tag="0x00ff00" + /> + <RadioButton + android:id="@+id/lights_blue" + style="@style/FieldContents" + android:text="blue" + android:tag="0x0000ff" + /> + <RadioButton + android:id="@+id/lights_cyan" + style="@style/FieldContents" + android:text="cyan" + android:tag="0x00ffff" + /> + <RadioButton + android:id="@+id/lights_magenta" + style="@style/FieldContents" + android:text="magenta" + android:tag="0xff00ff" + /> + <RadioButton + android:id="@+id/lights_yellow" + style="@style/FieldContents" + android:text="yellow" + android:tag="0xffff00" + /> + <RadioButton + android:id="@+id/lights_white" + style="@style/FieldContents" + android:text="white" + android:tag="0xffffff" + /> + </RadioGroup> + + <!-- setLights --> + <RadioGroup + android:id="@+id/group_lights_blink" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="setLights (blink)" + /> + <RadioButton + android:id="@+id/lights_off" + style="@style/FieldContents" + android:text="off" + /> + <RadioButton + android:id="@+id/lights_slow" + style="@style/FieldContents" + android:text="slow" + /> + <RadioButton + android:id="@+id/lights_fast" + style="@style/FieldContents" + android:text="fast" + /> + <RadioButton + android:id="@+id/lights_on" + style="@style/FieldContents" + android:text="on" + /> + </RadioGroup> + + <!-- flags --> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginTop="12dp" + > + <TextView + style="@style/FieldTitle" + android:text="flags" + /> + <CheckBox + android:id="@+id/flag_ongoing" + style="@style/FieldContents" + android:text="setOngoing" + /> + <CheckBox + android:id="@+id/flag_once" + style="@style/FieldContents" + android:text="setOnlyAlertOnce" + /> + <CheckBox + android:id="@+id/flag_auto_cancel" + style="@style/FieldContents" + android:text="setAutoCancel" + /> + </LinearLayout> + + <!-- defaults --> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView + style="@style/FieldTitle" + android:text="defaults" + /> + <CheckBox + android:id="@+id/default_sound" + style="@style/FieldContents" + android:text="sound" + /> + <CheckBox + android:id="@+id/default_vibrate" + style="@style/FieldContents" + android:text="vibrate" + /> + <CheckBox + android:id="@+id/default_lights" + style="@style/FieldContents" + android:text="lights" + /> + </LinearLayout> + + + + + </LinearLayout> + </ScrollView> + + +</LinearLayout> diff --git a/tests/StatusBar/res/values/styles.xml b/tests/StatusBar/res/values/styles.xml new file mode 100644 index 0000000..e051efd --- /dev/null +++ b/tests/StatusBar/res/values/styles.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2006 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + + <style name="IdTitle"> + <item name="android:textAppearance">?android:attr/textAppearanceLarge</item> + <item name="android:layout_width">30sp</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:gravity">center</item> + <item name="android:textStyle">bold</item> + </style> + + <style name="IdButton"> + <item name="android:textAppearance">?android:attr/textAppearanceLarge</item> + <item name="android:layout_width">0dp</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_weight">1</item> + <item name="android:layout_marginRight">8dp</item> + <item name="android:layout_marginLeft">8dp</item> + <item name="android:textStyle">bold</item> + </style> + + <style name="IdButton.Minus"> + <item name="android:text">-</item> + </style> + + <style name="IdButton.Plus"> + <item name="android:text">+</item> + </style> + + <style name="FieldTitle"> + <item name="android:textAppearance">?android:attr/textAppearanceLarge</item> + <item name="android:layout_width">208sp</item> + <item name="android:layout_height">wrap_content</item> + </style> + + <style name="FieldContents"> + <item name="android:textAppearance">?android:attr/textAppearanceMedium</item> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_marginRight">20dp</item> + </style> + + <style name="FieldContents.Disabled"> + <item name="android:clickable">false</item> + <item name="android:visibility">gone</item> + </style> + +</resources> + diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationBuilderTest.java b/tests/StatusBar/src/com/android/statusbartest/NotificationBuilderTest.java index 3c26212..e9a3513 100644 --- a/tests/StatusBar/src/com/android/statusbartest/NotificationBuilderTest.java +++ b/tests/StatusBar/src/com/android/statusbartest/NotificationBuilderTest.java @@ -16,6 +16,7 @@ package com.android.statusbartest; +import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -25,106 +26,363 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; +import android.os.Bundle; import android.os.Environment; import android.os.Vibrator; import android.os.Handler; +import android.text.TextUtils; import android.util.Log; import android.net.Uri; import android.os.SystemClock; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.RadioButton; +import android.widget.RadioGroup; import android.widget.RemoteViews; import android.os.PowerManager; -public class NotificationBuilderTest extends TestActivity +public class NotificationBuilderTest extends Activity { private final static String TAG = "NotificationTestList"; NotificationManager mNM; @Override - protected String tag() { - return TAG; + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + setContentView(R.layout.notification_builder_test); + if (icicle == null) { + setDefaults(); + } + for (int id: new int[] { + R.id.clear_1, + R.id.clear_2, + R.id.clear_3, + R.id.clear_4, + R.id.clear_5, + R.id.clear_6, + R.id.clear_7, + R.id.clear_8, + R.id.clear_9, + R.id.clear_10, + R.id.notify_1, + R.id.notify_2, + R.id.notify_3, + R.id.notify_4, + R.id.notify_5, + R.id.notify_6, + R.id.notify_7, + R.id.notify_8, + R.id.notify_9, + R.id.notify_10, + R.id.ten, + R.id.clear_all, + }) { + findViewById(id).setOnClickListener(mClickListener); + } } - @Override - protected Test[] tests() { - mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); - - return mTests; + private void setDefaults() { + setChecked(R.id.when_now); + setChecked(R.id.icon_surprise); + setChecked(R.id.title_medium); + setChecked(R.id.text_medium); + setChecked(R.id.info_none); + setChecked(R.id.number_0); + setChecked(R.id.intent_alert); + setChecked(R.id.delete_none); + setChecked(R.id.full_screen_none); + setChecked(R.id.ticker_none); + setChecked(R.id.large_icon_none); + setChecked(R.id.sound_none); + setChecked(R.id.vibrate_none); + setChecked(R.id.lights_red); + setChecked(R.id.lights_off); } - private Test[] mTests = new Test[] { - new Test("Cancel (1)") { - public void run() { - mNM.cancel(1); + private View.OnClickListener mClickListener = new View.OnClickListener() { + public void onClick(View v) { + switch (v.getId()) { + case R.id.clear_1: + mNM.cancel(1); + break; + case R.id.clear_2: + mNM.cancel(2); + break; + case R.id.clear_3: + mNM.cancel(3); + break; + case R.id.clear_4: + mNM.cancel(4); + break; + case R.id.clear_5: + mNM.cancel(5); + break; + case R.id.clear_6: + mNM.cancel(6); + break; + case R.id.clear_7: + mNM.cancel(7); + break; + case R.id.clear_8: + mNM.cancel(8); + break; + case R.id.clear_9: + mNM.cancel(9); + break; + case R.id.clear_10: + mNM.cancel(10); + break; + case R.id.notify_1: + sendNotification(1); + break; + case R.id.notify_2: + sendNotification(2); + break; + case R.id.notify_3: + sendNotification(3); + break; + case R.id.notify_4: + sendNotification(4); + break; + case R.id.notify_5: + sendNotification(5); + break; + case R.id.notify_6: + sendNotification(6); + break; + case R.id.notify_7: + sendNotification(7); + break; + case R.id.notify_8: + sendNotification(8); + break; + case R.id.notify_9: + sendNotification(9); + break; + case R.id.notify_10: + sendNotification(10); + break; + case R.id.ten: { + for (int id=1; id<=10; id++) { + sendNotification(id); + } + break; + } + case R.id.clear_all: { + for (int id=1; id<=10; id++) { + mNM.cancel(id); + } + break; + } } - }, + } + }; - new Test("Basic Content (1)") { - public void run() { - int id = 1; - final Notification.Builder b = makeBasicBuilder(this, id); + private void sendNotification(int id) { + final Notification n = buildNotification(id); + mNM.notify(id, n); + } - mNM.notify(id, b.getNotification()); - } - }, + private Notification buildNotification(int id) { + Notification.Builder b = new Notification.Builder(this); - new Test("Content w/ Info (1)") { - public void run() { - int id = 1; - final Notification.Builder b = makeBasicBuilder(this, id); + // when + switch (getRadioChecked(R.id.group_when)) { + case R.id.when_midnight: + break; + case R.id.when_now: + b.setWhen(System.currentTimeMillis()); + break; + case R.id.when_now_plus_1h: + break; + case R.id.when_tomorrow: + break; + } - b.setContentInfo("Snoozed"); + // icon + switch (getRadioChecked(R.id.group_icon)) { + case R.id.icon_im: + b.setSmallIcon(R.drawable.icon1); + break; + case R.id.icon_alert: + b.setSmallIcon(R.drawable.icon2); + break; + case R.id.icon_surprise: + b.setSmallIcon(R.drawable.emo_im_kissing); + break; + } - mNM.notify(id, b.getNotification()); - } - }, + // title + final String title = getRadioTag(R.id.group_title); + if (!TextUtils.isEmpty(title)) { + b.setContentTitle(title); + } - new Test("w/ Number (1)") { - public void run() { - int id = 1; - final Notification.Builder b = makeBasicBuilder(this, id); + // text + final String text = getRadioTag(R.id.group_text); + if (!TextUtils.isEmpty(text)) { + b.setContentText(text); + } - b.setNumber(12345); + // info + final String info = getRadioTag(R.id.group_info); + if (!TextUtils.isEmpty(info)) { + b.setContentInfo(info); + } - mNM.notify(id, b.getNotification()); - } - }, + // number + b.setNumber(getRadioInt(R.id.group_number, 0)); - new Test("w/ Number and Large Icon (1)") { - public void run() { - int id = 1; - final Notification.Builder b = makeBasicBuilder(this, id); + // contentIntent + switch (getRadioChecked(R.id.group_intent)) { + case R.id.intent_none: + break; + case R.id.intent_alert: + b.setContentIntent(makeContentIntent(id)); + break; + } - b.setNumber(42); + // deleteIntent + switch (getRadioChecked(R.id.group_delete)) { + case R.id.delete_none: + break; + case R.id.delete_alert: + b.setDeleteIntent(makeDeleteIntent(id)); + break; + } - final BitmapDrawable bd = (BitmapDrawable)getResources().getDrawable( - R.drawable.pineapple); - b.setLargeIcon(Bitmap.createBitmap(bd.getBitmap())); + // fullScreenIntent TODO - mNM.notify(id, b.getNotification()); - } - }, - }; + // ticker + switch (getRadioChecked(R.id.group_ticker)) { + case R.id.ticker_none: + break; + case R.id.ticker_short: + case R.id.ticker_wrap: + case R.id.ticker_haiku: + b.setTicker(getRadioTag(R.id.group_ticker)); + break; + case R.id.ticker_custom: + // TODO + break; + } - private Notification.Builder makeBasicBuilder(Test t, int id) { - final Notification.Builder b = new Notification.Builder(this); + // largeIcon + switch (getRadioChecked(R.id.group_large_icon)) { + case R.id.large_icon_none: + break; + case R.id.large_icon_pineapple: + b.setLargeIcon(loadBitmap(R.drawable.pineapple)); + break; + } - b.setWhen(System.currentTimeMillis()); - b.setSmallIcon(R.drawable.ic_statusbar_chat); - b.setContentTitle("Notification builder Test"); - b.setContentText(t.name + "\nhappy notifying"); - b.setContentIntent(makeContentIntent(id)); - b.setDeleteIntent(makeDeleteIntent(id)); + // sound TODO - return b; + // vibrate + switch (getRadioChecked(R.id.group_vibrate)) { + case R.id.vibrate_none: + break; + case R.id.vibrate_short: + b.setVibrate(new long[] { 0, 200 }); + break; + case R.id.vibrate_medium: + b.setVibrate(new long[] { 0, 500 }); + break; + case R.id.vibrate_long: + b.setVibrate(new long[] { 0, 1000 }); + break; + case R.id.vibrate_pattern: + b.setVibrate(new long[] { 0, 250, 250, 250, 250, 250, 250, 250 }); + break; + } + + // lights + final int color = getRadioInt(R.id.group_lights_color, 0xff0000); + int onMs; + int offMs; + switch (getRadioChecked(R.id.group_lights_blink)) { + case R.id.lights_slow: + onMs = 1300; + offMs = 1300; + break; + case R.id.lights_fast: + onMs = 300; + offMs = 300; + break; + case R.id.lights_on: + onMs = 1; + offMs = 0; + break; + case R.id.lights_off: + default: + onMs = 0; + offMs = 0; + break; + } + if (onMs != 0 && offMs != 0) { + b.setLights(color, onMs, offMs); + } + + // flags + b.setOngoing(getChecked(R.id.flag_ongoing)); + b.setOnlyAlertOnce(getChecked(R.id.flag_once)); + b.setAutoCancel(getChecked(R.id.flag_auto_cancel)); + + // defaults + int defaults = 0; + if (getChecked(R.id.default_sound)) { + defaults |= Notification.DEFAULT_SOUND; + } + if (getChecked(R.id.default_vibrate)) { + defaults |= Notification.DEFAULT_VIBRATE; + } + if (getChecked(R.id.default_lights)) { + defaults |= Notification.DEFAULT_LIGHTS; + } + b.setDefaults(defaults); + + return b.getNotification(); } - private PendingIntent makeContentIntent(int id) { - Intent intent = new Intent(this, ConfirmationActivity.class); - intent.setData(Uri.fromParts("content", "//status_bar_test/content/" + id, null)); - intent.putExtra(ConfirmationActivity.EXTRA_TITLE, "Content intent"); - intent.putExtra(ConfirmationActivity.EXTRA_TEXT, "id: " + id); - return PendingIntent.getActivity(this, 0, intent, 0); + private void setChecked(int id) { + final CompoundButton b = (CompoundButton)findViewById(id); + b.setChecked(true); + } + + private int getRadioChecked(int id) { + final RadioGroup g = (RadioGroup)findViewById(id); + return g.getCheckedRadioButtonId(); + } + + private String getRadioTag(int id) { + final RadioGroup g = (RadioGroup)findViewById(id); + final View v = findViewById(g.getCheckedRadioButtonId()); + return (String)v.getTag(); + } + + private int getRadioInt(int id, int def) { + String str = getRadioTag(id); + if (TextUtils.isEmpty(str)) { + return def; + } else { + try { + return Integer.parseInt(str); + } catch (NumberFormatException ex) { + return def; + } + } + } + + private boolean getChecked(int id) { + final CompoundButton b = (CompoundButton)findViewById(id); + return b.isChecked(); + } + + private Bitmap loadBitmap(int id) { + final BitmapDrawable bd = (BitmapDrawable)getResources().getDrawable(id); + return Bitmap.createBitmap(bd.getBitmap()); } private PendingIntent makeDeleteIntent(int id) { @@ -134,5 +392,13 @@ public class NotificationBuilderTest extends TestActivity intent.putExtra(ConfirmationActivity.EXTRA_TEXT, "id: " + id); return PendingIntent.getActivity(this, 0, intent, 0); } + + private PendingIntent makeContentIntent(int id) { + Intent intent = new Intent(this, ConfirmationActivity.class); + intent.setData(Uri.fromParts("content", "//status_bar_test/content/" + id, null)); + intent.putExtra(ConfirmationActivity.EXTRA_TITLE, "Content intent"); + intent.putExtra(ConfirmationActivity.EXTRA_TEXT, "id: " + id); + return PendingIntent.getActivity(this, 0, intent, 0); + } } diff --git a/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java index fec3671..4cacbc4 100644 --- a/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java @@ -39,7 +39,7 @@ public class ActivityManagerPermissionTests extends TestCase { @SmallTest public void testREORDER_TASKS() { try { - mAm.moveTaskToFront(-1); + mAm.moveTaskToFront(0, 0); fail("IActivityManager.moveTaskToFront did not throw SecurityException as" + " expected"); } catch (SecurityException e) { diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index 3cb614f..faae89b 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -33,8 +33,8 @@ static const char* kNoCompressExt[] = { /* fwd decls, so I can write this downward */ ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets); -ssize_t processAssets(Bundle* bundle, ZipFile* zip, - const sp<AaptDir>& dir, const AaptGroupEntry& ge); +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir, + const AaptGroupEntry& ge, const ResourceFilter* filter); bool processFile(Bundle* bundle, ZipFile* zip, const sp<AaptGroup>& group, const sp<AaptFile>& file); bool okayToCompress(Bundle* bundle, const String8& pathName); @@ -204,34 +204,45 @@ ssize_t processAssets(Bundle* bundle, ZipFile* zip, const size_t N = assets->getGroupEntries().size(); for (size_t i=0; i<N; i++) { const AaptGroupEntry& ge = assets->getGroupEntries()[i]; - if (!filter.match(ge.toParams())) { - continue; - } - ssize_t res = processAssets(bundle, zip, assets, ge); + + ssize_t res = processAssets(bundle, zip, assets, ge, &filter); if (res < 0) { return res; } + count += res; } return count; } -ssize_t processAssets(Bundle* bundle, ZipFile* zip, - const sp<AaptDir>& dir, const AaptGroupEntry& ge) +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir, + const AaptGroupEntry& ge, const ResourceFilter* filter) { ssize_t count = 0; const size_t ND = dir->getDirs().size(); size_t i; for (i=0; i<ND; i++) { - ssize_t res = processAssets(bundle, zip, dir->getDirs().valueAt(i), ge); + const sp<AaptDir>& subDir = dir->getDirs().valueAt(i); + + const bool filterable = filter != NULL && subDir->getLeaf().find("mipmap-") != 0; + + if (filterable && subDir->getLeaf() != subDir->getPath() && !filter->match(ge.toParams())) { + continue; + } + + ssize_t res = processAssets(bundle, zip, subDir, ge, filterable ? filter : NULL); if (res < 0) { return res; } count += res; } + if (filter != NULL && !filter->match(ge.toParams())) { + return count; + } + const size_t NF = dir->getFiles().size(); for (i=0; i<NF; i++) { sp<AaptGroup> gp = dir->getFiles().valueAt(i); diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 822262e..7b74506 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -150,7 +150,7 @@ bool isValidResourceType(const String8& type) { return type == "anim" || type == "drawable" || type == "layout" || type == "values" || type == "xml" || type == "raw" - || type == "color" || type == "menu"; + || type == "color" || type == "menu" || type == "mipmap"; } static sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true) @@ -284,9 +284,9 @@ static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets, } static status_t preProcessImages(Bundle* bundle, const sp<AaptAssets>& assets, - const sp<ResourceTypeSet>& set) + const sp<ResourceTypeSet>& set, const char* type) { - ResourceDirIterator it(set, String8("drawable")); + ResourceDirIterator it(set, String8(type)); Vector<sp<AaptFile> > newNameFiles; Vector<String8> newNamePaths; bool hasErrors = false; @@ -802,6 +802,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) sp<ResourceTypeSet> raws; sp<ResourceTypeSet> colors; sp<ResourceTypeSet> menus; + sp<ResourceTypeSet> mipmaps; ASSIGN_IT(drawable); ASSIGN_IT(layout); @@ -810,6 +811,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) ASSIGN_IT(raw); ASSIGN_IT(color); ASSIGN_IT(menu); + ASSIGN_IT(mipmap); assets->setResources(resources); // now go through any resource overlays and collect their files @@ -828,7 +830,8 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) !applyFileOverlay(bundle, assets, &xmls, "xml") || !applyFileOverlay(bundle, assets, &raws, "raw") || !applyFileOverlay(bundle, assets, &colors, "color") || - !applyFileOverlay(bundle, assets, &menus, "menu")) { + !applyFileOverlay(bundle, assets, &menus, "menu") || + !applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) { return UNKNOWN_ERROR; } @@ -836,7 +839,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) if (drawables != NULL) { if (bundle->getOutputAPKFile() != NULL) { - err = preProcessImages(bundle, assets, drawables); + err = preProcessImages(bundle, assets, drawables, "drawable"); } if (err == NO_ERROR) { err = makeFileResources(bundle, assets, &table, drawables, "drawable"); @@ -848,6 +851,20 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) } } + if (mipmaps != NULL) { + if (bundle->getOutputAPKFile() != NULL) { + err = preProcessImages(bundle, assets, mipmaps, "mipmap"); + } + if (err == NO_ERROR) { + err = makeFileResources(bundle, assets, &table, mipmaps, "mipmap"); + if (err != NO_ERROR) { + hasErrors = true; + } + } else { + hasErrors = true; + } + } + if (layouts != NULL) { err = makeFileResources(bundle, assets, &table, layouts, "layout"); if (err != NO_ERROR) { diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index a77042a..196b06c 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -2539,7 +2539,7 @@ ResourceFilter::parse(const char* arg) } bool -ResourceFilter::match(int axis, uint32_t value) +ResourceFilter::match(int axis, uint32_t value) const { if (value == 0) { // they didn't specify anything so take everything @@ -2555,7 +2555,7 @@ ResourceFilter::match(int axis, uint32_t value) } bool -ResourceFilter::match(const ResTable_config& config) +ResourceFilter::match(const ResTable_config& config) const { if (config.locale) { uint32_t locale = (config.country[1] << 24) | (config.country[0] << 16) @@ -2608,6 +2608,8 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) const size_t N = mOrderedPackages.size(); size_t pi; + const static String16 mipmap16("mipmap"); + bool useUTF8 = !bundle->getWantUTF16() && bundle->isMinSdkAtLeast(SDK_FROYO); // Iterate through all data, collecting all values (strings, @@ -2630,7 +2632,10 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) typeStrings.add(String16("<empty>"), false); continue; } - typeStrings.add(t->getName(), false); + const String16 typeName(t->getName()); + typeStrings.add(typeName, false); + + const bool filterable = (typeName != mipmap16); const size_t N = t->getOrderedConfigs().size(); for (size_t ci=0; ci<N; ci++) { @@ -2641,7 +2646,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) const size_t N = c->getEntries().size(); for (size_t ei=0; ei<N; ei++) { ConfigDescription config = c->getEntries().keyAt(ei); - if (!filter.match(config)) { + if (filterable && !filter.match(config)) { continue; } sp<Entry> e = c->getEntries().valueAt(ei); @@ -2721,6 +2726,8 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) "Type name %s not found", String8(typeName).string()); + const bool filterable = (typeName != mipmap16); + const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0; // First write the typeSpec chunk, containing information about @@ -2745,7 +2752,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) (((uint8_t*)data->editData()) + typeSpecStart + sizeof(ResTable_typeSpec)); memset(typeSpecFlags, 0, sizeof(uint32_t)*N); - + for (size_t ei=0; ei<N; ei++) { sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei); if (cl->getPublic()) { @@ -2753,11 +2760,11 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) } const size_t CN = cl->getEntries().size(); for (size_t ci=0; ci<CN; ci++) { - if (!filter.match(cl->getEntries().keyAt(ci))) { + if (filterable && !filter.match(cl->getEntries().keyAt(ci))) { continue; } for (size_t cj=ci+1; cj<CN; cj++) { - if (!filter.match(cl->getEntries().keyAt(cj))) { + if (filterable && !filter.match(cl->getEntries().keyAt(cj))) { continue; } typeSpecFlags[ei] |= htodl( @@ -2794,7 +2801,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) config.screenWidth, config.screenHeight)); - if (!filter.match(config)) { + if (filterable && !filter.match(config)) { continue; } diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index 186c7ca..bbb8140 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -549,9 +549,9 @@ class ResourceFilter public: ResourceFilter() : mData(), mContainsPseudo(false) {} status_t parse(const char* arg); - bool match(int axis, uint32_t value); - bool match(const ResTable_config& config); - inline bool containsPseudo() { return mContainsPseudo; } + bool match(int axis, uint32_t value) const; + bool match(const ResTable_config& config) const; + inline bool containsPseudo() const { return mContainsPseudo; } private: KeyedVector<int,SortedVector<uint32_t> > mData; diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java index 6fd59c4..212223c 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java @@ -456,7 +456,11 @@ public class BitmapFactory { // into is.read(...) This number is not related to the value passed // to mark(...) above. try { - bm = Bitmap_Delegate.createBitmap(is, Density.MEDIUM); + Density density = Density.MEDIUM; + if (opts != null) { + density = Density.getEnum(opts.inDensity); + } + bm = Bitmap_Delegate.createBitmap(is, true, density); } catch (IOException e) { return null; } diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index 0920497..b4c51b2 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -75,32 +75,56 @@ public class Bitmap_Delegate { /** * Creates and returns a {@link Bitmap} initialized with the given file content. + * + * @param input the file from which to read the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() */ - public static Bitmap createBitmap(File input, Density density) throws IOException { + public static Bitmap createBitmap(File input, boolean isMutable, Density density) + throws IOException { // create a delegate with the content of the file. Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input)); - return createBitmap(delegate, density.getValue()); + return createBitmap(delegate, isMutable, density.getValue()); } /** * Creates and returns a {@link Bitmap} initialized with the given stream content. + * + * @param input the stream from which to read the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() */ - public static Bitmap createBitmap(InputStream input, Density density) throws IOException { + public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density) + throws IOException { // create a delegate with the content of the stream. Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input)); - return createBitmap(delegate, density.getValue()); + return createBitmap(delegate, isMutable, density.getValue()); } /** * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage} + * + * @param image the bitmap content + * @param isMutable whether the bitmap is mutable + * @param density the density associated with the bitmap + * + * @see Bitmap#isMutable() + * @see Bitmap#getDensity() */ - public static Bitmap createBitmap(BufferedImage image, Density density) throws IOException { + public static Bitmap createBitmap(BufferedImage image, boolean isMutable, Density density) + throws IOException { // create a delegate with the given image. Bitmap_Delegate delegate = new Bitmap_Delegate(image); - return createBitmap(delegate, density.getValue()); + return createBitmap(delegate, isMutable, density.getValue()); } /** @@ -153,7 +177,7 @@ public class Bitmap_Delegate { // create a delegate with the content of the stream. Bitmap_Delegate delegate = new Bitmap_Delegate(image); - return createBitmap(delegate, Bitmap.getDefaultDensity()); + return createBitmap(delegate, mutable, Bitmap.getDefaultDensity()); } /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) { @@ -166,8 +190,7 @@ public class Bitmap_Delegate { } /*package*/ static void nativeRecycle(int nativeBitmap) { - // FIXME implement native delegate - throw new UnsupportedOperationException("Native delegate needed for Bitmap"); + sManager.removeDelegate(nativeBitmap); } /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality, @@ -336,11 +359,11 @@ public class Bitmap_Delegate { mImage = image; } - private static Bitmap createBitmap(Bitmap_Delegate delegate, int density) { + private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) { // get its native_int int nativeInt = sManager.addDelegate(delegate); // and create/return a new Bitmap with it - return new Bitmap(nativeInt, true /*isMutable*/, null /*ninePatchChunk*/, density); + return new Bitmap(nativeInt, isMutable, null /*ninePatchChunk*/, density); } } diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index cea07af..08f3c7a 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -960,7 +960,7 @@ public class Canvas_Delegate { * Creates a new {@link Graphics2D} based on the {@link Paint} parameters. * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used. */ - private Graphics2D getCustomGraphics(Paint_Delegate paint) { + /*package*/ Graphics2D getCustomGraphics(Paint_Delegate paint) { // make new one Graphics2D g = getGraphics2d(); g = (Graphics2D)g.create(); diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java new file mode 100644 index 0000000..3d26e47 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.ninepatch.NinePatchChunk; + +import android.graphics.drawable.NinePatchDrawable; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.ref.SoftReference; +import java.util.HashMap; +import java.util.Map; + +/** + * Delegate implementing the native methods of android.graphics.NinePatch + * + * Through the layoutlib_create tool, the original native methods of NinePatch have been replaced + * by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + * + */ +public class NinePatch_Delegate { + + /** + * Cache map for {@link NinePatchChunk}. + * When the chunks are created they are serialized into a byte[], and both are put + * in the cache, using a {@link SoftReference} for the chunk. The default Java classes + * for {@link NinePatch} and {@link NinePatchDrawable} only reference to the byte[] data, and + * provide this for drawing. + * Using the cache map allows us to not have to deserialize the byte[] back into a + * {@link NinePatchChunk} every time a rendering is done. + */ + private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache = + new HashMap<byte[], SoftReference<NinePatchChunk>>(); + + // ---- Public Helper methods ---- + + /** + * Serializes the given chunk. + * + * @return the serialized data for the chunk. + */ + public static byte[] serialize(NinePatchChunk chunk) { + // serialize the chunk to get a byte[] + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = null; + try { + oos = new ObjectOutputStream(baos); + oos.writeObject(chunk); + } catch (IOException e) { + //FIXME log this. + return null; + } finally { + if (oos != null) { + try { + oos.close(); + } catch (IOException e) { + } + } + } + + // get the array and add it to the cache + byte[] array = baos.toByteArray(); + sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk)); + return array; + } + + // ---- native methods ---- + + /*package*/ static boolean isNinePatchChunk(byte[] chunk) { + NinePatchChunk chunkObject = getChunk(chunk); + if (chunkObject != null) { + return true; + } + + return false; + } + + /*package*/ static void validateNinePatchChunk(int bitmap, byte[] chunk) { + // the default JNI implementation only checks that the byte[] has the same + // size as the C struct it represent. Since we cannot do the same check (serialization + // will return different size depending on content), we do nothing. + } + + /*package*/ static void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance, + byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) { + draw(canvas_instance, + (int) loc.left, (int) loc.top, (int) loc.width(), (int) loc.height(), + bitmap_instance, c, paint_instance_or_null, + destDensity, srcDensity); + } + + /*package*/ static void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance, + byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) { + draw(canvas_instance, + loc.left, loc.top, loc.width(), loc.height(), + bitmap_instance, c, paint_instance_or_null, + destDensity, srcDensity); + } + + private static void draw(int canvas_instance, + int left, int top, int right, int bottom, + int bitmap_instance, byte[] c, int paint_instance_or_null, + int destDensity, int srcDensity) { + // get the delegate from the native int. + Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance); + if (bitmap_delegate == null) { + assert false; + return; + } + + if (c == null) { + // not a 9-patch? + BufferedImage image = bitmap_delegate.getImage(); + Canvas_Delegate.native_drawBitmap(canvas_instance, bitmap_instance, + new Rect(0, 0, image.getWidth(), image.getHeight()), + new Rect(left, top, right, bottom), + paint_instance_or_null, destDensity, srcDensity); + return; + } + + NinePatchChunk chunkObject = getChunk(c); + assert chunkObject != null; + if (chunkObject == null) { + return; + } + + Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance); + if (canvas_delegate == null) { + assert false; + return; + } + + // this one can be null + Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null); + + Graphics2D graphics; + if (paint_delegate != null) { + graphics = canvas_delegate.getCustomGraphics(paint_delegate); + } else { + graphics = canvas_delegate.getGraphics2d(); + } + + try { + chunkObject.draw(bitmap_delegate.getImage(), graphics, + left, top, right - left, bottom - top); + } finally { + if (paint_delegate != null) { + graphics.dispose(); + } + } + + } + + /*package*/ static int nativeGetTransparentRegion(int bitmap, byte[] chunk, Rect location) { + return 0; + } + + // ---- Private Helper methods ---- + + /** + * Returns a {@link NinePatchChunk} object for the given serialized representation. + * + * If the chunk is present in the cache then the object from the cache is returned, otherwise + * the array is deserialized into a {@link NinePatchChunk} object. + * + * @param array the serialized representation of the chunk. + * @return the NinePatchChunk or null if deserialization failed. + */ + private static NinePatchChunk getChunk(byte[] array) { + SoftReference<NinePatchChunk> chunkRef = sChunkCache.get(array); + NinePatchChunk chunk = chunkRef.get(); + if (chunk == null) { + ByteArrayInputStream bais = new ByteArrayInputStream(array); + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(bais); + chunk = (NinePatchChunk) ois.readObject(); + + // put back the chunk in the cache + if (chunk != null) { + sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk)); + } + } catch (IOException e) { + // FIXME: log this + return null; + } catch (ClassNotFoundException e) { + // FIXME: log this + return null; + } finally { + if (ois != null) { + try { + ois.close(); + } catch (IOException e) { + } + } + } + } + + return chunk; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index e691fdf..35ba73d 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -26,7 +26,7 @@ import com.android.layoutlib.api.SceneResult; import com.android.layoutlib.bridge.android.BridgeAssetManager; import com.android.layoutlib.bridge.impl.FontLoader; import com.android.layoutlib.bridge.impl.LayoutSceneImpl; -import com.android.ninepatch.NinePatch; +import com.android.ninepatch.NinePatchChunk; import com.android.tools.layoutlib.create.MethodAdapter; import com.android.tools.layoutlib.create.OverrideMethod; @@ -73,13 +73,13 @@ public final class Bridge extends LayoutBridge { private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = new HashMap<Object, Map<String, SoftReference<Bitmap>>>(); - private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache = - new HashMap<Object, Map<String, SoftReference<NinePatch>>>(); + private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache = + new HashMap<Object, Map<String, SoftReference<NinePatchChunk>>>(); private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<String, SoftReference<Bitmap>>(); - private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache = - new HashMap<String, SoftReference<NinePatch>>(); + private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache = + new HashMap<String, SoftReference<NinePatchChunk>>(); private static Map<String, Map<String, Integer>> sEnumValueMap; @@ -252,23 +252,23 @@ public final class Bridge extends LayoutBridge { } /** - * Sets a 9 patch in a project cache or in the framework cache. + * Sets a 9 patch chunk in a project cache or in the framework cache. * @param value the path of the 9 patch * @param ninePatch the 9 patch object * @param projectKey the key of the project, or null to put the bitmap in the framework cache. */ - public static void setCached9Patch(String value, NinePatch ninePatch, Object projectKey) { + public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) { if (projectKey != null) { - Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); + Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); if (map == null) { - map = new HashMap<String, SoftReference<NinePatch>>(); + map = new HashMap<String, SoftReference<NinePatchChunk>>(); sProject9PatchCache.put(projectKey, map); } - map.put(value, new SoftReference<NinePatch>(ninePatch)); + map.put(value, new SoftReference<NinePatchChunk>(ninePatch)); } else { - sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch)); + sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch)); } } @@ -436,24 +436,24 @@ public final class Bridge extends LayoutBridge { } /** - * Returns the 9 patch for a specific path, from a specific project cache, or from the + * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the * framework cache. * @param value the path of the 9 patch * @param projectKey the key of the project, or null to query the framework cache. * @return the cached 9 patch or null if not found. */ - public static NinePatch getCached9Patch(String value, Object projectKey) { + public static NinePatchChunk getCached9Patch(String value, Object projectKey) { if (projectKey != null) { - Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); + Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); if (map != null) { - SoftReference<NinePatch> ref = map.get(value); + SoftReference<NinePatchChunk> ref = map.get(value); if (ref != null) { return ref.get(); } } } else { - SoftReference<NinePatch> ref = sFramework9PatchCache.get(value); + SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value); if (ref != null) { return ref.get(); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/NinePatchDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/NinePatchDrawable.java deleted file mode 100644 index 4efa631..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/NinePatchDrawable.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.layoutlib.bridge.android; - -import com.android.ninepatch.NinePatch; - -import android.graphics.Canvas; -import android.graphics.Canvas_Delegate; -import android.graphics.ColorFilter; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; - -public class NinePatchDrawable extends Drawable { - - private NinePatch m9Patch; - - public NinePatchDrawable(NinePatch ninePatch) { - m9Patch = ninePatch; - } - - @Override - public int getMinimumWidth() { - return m9Patch.getWidth(); - } - - @Override - public int getMinimumHeight() { - return m9Patch.getHeight(); - } - - /** - * Return the intrinsic width of the underlying drawable object. Returns - * -1 if it has no intrinsic width, such as with a solid color. - */ - @Override - public int getIntrinsicWidth() { - return m9Patch.getWidth(); - } - - /** - * Return the intrinsic height of the underlying drawable object. Returns - * -1 if it has no intrinsic height, such as with a solid color. - */ - @Override - public int getIntrinsicHeight() { - return m9Patch.getHeight(); - } - - /** - * Return in padding the insets suggested by this Drawable for placing - * content inside the drawable's bounds. Positive values move toward the - * center of the Drawable (set Rect.inset). Returns true if this drawable - * actually has a padding, else false. When false is returned, the padding - * is always set to 0. - */ - @Override - public boolean getPadding(Rect padding) { - int[] padd = new int[4]; - m9Patch.getPadding(padd); - padding.left = padd[0]; - padding.top = padd[1]; - padding.right = padd[2]; - padding.bottom = padd[3]; - return true; - } - - @Override - public void draw(Canvas canvas) { - Rect r = getBounds(); - Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas); - m9Patch.draw(canvasDelegate.getGraphics2d(), r.left, r.top, r.width(), r.height()); - - return; - } - - - // ----------- Not implemented methods --------------- - - - @Override - public int getOpacity() { - // FIXME - return 0xFF; - } - - @Override - public void setAlpha(int arg0) { - // FIXME ! - } - - @Override - public void setColorFilter(ColorFilter arg0) { - // FIXME - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java index 2e3f9a8..f7d249e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java @@ -313,10 +313,12 @@ public class LayoutSceneImpl { // create an Android bitmap around the BufferedImage Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage, + true /*isMutable*/, Density.getEnum(mParams.getDensity())); // create a Canvas around the Android bitmap Canvas canvas = new Canvas(bitmap); + canvas.setDensity(mParams.getDensity()); // to set the logger, get the native delegate Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index 3e506b8..ceb8a0d 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -22,8 +22,8 @@ import com.android.layoutlib.api.IResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; -import com.android.layoutlib.bridge.android.NinePatchDrawable; import com.android.ninepatch.NinePatch; +import com.android.ninepatch.NinePatchChunk; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; @@ -31,9 +31,12 @@ import org.xmlpull.v1.XmlPullParserException; import android.graphics.Bitmap; import android.graphics.Bitmap_Delegate; +import android.graphics.NinePatch_Delegate; +import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; import android.util.TypedValue; import java.io.File; @@ -121,15 +124,38 @@ public final class ResourceHelper { if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) { File file = new File(stringValue); if (file.isFile()) { - NinePatch ninePatch = Bridge.getCached9Patch(stringValue, + // see if we still have both the chunk and the bitmap in the caches + NinePatchChunk chunk = Bridge.getCached9Patch(stringValue, + isFramework ? null : context.getProjectKey()); + Bitmap bitmap = Bridge.getCachedBitmap(stringValue, isFramework ? null : context.getProjectKey()); - if (ninePatch == null) { + // if either chunk or bitmap is null, then we reload the 9-patch file. + if (chunk == null || bitmap == null) { try { - ninePatch = NinePatch.load(file.toURL(), false /* convert */); + NinePatch ninePatch = NinePatch.load(file.toURL(), false /* convert */); + if (ninePatch != null) { + if (chunk == null) { + chunk = ninePatch.getChunk(); - Bridge.setCached9Patch(stringValue, ninePatch, - isFramework ? null : context.getProjectKey()); + Bridge.setCached9Patch(stringValue, chunk, + isFramework ? null : context.getProjectKey()); + } + + if (bitmap == null) { + Density density = Density.MEDIUM; + if (value instanceof IDensityBasedResourceValue) { + density = ((IDensityBasedResourceValue)value).getDensity(); + } + + bitmap = Bitmap_Delegate.createBitmap(ninePatch.getImage(), + false /*isMutable*/, + density); + + Bridge.setCachedBitmap(stringValue, bitmap, + isFramework ? null : context.getProjectKey()); + } + } } catch (MalformedURLException e) { // URL is wrong, we'll return null below } catch (IOException e) { @@ -137,8 +163,13 @@ public final class ResourceHelper { } } - if (ninePatch != null) { - return new NinePatchDrawable(ninePatch); + if (chunk != null && bitmap != null) { + int[] padding = chunk.getPadding(); + Rect paddingRect = new Rect(padding[0], padding[1], padding[2], padding[3]); + + return new NinePatchDrawable(context.getResources(), bitmap, + NinePatch_Delegate.serialize(chunk), + paddingRect, null); } } @@ -174,27 +205,15 @@ public final class ResourceHelper { isFramework ? null : context.getProjectKey()); if (bitmap == null) { - // always create the cache copy in the original density. - bitmap = Bitmap_Delegate.createBitmap(bmpFile, Density.MEDIUM); - Bridge.setCachedBitmap(stringValue, bitmap, - isFramework ? null : context.getProjectKey()); - } - - try { + Density density = Density.MEDIUM; if (value instanceof IDensityBasedResourceValue) { - Density density = ((IDensityBasedResourceValue)value).getDensity(); - if (density != Density.MEDIUM) { - // create a copy of the bitmap - bitmap = Bitmap.createBitmap(bitmap); - - // apply the density - bitmap.setDensity(density.getValue()); - } + density = ((IDensityBasedResourceValue)value).getDensity(); } - } catch (NoClassDefFoundError error) { - // look like we're running in an older version of ADT that doesn't include - // the new layoutlib_api. Let's just ignore this, the drawing will just be - // wrong. + + bitmap = Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/, + density); + Bridge.setCachedBitmap(stringValue, bitmap, + isFramework ? null : context.getProjectKey()); } return new BitmapDrawable(context.getResources(), bitmap); diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java index 23351ab..a3219e7 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java @@ -48,5 +48,4 @@ public class NinePatchTest extends TestCase { assertEquals(36, mPatch.getWidth()); assertEquals(25, mPatch.getHeight()); } - } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index b9c7113..bb2e6b3 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -109,6 +109,7 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.DashPathEffect", "android.graphics.LinearGradient", "android.graphics.Matrix", + "android.graphics.NinePatch", "android.graphics.Paint", "android.graphics.PathEffect", "android.graphics.PorterDuffXfermode", diff --git a/tools/validatekeymaps/Android.mk b/tools/validatekeymaps/Android.mk new file mode 100644 index 0000000..90979e1 --- /dev/null +++ b/tools/validatekeymaps/Android.mk @@ -0,0 +1,34 @@ +# +# Copyright 2010 The Android Open Source Project +# +# Keymap validation tool. +# + +# This tool is prebuilt if we're doing an app-only build. +ifeq ($(TARGET_BUILD_APPS),) + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + Main.cpp + +LOCAL_CFLAGS := -Wall -Werror + +#LOCAL_C_INCLUDES += + +LOCAL_STATIC_LIBRARIES := \ + libui \ + libutils \ + libcutils + +ifeq ($(HOST_OS),linux) +LOCAL_LDLIBS += -lpthread +endif + +LOCAL_MODULE := validatekeymaps +LOCAL_MODULE_TAGS := optional + +include $(BUILD_HOST_EXECUTABLE) + +endif # TARGET_BUILD_APPS diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp new file mode 100644 index 0000000..6ec223b --- /dev/null +++ b/tools/validatekeymaps/Main.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ui/KeyCharacterMap.h> +#include <ui/KeyLayoutMap.h> +#include <utils/String8.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +using namespace android; + +static const char* gProgName = "validatekeymaps"; + +enum FileType { + FILETYPE_UNKNOWN, + FILETYPE_KEYLAYOUT, + FILETYPE_KEYCHARACTERMAP, +}; + + +static void usage() { + fprintf(stderr, "Keymap Validation Tool\n\n"); + fprintf(stderr, "Usage:\n"); + fprintf(stderr, + " %s [FILENAME.kl] [FILENAME.kcm] [...]\n" + " Validates the specified key layout and/or key character map files.\n\n", gProgName); +} + +static FileType getFileType(const char* filename) { + const char *extension = strrchr(filename, '.'); + if (extension) { + if (strcmp(extension, ".kl") == 0) { + return FILETYPE_KEYLAYOUT; + } + if (strcmp(extension, ".kcm") == 0) { + return FILETYPE_KEYCHARACTERMAP; + } + } + return FILETYPE_UNKNOWN; +} + +static bool validateFile(const char* filename) { + fprintf(stdout, "Validating file '%s'...\n", filename); + + FileType fileType = getFileType(filename); + switch (fileType) { + case FILETYPE_UNKNOWN: + fprintf(stderr, "File extension must be .kl or .kcm.\n\n"); + return false; + + case FILETYPE_KEYLAYOUT: { + KeyLayoutMap* map; + status_t status = KeyLayoutMap::load(String8(filename), &map); + if (status) { + fprintf(stderr, "Error %d parsing key layout file.\n\n", status); + return false; + } + break; + } + + case FILETYPE_KEYCHARACTERMAP: { + KeyCharacterMap* map; + status_t status = KeyCharacterMap::load(String8(filename), &map); + if (status) { + fprintf(stderr, "Error %d parsing key character map file.\n\n", status); + return false; + } + break; + } + } + + fputs("No errors.\n\n", stdout); + return true; +} + +int main(int argc, const char** argv) { + if (argc < 2) { + usage(); + return 1; + } + + int result = 0; + for (int i = 1; i < argc; i++) { + if (!validateFile(argv[i])) { + result = 1; + } + } + + if (result) { + fputs("Failed!\n", stderr); + } else { + fputs("Success.\n", stdout); + } + return result; +} diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 90abd02..7e3df1a 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -1529,7 +1529,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } void setNetworkAvailable(boolean available) { - sendMessage(CMD_SET_NETWORK_AVAILABLE, available ? 1 : 0); + sendMessage(obtainMessage(CMD_SET_NETWORK_AVAILABLE, available ? 1 : 0, 0)); } /******************************************************** |