Merge "Don't drop zeros in the second position in formatDuration()"
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 @@
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 @@
}
}
});
+ delayAnim.start();
}
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
@@ -408,6 +423,11 @@
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 @@
* @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 @@
}
Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
node.addDependency(dependency);
+ return this;
}
/**
@@ -913,7 +934,7 @@
* @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 @@
}
Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
node.addDependency(dependency);
+ return this;
}
/**
@@ -932,7 +954,7 @@
* @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 @@
}
Dependency dependency = new Dependency(node, Dependency.AFTER);
mCurrentNode.addDependency(dependency);
+ return this;
}
/**
@@ -951,11 +974,12 @@
* @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 @@
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 @@
/**
* 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 @@
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.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 @@
* 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 @@
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 @@
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 ActivityManagerNative()
- {
+ public ActivityManagerNative() {
attachInterface(this, descriptor);
}
@@ -1390,16 +1376,27 @@
}
}
-
+
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 @@
(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 @@
}
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 @@
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 @@
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 @@
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 94960791..72377f0 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -121,27 +121,31 @@
*/
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 @@
}
/**
- * 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 @@
* 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.
+ * <p>Pass in the maximum number of allowed allocations. Use -1 to disable
+ * the limit. Returns the previous limit.</p>
*
- * The preferred way to use this is:
- *
+ * <p>The preferred way to use this is:
+ * <pre>
* int prevLimit = -1;
* try {
* prevLimit = Debug.setAllocationLimit(0);
@@ -683,16 +689,16 @@
* } 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 @@
* 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 @@
* 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 @@
* <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 @@
}
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 @@
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 @@
}
/**
- * 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 @@
* 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 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 @@
}
}
}
-
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.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 @@
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 @@
}
// 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 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 @@
}
};
+ 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 @@
}
}
+ @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 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 @@
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
similarity index 89%
rename from core/java/android/provider/Mtp.java
rename to 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 @@
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
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 @@
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 @@
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 @@
});
}
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 @@
}
/**
+ * 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 @@
/**
* 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 @@
*/
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 @@
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 @@
mWindowTouchSlop = WINDOW_TOUCH_SLOP;
//noinspection deprecation
mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
+ mOverscrollDistance = OVERSCROLL_DISTANCE;
+ mOverflingDistance = OVERFLING_DISTANCE;
}
/**
@@ -216,6 +230,9 @@
// 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 @@
}
/**
+ * @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.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 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 @@
}
}
}
-
+
/**
- * 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 @@
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 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 @@
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 b2ba7e23..755366c 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -330,4 +330,14 @@
*/
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 @@
private AutoFillProfile mAutoFillProfile;
+ private boolean mUseWebViewBackgroundForOverscroll = true;
+
// private WebSettings, not accessible by the host activity
static private int mDoubleTapToastCount = 3;
@@ -631,6 +633,23 @@
}
/**
+ * 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 @@
}
}
+ /**
+ * @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.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 @@
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 @@
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 @@
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 @@
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 @@
/* 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.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.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.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 @@
// 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 @@
// 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 @@
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 @@
// 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 @@
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 @@
* @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 @@
}
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 @@
// 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 @@
// 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 @@
return false;
}
- @Override
- protected int computeHorizontalScrollRange() {
+ private int computeRealHorizontalScrollRange() {
if (mDrawHistory) {
return mHistoryWidth;
} else if (mHorizontalScrollBarMode == SCROLLBAR_ALWAYSOFF
@@ -2512,7 +2557,27 @@
}
@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 @@
}
@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 @@
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 @@
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 @@
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 @@
}
int saveCount = canvas.save();
+ if (mInOverScrollMode && !getSettings()
+ .getUseWebViewBackgroundForOverscrollBackground()) {
+ drawOverScrollBackground(canvas);
+ }
if (mTitleBar != null) {
canvas.translate(0, (int) mTitleBar.getHeight());
}
@@ -3466,6 +3624,10 @@
}
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 @@
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 @@
// 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 @@
@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 @@
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 @@
}
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 @@
}
}
- // 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 @@
}
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
mVelocityTracker.recycle();
mVelocityTracker = null;
}
+
+ // Release any pulled glows
+ if (mOverScrollGlow != null) {
+ mOverScrollGlow.releaseAll();
+ }
}
private void cancelTouch() {
@@ -5478,6 +5680,7 @@
mVelocityTracker.recycle();
mVelocityTracker = null;
}
+
if (mTouchMode == TOUCH_DRAG_MODE ||
mTouchMode == TOUCH_DRAG_LAYER_MODE) {
WebViewCore.resumePriority();
@@ -5789,12 +5992,20 @@
}
}
- 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 void flingScroll(int vx, int vy) {
mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
- computeMaxScrollY());
+ computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
invalidate();
}
@@ -5843,6 +6054,10 @@
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 @@
+ " 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 @@
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 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.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.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 @@
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 @@
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 @@
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 @@
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 @@
}
/**
+ * @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 @@
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 @@
layoutChildren();
mInLayout = false;
+
+ mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
}
/**
@@ -2126,6 +2228,7 @@
mFlingRunnable.endFling();
if (mScrollY != 0) {
mScrollY = 0;
+ finishGlows();
invalidate();
}
}
@@ -2445,9 +2548,10 @@
// 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 @@
// 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 @@
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 @@
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 @@
}
// 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 @@
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 @@
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 @@
}
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 @@
}
@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 @@
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 @@
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 @@
/**
* 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 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 @@
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 @@
}
}
+ 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 @@
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 @@
final int firstPosition = mFirstPosition;
- if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) {
- // Don't need to move views down if the top of the first position
- // is already visible
- return true;
+ // 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 + childCount == mItemCount && lastBottom <= end && deltaY <= 0) {
+ 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 incrementalDeltaY != 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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
private void initScrollView() {
- mScroller = new Scroller(getContext());
+ mScroller = new OverScroller(getContext());
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
@@ -199,6 +209,8 @@
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mOverscrollDistance = configuration.getScaledOverscrollDistance();
+ mOverflingDistance = configuration.getScaledOverflingDistance();
}
@Override
@@ -463,6 +475,9 @@
/* 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ @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 @@
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 @@
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 @@
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 @@
}
}
+ @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 @@
Drawable mDivider;
int mDividerHeight;
+ Drawable mOverScrollHeader;
+ Drawable mOverScrollFooter;
+
private boolean mIsCacheColorOpaque;
private boolean mDividerIsOpaque;
@@ -152,6 +155,18 @@
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 @@
}
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
private void initScrollView() {
- mScroller = new Scroller(getContext());
+ mScroller = new OverScroller(getContext());
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
@@ -195,6 +213,8 @@
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mOverscrollDistance = configuration.getScaledOverscrollDistance();
+ mOverflingDistance = configuration.getScaledOverflingDistance();
}
@Override
@@ -427,6 +447,9 @@
if (yDiff > mTouchSlop) {
mIsBeingDragged = true;
mLastMotionY = y;
+ if (mScrollStrictSpan == null) {
+ mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
+ }
}
break;
}
@@ -451,6 +474,9 @@
* being flinged.
*/
mIsBeingDragged = !mScroller.isFinished();
+ if (mIsBeingDragged && mScrollStrictSpan == null) {
+ mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
+ }
break;
}
@@ -459,6 +485,9 @@
/* 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 @@
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 @@
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
+ if (mFlingStrictSpan != null) {
+ mFlingStrictSpan.finish();
+ mFlingStrictSpan = null;
+ }
}
// Remember where the motion event started
@@ -516,7 +547,36 @@
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 @@
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 @@
}
}
+ @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 @@
} else {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
+ if (mFlingStrictSpan != null) {
+ mFlingStrictSpan.finish();
+ mFlingStrictSpan = null;
+ }
}
scrollBy(dx, dy);
}
@@ -948,7 +1039,16 @@
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 @@
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 @@
}
@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 @@
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 @@
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 @@
}
}
+ @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 @@
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 @@
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 @@
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 @@
/**
* 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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
* 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 @@
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 @@
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
index 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
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_15.png b/core/res/res/drawable-mdpi/stat_sys_battery_15.png
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_unknown.png b/core/res/res/drawable-mdpi/stat_sys_battery_unknown.png
index 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
Binary files differ
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 @@
ctrl, alt, meta: none
}
+### Numeric keypad ###
+
key NUMPAD_0 {
label, number: '0'
base: fallback INSERT
@@ -499,3 +503,25 @@
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 @@
ctrl, alt, meta: none
}
+### Numeric keypad ###
+
key NUMPAD_0 {
label, number: '0'
base: fallback INSERT
@@ -496,3 +500,25 @@
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,$(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 @@
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 @@
/** 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 @@
/** 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 @@
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 @@
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 @@
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 @@
}
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::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 @@
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.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 @@
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
similarity index 90%
rename from media/java/android/media/MtpClient.java
rename to 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 @@
/**
* {@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 @@
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 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
similarity index 75%
rename from media/java/android/media/MtpCursor.java
rename to 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 @@
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 @@
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 @@
}
/* 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 @@
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(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(Mtp.Object.NAME, new Integer(OBJECT_NAME));
+ 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 @@
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_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 @@
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 @@
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
similarity index 80%
rename from media/jni/android_media_MtpClient.cpp
rename to 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 @@
// ----------------------------------------------------------------------------
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
// ----------------------------------------------------------------------------
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 @@
}
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
similarity index 72%
rename from media/jni/android_media_MtpCursor.cpp
rename to 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 @@
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 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 @@
}
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 @@
}
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 @@
"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 @@
// ----------------------------------------------------------------------------
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 @@
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 (!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);
+ 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);
+ 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 @@
LOCAL_SRC_FILES:= \
MtpClient.cpp \
- MtpCursor.cpp \
MtpDataPacket.cpp \
MtpDebug.cpp \
MtpDevice.cpp \
@@ -38,6 +37,7 @@
MtpStringBuffer.cpp \
MtpStorage.cpp \
MtpUtils.cpp \
+ PtpCursor.cpp \
LOCAL_MODULE:= libmtp
@@ -47,32 +47,3 @@
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 @@
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 @@
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::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 @@
kFormNone = 0,
kFormRange = 1,
kFormEnum = 2,
+ kFormDateTime = 3,
};
uint32_t mGroupCode;
@@ -90,6 +91,7 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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
similarity index 91%
rename from media/mtp/MtpCursor.cpp
rename to 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 @@
#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() {
+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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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::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 @@
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 @@
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
similarity index 93%
rename from media/mtp/MtpCursor.h
rename to 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 @@
class CursorWindow;
-class MtpCursor {
+class PtpCursor {
private:
enum {
DEVICE = 1,
@@ -45,10 +45,10 @@
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 @@
}; // 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.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 @@
}
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 @@
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 @@
// 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.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 @@
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 @@
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 @@
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.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 @@
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 @@
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 @@
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 @@
}
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.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 @@
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 @@
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 @@
// 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
deleted file mode 100644
index 9ababb7..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_mini.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png
deleted file mode 100644
index ffbd2d3..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png
index 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
Binary files differ
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
index 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
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png
index 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
Binary files differ
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
index 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
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1x.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1x.png
new file mode 100644
index 0000000..02c27ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1x.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png
index 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
Binary files differ
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
index 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
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png
index 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
Binary files differ
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
index 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
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3g.png
new file mode 100644
index 0000000..84ac927
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png
index 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
Binary files differ
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
index 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
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_edge.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_edge.png
new file mode 100644
index 0000000..13cae40
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_edge.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png
index da5b02c..690b5f6c7 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_gprs.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_gprs.png
new file mode 100644
index 0000000..d0a4fd0
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_gprs.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_hsdpa.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_hsdpa.png
new file mode 100644
index 0000000..05976bd
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_hsdpa.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_roam.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_roam.png
new file mode 100644
index 0000000..2cc3cd6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_roam.png
Binary files differ
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
index 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
Binary files differ
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
new file mode 100644
index 0000000..1c59b2a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0_fully.png
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index ab1b0c67..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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="top"
- android:layout_marginTop="18dp"
- />
- <ImageView
android:id="@+id/network"
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_marginRight="4dp"
+ />
+ <ImageView
+ android:id="@+id/battery"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="top"
+ 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.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 @@
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 @@
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 @@
// 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 @@
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 @@
mBatteryStats = BatteryStatsService.getService();
}
- public void addPhoneIconView(ImageView v) {
- mPhoneIconViews.add(v);
+ public void addPhoneSignalIconView(ImageView v) {
+ mPhoneSignalIconViews.add(v);
}
- public void addCombinedDataIconView(ImageView v) {
- mDataIconViews.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 addDataTypeIconView(ImageView v) {
+ mDataTypeIconViews.add(v);
}
public void addLabelView(TextView v) {
@@ -141,7 +161,6 @@
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 @@
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 @@
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 @@
iconList = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[mInetCondition];
}
mPhoneSignalIconId = iconList[iconLevel];
+ mDataSignalIconId = TelephonyIcons.DATA_SIGNAL_STRENGTH[mInetCondition][iconLevel];
}
}
}
@@ -370,36 +394,47 @@
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 @@
iconId = mDataIconList[0];
break;
}
- mDataIconId = iconId;
+ mDataDirectionIconId = iconId;
} else {
iconId = 0;
visible = false;
@@ -477,7 +512,7 @@
Binder.restoreCallingIdentity(ident);
}
- mDataIconId = iconId;
+ mDataDirectionIconId = iconId;
mDataConnected = visible;
}
@@ -489,8 +524,7 @@
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 @@
void refreshViews() {
Context context = mContext;
- int combinedDataIconId;
+ int combinedSignalIconId;
+ int dataTypeIconId;
String label;
+ int N;
if (mWifiConnected) {
if (mWifiSsid == null) {
@@ -585,35 +621,84 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
* 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 @@
*/
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
}
}
- 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 @@
}
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 @@
+ 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 @@
}
}
+ 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);
+ 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 mShortcutInputMethodsAndSubtypesObjectList;
+ 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.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 @@
// 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 @@
// 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 @@
= 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 @@
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 @@
/* 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 @@
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 @@
{
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 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 @@
sp<ResourceTypeSet> raws;
sp<ResourceTypeSet> colors;
sp<ResourceTypeSet> menus;
+ sp<ResourceTypeSet> mipmaps;
ASSIGN_IT(drawable);
ASSIGN_IT(layout);
@@ -810,6 +811,7 @@
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 @@
!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 @@
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 @@
}
}
+ 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 @@
}
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 @@
}
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 @@
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 @@
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 @@
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 @@
"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 @@
(((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 @@
}
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 @@
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 @@
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 @@
// 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 @@
/**
* 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 @@
// 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 @@
}
/*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 @@
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 @@
* 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.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 @@
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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
// 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.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 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 @@
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 @@
}
}
- 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,29 +205,17 @@
isFramework ? null : context.getProjectKey());
if (bitmap == null) {
- // always create the cache copy in the original density.
- bitmap = Bitmap_Delegate.createBitmap(bmpFile, Density.MEDIUM);
+ Density density = Density.MEDIUM;
+ if (value instanceof IDensityBasedResourceValue) {
+ density = ((IDensityBasedResourceValue)value).getDensity();
+ }
+
+ bitmap = Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/,
+ density);
Bridge.setCachedBitmap(stringValue, bitmap,
isFramework ? null : context.getProjectKey());
}
- try {
- 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());
- }
- }
- } 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.
- }
-
return new BitmapDrawable(context.getResources(), bitmap);
} catch (IOException e) {
// we'll return null below
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 @@
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 b9c71133..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 @@
"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 @@
}
void setNetworkAvailable(boolean available) {
- sendMessage(CMD_SET_NETWORK_AVAILABLE, available ? 1 : 0);
+ sendMessage(obtainMessage(CMD_SET_NETWORK_AVAILABLE, available ? 1 : 0, 0));
}
/********************************************************