diff options
153 files changed, 6817 insertions, 1000 deletions
diff --git a/api/current.xml b/api/current.xml index 403e3d0..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" @@ -18961,7 +18994,7 @@ visibility="public" > <method name="after" - return="void" + return="android.animation.AnimatorSet.Builder" abstract="false" native="false" synchronized="false" @@ -18974,7 +19007,7 @@ </parameter> </method> <method name="after" - return="void" + return="android.animation.AnimatorSet.Builder" abstract="false" native="false" synchronized="false" @@ -18987,7 +19020,7 @@ </parameter> </method> <method name="before" - return="void" + return="android.animation.AnimatorSet.Builder" abstract="false" native="false" synchronized="false" @@ -19000,7 +19033,7 @@ </parameter> </method> <method name="with" - return="void" + return="android.animation.AnimatorSet.Builder" abstract="false" native="false" synchronized="false" @@ -157973,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" @@ -205682,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" @@ -207013,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" @@ -207166,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" @@ -208083,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" @@ -208917,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" @@ -209797,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" @@ -221035,7 +221206,7 @@ > </method> <method name="getShortcutInputMethodsAndSubtypes" - return="java.util.List<android.util.Pair<android.view.inputmethod.InputMethodInfo, android.view.inputmethod.InputMethodSubtype>>" + return="java.util.Map<android.view.inputmethod.InputMethodInfo, java.util.List<android.view.inputmethod.InputMethodSubtype>>" abstract="false" native="false" synchronized="false" @@ -221222,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" @@ -224647,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" @@ -225252,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" @@ -237693,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" @@ -237784,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" @@ -238335,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" @@ -248624,7 +249212,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="t" type="T"> +<parameter name="arg0" type="T"> </parameter> </method> </interface> 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/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 273e3c6..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); } @@ -1390,16 +1376,27 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM } } - + 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 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/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/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 86322ac..854428f 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -19,9 +19,11 @@ 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; @@ -922,10 +924,8 @@ public final class StrictMode { return; } - // TODO: cache the window manager stub? final IWindowManager windowManager = (info.policy & PENALTY_FLASH) != 0 ? - IWindowManager.Stub.asInterface(ServiceManager.getService("window")) : - null; + sWindowManager.get() : null; if (windowManager != null) { try { windowManager.showStrictModeViolation(true); @@ -988,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); @@ -1092,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); } @@ -1404,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) * @@ -1545,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/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/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 a5f3ade..9bc1c22 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -23,7 +23,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -49,7 +48,9 @@ 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; @@ -1277,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. @@ -1293,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 @@ -1454,30 +1472,29 @@ public final class InputMethodManager { } } - public List<Pair<InputMethodInfo, InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() { + public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() { synchronized (mH) { - List<Pair<InputMethodInfo, InputMethodSubtype>> ret = - new ArrayList<Pair<InputMethodInfo, InputMethodSubtype>>(); + 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, imi2, subtype2, imi3, subtype3,..... in the list - Object imi; - Object subtype; - if (info != null && info.size() > 0) { - final int N = info.size(); - if (N % 2 == 0) { - for (int i = 0; i < N;) { - if ((imi = info.get(i++)) instanceof InputMethodInfo) { - subtype = info.get(i++); - ret.add(new Pair<InputMethodInfo, InputMethodSubtype> ( - (InputMethodInfo)imi, - (subtype instanceof InputMethodSubtype) ? - (InputMethodSubtype)subtype : null)); + // "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); } - } else { - Log.w(TAG, "The size of list was illegal."); } } } catch (RemoteException e) { @@ -1486,6 +1503,7 @@ public final class InputMethodManager { return ret; } } + public boolean switchToLastInputMethod(IBinder imeToken) { synchronized (mH) { try { 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 fafb6be..e1a5c2d 100644 --- a/core/java/android/webkit/WebTextView.java +++ b/core/java/android/webkit/WebTextView.java @@ -26,6 +26,8 @@ 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; @@ -129,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; @@ -140,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. @@ -163,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) { @@ -801,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)); + } } } }); @@ -1124,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 602975f..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; @@ -909,7 +937,7 @@ public class WebView extends AbsoluteLayout 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, @@ -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) { @@ -3570,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. @@ -4051,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)); @@ -4697,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); + } } } @@ -4829,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; @@ -4857,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. @@ -4866,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"); } @@ -4877,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 @@ -5150,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) { @@ -5360,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; @@ -5380,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); @@ -5455,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(); } @@ -5468,6 +5665,11 @@ public class WebView extends AbsoluteLayout mVelocityTracker.recycle(); mVelocityTracker = null; } + + // Release any pulled glows + if (mOverScrollGlow != null) { + mOverScrollGlow.releaseAll(); + } } private void cancelTouch() { @@ -5478,6 +5680,7 @@ public class WebView extends AbsoluteLayout mVelocityTracker.recycle(); mVelocityTracker = null; } + if (mTouchMode == TOUCH_DRAG_MODE || mTouchMode == TOUCH_DRAG_LAYER_MODE) { WebViewCore.resumePriority(); @@ -5789,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); } @@ -5813,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(); } @@ -5843,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(); @@ -5869,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(); } @@ -6674,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); } 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 78f3cd9..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'). diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 125b210..a2e9486 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -55,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/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/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 642a588..15b5db4 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -346,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 4aa16f9..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> @@ -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/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/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/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/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/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/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/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/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/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 3f32f2f..3108e4e 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -3898,32 +3898,34 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { mOutputFormat->setInt32(kKeyHeight, video_def->nFrameHeight); mOutputFormat->setInt32(kKeyColorFormat, video_def->eColorFormat); - 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); + 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; 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/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/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/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 b2cc6bd..723432d 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -240,10 +240,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private InputMethodSubtype mCurrentSubtype; // This list contains the pairs of InputMethodInfo and InputMethodSubtype. - private List<Pair<InputMethodInfo, InputMethodSubtype>> mShortcutInputMethodsAndSubtypes; - // This list is used for returning the pairs of InputMethodInfo and InputMethodSubtype through - // aidl. This list has imi1, subtype1 imi2, subtype2... - private List mShortcutInputMethodsAndSubtypesObjectList; + private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>> + mShortcutInputMethodsAndSubtypes = + new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>(); /** * Set to true if our ServiceConnection is currently actively bound to @@ -992,7 +991,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurMethodId = null; unbindCurrentMethodLocked(true); } - mShortcutInputMethodsAndSubtypes = null; + mShortcutInputMethodsAndSubtypes.clear(); } else { // There is no longer an input method set, so stop any current one. mCurMethodId = null; @@ -1301,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) { @@ -1310,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; } @@ -1319,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( @@ -1910,11 +1920,13 @@ 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; @@ -2022,7 +2034,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + mostApplicableIMI.getId() + "," + mostApplicableSubtypeId); } if (mostApplicableIMI != null && mostApplicableSubtypeId != NOT_A_SUBTYPE_ID) { - ArrayList<Parcelable> ret = new ArrayList<Parcelable>(2); return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI, mostApplicableIMI.getSubtypes().get(mostApplicableSubtypeId)); } else { @@ -2067,25 +2078,37 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + 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 (mShortcutInputMethodsAndSubtypesObjectList != null) { + 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. - mShortcutInputMethodsAndSubtypes = - new ArrayList<Pair<InputMethodInfo, InputMethodSubtype>>(); - mShortcutInputMethodsAndSubtypes.add( - findLastResortApplicableShortcutInputMethodAndSubtypeLocked( - SUBTYPE_MODE_VOICE)); - mShortcutInputMethodsAndSubtypesObjectList = new ArrayList<Parcelable>(); - for (Pair ime: mShortcutInputMethodsAndSubtypes) { - mShortcutInputMethodsAndSubtypesObjectList.add(ime.first); - mShortcutInputMethodsAndSubtypesObjectList.add(ime.second); - } - } - return mShortcutInputMethodsAndSubtypesObjectList; + 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; } } 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/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 1773eca..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. @@ -6671,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/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)); } /******************************************************** |