summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/animation/LayoutTransition.java339
-rwxr-xr-xcore/java/android/animation/ValueAnimator.java50
-rw-r--r--core/java/android/app/ActivityOptions.java84
-rw-r--r--core/java/android/app/ContextImpl.java3
-rw-r--r--core/java/android/app/DownloadManager.java20
-rw-r--r--core/java/android/app/Notification.java58
-rw-r--r--core/java/android/content/Intent.java53
-rw-r--r--core/java/android/content/pm/PackageParser.java4
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java8
-rw-r--r--core/java/android/hardware/LegacySensorManager.java434
-rw-r--r--core/java/android/hardware/Sensor.java9
-rw-r--r--core/java/android/hardware/SensorManager.java929
-rw-r--r--core/java/android/hardware/SystemSensorManager.java398
-rw-r--r--core/java/android/net/ConnectivityManager.java24
-rw-r--r--core/java/android/net/IConnectivityManager.aidl1
-rw-r--r--core/java/android/net/INetworkPolicyManager.aidl1
-rw-r--r--core/java/android/net/INetworkStatsService.aidl3
-rw-r--r--core/java/android/net/NetworkTemplate.java46
-rw-r--r--core/java/android/net/TrafficStats.java106
-rw-r--r--core/java/android/net/nsd/DnsSdServiceInfo.java20
-rw-r--r--core/java/android/net/nsd/DnsSdTxtRecord.java1
-rw-r--r--core/java/android/net/nsd/NsdManager.java334
-rw-r--r--core/java/android/os/Debug.java38
-rw-r--r--core/java/android/os/Trace.java1
-rw-r--r--core/java/android/provider/CalendarContract.java27
-rwxr-xr-xcore/java/android/server/BluetoothService.java5
-rw-r--r--core/java/android/service/textservice/SpellCheckerService.java185
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java1
-rw-r--r--core/java/android/view/IWindowManager.aidl2
-rw-r--r--core/java/android/view/TextureView.java1
-rw-r--r--core/java/android/view/View.java51
-rw-r--r--core/java/android/view/ViewGroup.java69
-rw-r--r--core/java/android/view/ViewRootImpl.java5
-rw-r--r--core/java/android/view/textservice/SpellCheckerInfo.java13
-rw-r--r--core/java/android/view/textservice/SpellCheckerSession.java16
-rw-r--r--core/java/android/webkit/CallbackProxy.java2
-rwxr-xr-xcore/java/android/webkit/GeolocationPermissions.java2
-rw-r--r--core/java/android/webkit/WebSettings.java22
-rw-r--r--core/java/android/webkit/WebView.java16
-rw-r--r--core/java/android/webkit/WebViewCore.java3
-rw-r--r--core/java/android/widget/AdapterView.java3
-rw-r--r--core/java/android/widget/GridLayout.java48
-rw-r--r--core/java/android/widget/SpellChecker.java26
-rw-r--r--core/java/com/android/internal/app/ActionBarImpl.java17
-rw-r--r--core/java/com/android/internal/util/XmlUtils.java15
-rw-r--r--core/java/com/android/internal/widget/SizeAdaptiveLayout.java405
46 files changed, 2751 insertions, 1147 deletions
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 634e4d8..c643137 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -119,6 +119,24 @@ public class LayoutTransition {
public static final int DISAPPEARING = 3;
/**
+ * A flag indicating the animation that runs on those items that are changing
+ * due to a layout change not caused by items being added to or removed
+ * from the container. This transition type is not enabled by default; it can be
+ * enabled via {@link #enableTransitionType(int)}.
+ */
+ public static final int CHANGING = 4;
+
+ /**
+ * Private bit fields used to set the collection of enabled transition types for
+ * mTransitionTypes.
+ */
+ private static final int FLAG_APPEARING = 0x01;
+ private static final int FLAG_DISAPPEARING = 0x02;
+ private static final int FLAG_CHANGE_APPEARING = 0x04;
+ private static final int FLAG_CHANGE_DISAPPEARING = 0x08;
+ private static final int FLAG_CHANGING = 0x10;
+
+ /**
* These variables hold the animations that are currently used to run the transition effects.
* These animations are set to defaults, but can be changed to custom animations by
* calls to setAnimator().
@@ -127,11 +145,13 @@ public class LayoutTransition {
private Animator mAppearingAnim = null;
private Animator mChangingAppearingAnim = null;
private Animator mChangingDisappearingAnim = null;
+ private Animator mChangingAnim = null;
/**
* These are the default animations, defined in the constructor, that will be used
* unless the user specifies custom animations.
*/
+ private static ObjectAnimator defaultChange;
private static ObjectAnimator defaultChangeIn;
private static ObjectAnimator defaultChangeOut;
private static ObjectAnimator defaultFadeIn;
@@ -143,15 +163,16 @@ public class LayoutTransition {
private static long DEFAULT_DURATION = 300;
/**
- * The durations of the four different animations
+ * The durations of the different animations
*/
private long mChangingAppearingDuration = DEFAULT_DURATION;
private long mChangingDisappearingDuration = DEFAULT_DURATION;
+ private long mChangingDuration = DEFAULT_DURATION;
private long mAppearingDuration = DEFAULT_DURATION;
private long mDisappearingDuration = DEFAULT_DURATION;
/**
- * The start delays of the four different animations. Note that the default behavior of
+ * The start delays of the different animations. Note that the default behavior of
* the appearing item is the default duration, since it should wait for the items to move
* before fading it. Same for the changing animation when disappearing; it waits for the item
* to fade out before moving the other items.
@@ -160,12 +181,14 @@ public class LayoutTransition {
private long mDisappearingDelay = 0;
private long mChangingAppearingDelay = 0;
private long mChangingDisappearingDelay = DEFAULT_DURATION;
+ private long mChangingDelay = 0;
/**
- * The inter-animation delays used on the two changing animations
+ * The inter-animation delays used on the changing animations
*/
private long mChangingAppearingStagger = 0;
private long mChangingDisappearingStagger = 0;
+ private long mChangingStagger = 0;
/**
* The default interpolators used for the animations
@@ -174,6 +197,7 @@ public class LayoutTransition {
private TimeInterpolator mDisappearingInterpolator = new AccelerateDecelerateInterpolator();
private TimeInterpolator mChangingAppearingInterpolator = new DecelerateInterpolator();
private TimeInterpolator mChangingDisappearingInterpolator = new DecelerateInterpolator();
+ private TimeInterpolator mChangingInterpolator = new DecelerateInterpolator();
/**
* These hashmaps are used to store the animations that are currently running as part of
@@ -212,6 +236,13 @@ public class LayoutTransition {
private long staggerDelay;
/**
+ * These are the types of transition animations that the LayoutTransition is reacting
+ * to. By default, appearing/disappearing and the change animations related to them are
+ * enabled (not CHANGING).
+ */
+ private int mTransitionTypes = FLAG_CHANGE_APPEARING | FLAG_CHANGE_DISAPPEARING |
+ FLAG_APPEARING | FLAG_DISAPPEARING;
+ /**
* The set of listeners that should be notified when APPEARING/DISAPPEARING transitions
* start and end.
*/
@@ -248,6 +279,9 @@ public class LayoutTransition {
defaultChangeOut = defaultChangeIn.clone();
defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
+ defaultChange = defaultChangeIn.clone();
+ defaultChange.setStartDelay(mChangingDelay);
+ defaultChange.setInterpolator(mChangingInterpolator);
defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
defaultFadeIn.setDuration(DEFAULT_DURATION);
@@ -260,6 +294,7 @@ public class LayoutTransition {
}
mChangingAppearingAnim = defaultChangeIn;
mChangingDisappearingAnim = defaultChangeOut;
+ mChangingAnim = defaultChange;
mAppearingAnim = defaultFadeIn;
mDisappearingAnim = defaultFadeOut;
}
@@ -275,18 +310,101 @@ public class LayoutTransition {
public void setDuration(long duration) {
mChangingAppearingDuration = duration;
mChangingDisappearingDuration = duration;
+ mChangingDuration = duration;
mAppearingDuration = duration;
mDisappearingDuration = duration;
}
/**
+ * Enables the specified transitionType for this LayoutTransition object.
+ * By default, a LayoutTransition listens for changes in children being
+ * added/remove/hidden/shown in the container, and runs the animations associated with
+ * those events. That is, all transition types besides {@link #CHANGING} are enabled by default.
+ * You can also enable {@link #CHANGING} animations by calling this method with the
+ * {@link #CHANGING} transitionType.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
+ */
+ public void enableTransitionType(int transitionType) {
+ switch (transitionType) {
+ case APPEARING:
+ mTransitionTypes |= FLAG_APPEARING;
+ break;
+ case DISAPPEARING:
+ mTransitionTypes |= FLAG_DISAPPEARING;
+ break;
+ case CHANGE_APPEARING:
+ mTransitionTypes |= FLAG_CHANGE_APPEARING;
+ break;
+ case CHANGE_DISAPPEARING:
+ mTransitionTypes |= FLAG_CHANGE_DISAPPEARING;
+ break;
+ case CHANGING:
+ mTransitionTypes |= FLAG_CHANGING;
+ break;
+ }
+ }
+
+ /**
+ * Disables the specified transitionType for this LayoutTransition object.
+ * By default, all transition types except {@link #CHANGING} are enabled.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
+ */
+ public void disableTransitionType(int transitionType) {
+ switch (transitionType) {
+ case APPEARING:
+ mTransitionTypes &= ~FLAG_APPEARING;
+ break;
+ case DISAPPEARING:
+ mTransitionTypes &= ~FLAG_DISAPPEARING;
+ break;
+ case CHANGE_APPEARING:
+ mTransitionTypes &= ~FLAG_CHANGE_APPEARING;
+ break;
+ case CHANGE_DISAPPEARING:
+ mTransitionTypes &= ~FLAG_CHANGE_DISAPPEARING;
+ break;
+ case CHANGING:
+ mTransitionTypes &= ~FLAG_CHANGING;
+ break;
+ }
+ }
+
+ /**
+ * Returns whether the specified transitionType is enabled for this LayoutTransition object.
+ * By default, all transition types except {@link #CHANGING} are enabled.
+ *
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}.
+ * @return true if the specified transitionType is currently enabled, false otherwise.
+ */
+ public boolean isTransitionTypeEnabled(int transitionType) {
+ switch (transitionType) {
+ case APPEARING:
+ return (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING;
+ case DISAPPEARING:
+ return (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING;
+ case CHANGE_APPEARING:
+ return (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING;
+ case CHANGE_DISAPPEARING:
+ return (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING;
+ case CHANGING:
+ return (mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING;
+ }
+ return false;
+ }
+
+ /**
* Sets the start delay on one of the animation objects used by this transition. The
* <code>transitionType</code> parameter determines the animation whose start delay
* is being set.
*
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start
- * delay is being set.
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose start delay is being set.
* @param delay The length of time, in milliseconds, to delay before starting the animation.
* @see Animator#setStartDelay(long)
*/
@@ -298,6 +416,9 @@ public class LayoutTransition {
case CHANGE_DISAPPEARING:
mChangingDisappearingDelay = delay;
break;
+ case CHANGING:
+ mChangingDelay = delay;
+ break;
case APPEARING:
mAppearingDelay = delay;
break;
@@ -312,9 +433,9 @@ public class LayoutTransition {
* <code>transitionType</code> parameter determines the animation whose start delay
* is returned.
*
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start
- * delay is returned.
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose start delay is returned.
* @return long The start delay of the specified animation.
* @see Animator#getStartDelay()
*/
@@ -324,6 +445,8 @@ public class LayoutTransition {
return mChangingAppearingDelay;
case CHANGE_DISAPPEARING:
return mChangingDisappearingDelay;
+ case CHANGING:
+ return mChangingDelay;
case APPEARING:
return mAppearingDelay;
case DISAPPEARING:
@@ -338,9 +461,9 @@ public class LayoutTransition {
* <code>transitionType</code> parameter determines the animation whose duration
* is being set.
*
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
- * duration is being set.
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose duration is being set.
* @param duration The length of time, in milliseconds, that the specified animation should run.
* @see Animator#setDuration(long)
*/
@@ -352,6 +475,9 @@ public class LayoutTransition {
case CHANGE_DISAPPEARING:
mChangingDisappearingDuration = duration;
break;
+ case CHANGING:
+ mChangingDuration = duration;
+ break;
case APPEARING:
mAppearingDuration = duration;
break;
@@ -366,9 +492,9 @@ public class LayoutTransition {
* <code>transitionType</code> parameter determines the animation whose duration
* is returned.
*
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
- * duration is returned.
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose duration is returned.
* @return long The duration of the specified animation.
* @see Animator#getDuration()
*/
@@ -378,6 +504,8 @@ public class LayoutTransition {
return mChangingAppearingDuration;
case CHANGE_DISAPPEARING:
return mChangingDisappearingDuration;
+ case CHANGING:
+ return mChangingDuration;
case APPEARING:
return mAppearingDuration;
case DISAPPEARING:
@@ -389,9 +517,10 @@ public class LayoutTransition {
/**
* Sets the length of time to delay between starting each animation during one of the
- * CHANGE animations.
+ * change animations.
*
- * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}.
+ * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or
+ * {@link #CHANGING}.
* @param duration The length of time, in milliseconds, to delay before launching the next
* animation in the sequence.
*/
@@ -403,15 +532,19 @@ public class LayoutTransition {
case CHANGE_DISAPPEARING:
mChangingDisappearingStagger = duration;
break;
+ case CHANGING:
+ mChangingStagger = duration;
+ break;
// noop other cases
}
}
/**
- * Tets the length of time to delay between starting each animation during one of the
- * CHANGE animations.
+ * Gets the length of time to delay between starting each animation during one of the
+ * change animations.
*
- * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}.
+ * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or
+ * {@link #CHANGING}.
* @return long The length of time, in milliseconds, to delay before launching the next
* animation in the sequence.
*/
@@ -421,6 +554,8 @@ public class LayoutTransition {
return mChangingAppearingStagger;
case CHANGE_DISAPPEARING:
return mChangingDisappearingStagger;
+ case CHANGING:
+ return mChangingStagger;
}
// shouldn't reach here
return 0;
@@ -431,9 +566,9 @@ public class LayoutTransition {
* <code>transitionType</code> parameter determines the animation whose interpolator
* is being set.
*
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
- * duration is being set.
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose interpolator is being set.
* @param interpolator The interpolator that the specified animation should use.
* @see Animator#setInterpolator(TimeInterpolator)
*/
@@ -445,6 +580,9 @@ public class LayoutTransition {
case CHANGE_DISAPPEARING:
mChangingDisappearingInterpolator = interpolator;
break;
+ case CHANGING:
+ mChangingInterpolator = interpolator;
+ break;
case APPEARING:
mAppearingInterpolator = interpolator;
break;
@@ -459,9 +597,9 @@ public class LayoutTransition {
* <code>transitionType</code> parameter determines the animation whose interpolator
* is returned.
*
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
- * duration is being set.
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose interpolator is being returned.
* @return TimeInterpolator The interpolator that the specified animation uses.
* @see Animator#setInterpolator(TimeInterpolator)
*/
@@ -471,6 +609,8 @@ public class LayoutTransition {
return mChangingAppearingInterpolator;
case CHANGE_DISAPPEARING:
return mChangingDisappearingInterpolator;
+ case CHANGING:
+ return mChangingInterpolator;
case APPEARING:
return mAppearingInterpolator;
case DISAPPEARING:
@@ -504,9 +644,9 @@ public class LayoutTransition {
* values queried when the transition begins may need to use a different mechanism
* than a standard ObjectAnimator object.</p>
*
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
- * duration is being set.
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the
+ * animation whose animator is being set.
* @param animator The animation being assigned. A value of <code>null</code> means that no
* animation will be run for the specified transitionType.
*/
@@ -518,6 +658,9 @@ public class LayoutTransition {
case CHANGE_DISAPPEARING:
mChangingDisappearingAnim = animator;
break;
+ case CHANGING:
+ mChangingAnim = animator;
+ break;
case APPEARING:
mAppearingAnim = animator;
break;
@@ -530,9 +673,9 @@ public class LayoutTransition {
/**
* Gets the animation used during one of the transition types that may run.
*
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
- * duration is being set.
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines
+ * the animation whose animator is being returned.
* @return Animator The animation being used for the given transition type.
* @see #setAnimator(int, Animator)
*/
@@ -542,6 +685,8 @@ public class LayoutTransition {
return mChangingAppearingAnim;
case CHANGE_DISAPPEARING:
return mChangingDisappearingAnim;
+ case CHANGING:
+ return mChangingAnim;
case APPEARING:
return mAppearingAnim;
case DISAPPEARING:
@@ -554,20 +699,44 @@ public class LayoutTransition {
/**
* This function sets up animations on all of the views that change during layout.
* For every child in the parent, we create a change animation of the appropriate
- * type (appearing or disappearing) and ask it to populate its start values from its
+ * type (appearing, disappearing, or changing) and ask it to populate its start values from its
* target view. We add layout listeners to all child views and listen for changes. For
* those views that change, we populate the end values for those animations and start them.
* Animations are not run on unchanging views.
*
- * @param parent The container which is undergoing an appearing or disappearing change.
- * @param newView The view being added to or removed from the parent.
- * @param changeReason A value of APPEARING or DISAPPEARING, indicating whether the
- * transition is occuring because an item is being added to or removed from the parent.
+ * @param parent The container which is undergoing a change.
+ * @param newView The view being added to or removed from the parent. May be null if the
+ * changeReason is CHANGING.
+ * @param changeReason A value of APPEARING, DISAPPEARING, or CHANGING, indicating whether the
+ * transition is occurring because an item is being added to or removed from the parent, or
+ * if it is running in response to a layout operation (that is, if the value is CHANGING).
*/
private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) {
- Animator baseAnimator = (changeReason == APPEARING) ?
- mChangingAppearingAnim : mChangingDisappearingAnim;
+ Animator baseAnimator = null;
+ Animator parentAnimator = null;
+ final long duration;
+ switch (changeReason) {
+ case APPEARING:
+ baseAnimator = mChangingAppearingAnim;
+ duration = mChangingAppearingDuration;
+ parentAnimator = defaultChangeIn;
+ break;
+ case DISAPPEARING:
+ baseAnimator = mChangingDisappearingAnim;
+ duration = mChangingDisappearingDuration;
+ parentAnimator = defaultChangeOut;
+ break;
+ case CHANGING:
+ baseAnimator = mChangingAnim;
+ duration = mChangingDuration;
+ parentAnimator = defaultChange;
+ break;
+ default:
+ // Shouldn't reach here
+ duration = 0;
+ break;
+ }
// If the animation is null, there's nothing to do
if (baseAnimator == null) {
return;
@@ -575,8 +744,6 @@ public class LayoutTransition {
// reset the inter-animation delay, in case we use it later
staggerDelay = 0;
- final long duration = (changeReason == APPEARING) ?
- mChangingAppearingDuration : mChangingDisappearingDuration;
final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup
if (!observer.isAlive()) {
@@ -594,8 +761,6 @@ public class LayoutTransition {
}
}
if (mAnimateParentHierarchy) {
- Animator parentAnimator = (changeReason == APPEARING) ?
- defaultChangeIn : defaultChangeOut;
ViewGroup tempParent = parent;
while (tempParent != null) {
ViewParent parentParent = tempParent.getParent();
@@ -727,13 +892,20 @@ public class LayoutTransition {
}
}
- long startDelay;
- if (changeReason == APPEARING) {
- startDelay = mChangingAppearingDelay + staggerDelay;
- staggerDelay += mChangingAppearingStagger;
- } else {
- startDelay = mChangingDisappearingDelay + staggerDelay;
- staggerDelay += mChangingDisappearingStagger;
+ long startDelay = 0;
+ switch (changeReason) {
+ case APPEARING:
+ startDelay = mChangingAppearingDelay + staggerDelay;
+ staggerDelay += mChangingAppearingStagger;
+ break;
+ case DISAPPEARING:
+ startDelay = mChangingDisappearingDelay + staggerDelay;
+ staggerDelay += mChangingDisappearingStagger;
+ break;
+ case CHANGING:
+ startDelay = mChangingDelay + staggerDelay;
+ staggerDelay += mChangingStagger;
+ break;
}
anim.setStartDelay(startDelay);
anim.setDuration(duration);
@@ -766,7 +938,8 @@ public class LayoutTransition {
for (TransitionListener listener : mListeners) {
listener.startTransition(LayoutTransition.this, parent, child,
changeReason == APPEARING ?
- CHANGE_APPEARING : CHANGE_DISAPPEARING);
+ CHANGE_APPEARING : changeReason == DISAPPEARING ?
+ CHANGE_DISAPPEARING : CHANGING);
}
}
}
@@ -784,7 +957,8 @@ public class LayoutTransition {
for (TransitionListener listener : mListeners) {
listener.endTransition(LayoutTransition.this, parent, child,
changeReason == APPEARING ?
- CHANGE_APPEARING : CHANGE_DISAPPEARING);
+ CHANGE_APPEARING : changeReason == DISAPPEARING ?
+ CHANGE_DISAPPEARING : CHANGING);
}
}
}
@@ -831,6 +1005,8 @@ public class LayoutTransition {
anim.start();
anim.end();
}
+ // listeners should clean up the currentChangingAnimations list, but just in case...
+ currentChangingAnimations.clear();
}
/**
@@ -902,6 +1078,7 @@ public class LayoutTransition {
switch (transitionType) {
case CHANGE_APPEARING:
case CHANGE_DISAPPEARING:
+ case CHANGING:
if (currentChangingAnimations.size() > 0) {
LinkedHashMap<View, Animator> currentAnimCopy =
(LinkedHashMap<View, Animator>) currentChangingAnimations.clone();
@@ -1031,21 +1208,48 @@ public class LayoutTransition {
* affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
*/
private void addChild(ViewGroup parent, View child, boolean changesLayout) {
- // Want disappearing animations to finish up before proceeding
- cancel(DISAPPEARING);
- if (changesLayout) {
+ if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
+ // Want disappearing animations to finish up before proceeding
+ cancel(DISAPPEARING);
+ }
+ if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
// Also, cancel changing animations so that we start fresh ones from current locations
cancel(CHANGE_APPEARING);
+ cancel(CHANGING);
}
- if (mListeners != null) {
+ if (mListeners != null && (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
for (TransitionListener listener : mListeners) {
listener.startTransition(this, parent, child, APPEARING);
}
}
- if (changesLayout) {
+ if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
runChangeTransition(parent, child, APPEARING);
}
- runAppearingTransition(parent, child);
+ if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
+ runAppearingTransition(parent, child);
+ }
+ }
+
+ /**
+ * This method is called by ViewGroup when there is a call to layout() on the container
+ * with this LayoutTransition. If the CHANGING transition is enabled and if there is no other
+ * transition currently running on the container, then this call runs a CHANGING transition.
+ * The transition does not start immediately; it just sets up the mechanism to run if any
+ * of the children of the container change their layout parameters (similar to
+ * the CHANGE_APPEARING and CHANGE_DISAPPEARING transitions).
+ *
+ * @param parent The ViewGroup whose layout() method has been called.
+ *
+ * @hide
+ */
+ public void layoutChange(ViewGroup parent) {
+ if ((mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING && !isRunning()) {
+ // This method is called for all calls to layout() in the container, including
+ // those caused by add/remove/hide/show events, which will already have set up
+ // transition animations. Avoid setting up CHANGING animations in this case; only
+ // do so when there is not a transition already running on the container.
+ runChangeTransition(parent, null, CHANGING);
+ }
}
/**
@@ -1097,21 +1301,28 @@ public class LayoutTransition {
* affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
*/
private void removeChild(ViewGroup parent, View child, boolean changesLayout) {
- // Want appearing animations to finish up before proceeding
- cancel(APPEARING);
- if (changesLayout) {
+ if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
+ // Want appearing animations to finish up before proceeding
+ cancel(APPEARING);
+ }
+ if (changesLayout &&
+ (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
// Also, cancel changing animations so that we start fresh ones from current locations
cancel(CHANGE_DISAPPEARING);
+ cancel(CHANGING);
}
- if (mListeners != null) {
+ if (mListeners != null && (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
for (TransitionListener listener : mListeners) {
listener.startTransition(this, parent, child, DISAPPEARING);
}
}
- if (changesLayout) {
+ if (changesLayout &&
+ (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
runChangeTransition(parent, child, DISAPPEARING);
}
- runDisappearingTransition(parent, child);
+ if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
+ runDisappearingTransition(parent, child);
+ }
}
/**
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index fade20c..2154b14 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -150,6 +150,13 @@ public class ValueAnimator extends Animator {
private boolean mStarted = false;
/**
+ * Tracks whether we've notified listeners of the onAnimationSTart() event. This can be
+ * complex to keep track of since we notify listeners at different times depending on
+ * startDelay and whether start() was called before end().
+ */
+ private boolean mStartListenersCalled = false;
+
+ /**
* Flag that denotes whether the animation is set up and ready to go. Used to
* set up animation that has not yet been started.
*/
@@ -885,6 +892,18 @@ public class ValueAnimator extends Animator {
}
}
+ private void notifyStartListeners() {
+ if (mListeners != null && !mStartListenersCalled) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ tmpListeners.get(i).onAnimationStart(this);
+ }
+ }
+ mStartListenersCalled = true;
+ }
+
/**
* 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
@@ -914,15 +933,7 @@ public class ValueAnimator extends Animator {
setCurrentPlayTime(getCurrentPlayTime());
mPlayingState = STOPPED;
mRunning = true;
-
- if (mListeners != null) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationStart(this);
- }
- }
+ notifyStartListeners();
}
animationHandler.sendEmptyMessage(ANIMATION_START);
}
@@ -941,7 +952,11 @@ public class ValueAnimator extends Animator {
|| handler.mPendingAnimations.contains(this)
|| handler.mDelayedAnims.contains(this)) {
// Only notify listeners if the animator has actually started
- if (mRunning && mListeners != null) {
+ if ((mStarted || mRunning) && mListeners != null) {
+ if (!mRunning) {
+ // If it's not yet running, then start listeners weren't called. Call them now.
+ notifyStartListeners();
+ }
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
for (AnimatorListener listener : tmpListeners) {
@@ -959,6 +974,7 @@ public class ValueAnimator extends Animator {
// Special case if the animation has not yet started; get it ready for ending
mStartedDelay = false;
startAnimation(handler);
+ mStarted = true;
} else if (!mInitialized) {
initAnimation();
}
@@ -1010,7 +1026,11 @@ public class ValueAnimator extends Animator {
handler.mPendingAnimations.remove(this);
handler.mDelayedAnims.remove(this);
mPlayingState = STOPPED;
- if (mRunning && mListeners != null) {
+ if ((mStarted || mRunning) && mListeners != null) {
+ if (!mRunning) {
+ // If it's not yet running, then start listeners weren't called. Call them now.
+ notifyStartListeners();
+ }
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
@@ -1020,6 +1040,7 @@ public class ValueAnimator extends Animator {
}
mRunning = false;
mStarted = false;
+ mStartListenersCalled = false;
}
/**
@@ -1032,12 +1053,7 @@ public class ValueAnimator extends Animator {
if (mStartDelay > 0 && mListeners != null) {
// Listeners were already notified in start() if startDelay is 0; this is
// just for delayed animations
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationStart(this);
- }
+ notifyStartListeners();
}
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index c3cceaf..423b02a 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -73,6 +73,18 @@ public class ActivityOptions {
public static final String KEY_ANIM_START_Y = "android:animStartY";
/**
+ * Initial width of the animation.
+ * @hide
+ */
+ public static final String KEY_ANIM_START_WIDTH = "android:animStartWidth";
+
+ /**
+ * Initial height of the animation.
+ * @hide
+ */
+ public static final String KEY_ANIM_START_HEIGHT = "android:animStartHeight";
+
+ /**
* Callback for when animation is started.
* @hide
*/
@@ -83,7 +95,9 @@ public class ActivityOptions {
/** @hide */
public static final int ANIM_CUSTOM = 1;
/** @hide */
- public static final int ANIM_THUMBNAIL = 2;
+ public static final int ANIM_SCALE_UP = 2;
+ /** @hide */
+ public static final int ANIM_THUMBNAIL = 3;
private String mPackageName;
private int mAnimationType = ANIM_NONE;
@@ -92,6 +106,8 @@ public class ActivityOptions {
private Bitmap mThumbnail;
private int mStartX;
private int mStartY;
+ private int mStartWidth;
+ private int mStartHeight;
private IRemoteCallback mAnimationStartedListener;
/**
@@ -127,6 +143,34 @@ public class ActivityOptions {
}
/**
+ * Create an ActivityOptions specifying an animation where the new
+ * activity is scaled from a small originating area of the screen to
+ * its final full representation.
+ *
+ * @param source The View that the new activity is animating from. This
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+ * @param startX The x starting location of the new activity, relative to <var>source</var>.
+ * @param startY The y starting location of the activity, relative to <var>source</var>.
+ * @param startWidth The initial width of the new activity.
+ * @param startWidth The initial height of the new activity.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static ActivityOptions makeScaleUpAnimation(View source,
+ int startX, int startY, int startWidth, int startHeight) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mPackageName = source.getContext().getPackageName();
+ opts.mAnimationType = ANIM_SCALE_UP;
+ int[] pts = new int[2];
+ source.getLocationOnScreen(pts);
+ opts.mStartX = pts[0] + startX;
+ opts.mStartY = pts[1] + startY;
+ opts.mStartWidth = startWidth;
+ opts.mStartHeight = startHeight;
+ return opts;
+ }
+
+ /**
* Create an ActivityOptions specifying an animation where a thumbnail
* is scaled from a given position to the new activity window that is
* being started.
@@ -135,8 +179,8 @@ public class ActivityOptions {
* defines the coordinate space for <var>startX</var> and <var>startY</var>.
* @param thumbnail The bitmap that will be shown as the initial thumbnail
* of the animation.
- * @param startX The x starting location of the bitmap, in screen coordiantes.
- * @param startY The y starting location of the bitmap, in screen coordinates.
+ * @param startX The x starting location of the bitmap, relative to <var>source</var>.
+ * @param startY The y starting location of the bitmap, relative to <var>source</var>.
* @return Returns a new ActivityOptions object that you can use to
* supply these options as the options Bundle when starting an activity.
*/
@@ -154,8 +198,8 @@ public class ActivityOptions {
* defines the coordinate space for <var>startX</var> and <var>startY</var>.
* @param thumbnail The bitmap that will be shown as the initial thumbnail
* of the animation.
- * @param startX The x starting location of the bitmap, in screen coordiantes.
- * @param startY The y starting location of the bitmap, in screen coordinates.
+ * @param startX The x starting location of the bitmap, relative to <var>source</var>.
+ * @param startY The y starting location of the bitmap, relative to <var>source</var>.
* @param listener Optional OnAnimationStartedListener to find out when the
* requested animation has started running. If for some reason the animation
* is not executed, the callback will happen immediately.
@@ -199,6 +243,11 @@ public class ActivityOptions {
if (mAnimationType == ANIM_CUSTOM) {
mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
+ } else if (mAnimationType == ANIM_SCALE_UP) {
+ mStartX = opts.getInt(KEY_ANIM_START_X, 0);
+ mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
+ mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0);
+ mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0);
} else if (mAnimationType == ANIM_THUMBNAIL) {
mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL);
mStartX = opts.getInt(KEY_ANIM_START_X, 0);
@@ -244,6 +293,16 @@ public class ActivityOptions {
}
/** @hide */
+ public int getStartWidth() {
+ return mStartWidth;
+ }
+
+ /** @hide */
+ public int getStartHeight() {
+ return mStartHeight;
+ }
+
+ /** @hide */
public IRemoteCallback getOnAnimationStartListener() {
return mAnimationStartedListener;
}
@@ -281,6 +340,13 @@ public class ActivityOptions {
mThumbnail = null;
mAnimationStartedListener = null;
break;
+ case ANIM_SCALE_UP:
+ mAnimationType = otherOptions.mAnimationType;
+ mStartX = otherOptions.mStartX;
+ mStartY = otherOptions.mStartY;
+ mStartWidth = otherOptions.mStartWidth;
+ mStartHeight = otherOptions.mStartHeight;
+ break;
case ANIM_THUMBNAIL:
mAnimationType = otherOptions.mAnimationType;
mThumbnail = otherOptions.mThumbnail;
@@ -316,6 +382,13 @@ public class ActivityOptions {
b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
break;
+ case ANIM_SCALE_UP:
+ b.putInt(KEY_ANIM_TYPE, mAnimationType);
+ b.putInt(KEY_ANIM_START_X, mStartX);
+ b.putInt(KEY_ANIM_START_Y, mStartY);
+ b.putInt(KEY_ANIM_START_WIDTH, mStartWidth);
+ b.putInt(KEY_ANIM_START_HEIGHT, mStartHeight);
+ break;
case ANIM_THUMBNAIL:
b.putInt(KEY_ANIM_TYPE, mAnimationType);
b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
@@ -323,6 +396,7 @@ public class ActivityOptions {
b.putInt(KEY_ANIM_START_Y, mStartY);
b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
!= null ? mAnimationStartedListener.asBinder() : null);
+ break;
}
return b;
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 0645aa9..8942135 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -45,6 +45,7 @@ import android.graphics.drawable.Drawable;
import android.hardware.ISerialManager;
import android.hardware.SensorManager;
import android.hardware.SerialManager;
+import android.hardware.SystemSensorManager;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
import android.hardware.usb.IUsbManager;
@@ -407,7 +408,7 @@ class ContextImpl extends Context {
registerService(SENSOR_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
- return new SensorManager(ctx.mMainThread.getHandler().getLooper());
+ return new SystemSensorManager(ctx.mMainThread.getHandler().getLooper());
}});
registerService(STATUS_BAR_SERVICE, new ServiceFetcher() {
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index dd58397..55f29e6 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -349,6 +349,7 @@ public class DownloadManager {
private String mMimeType;
private boolean mRoamingAllowed = true;
private int mAllowedNetworkTypes = ~0; // default to all network types allowed
+ private boolean mAllowedOverMetered = true;
private boolean mIsVisibleInDownloadsUi = true;
private boolean mScannable = false;
private boolean mUseSystemCache = false;
@@ -609,8 +610,11 @@ public class DownloadManager {
}
/**
- * Restrict the types of networks over which this download may proceed. By default, all
- * network types are allowed.
+ * Restrict the types of networks over which this download may proceed.
+ * By default, all network types are allowed. Consider using
+ * {@link #setAllowedOverMetered(boolean)} instead, since it's more
+ * flexible.
+ *
* @param flags any combination of the NETWORK_* bit flags.
* @return this object
*/
@@ -620,6 +624,17 @@ public class DownloadManager {
}
/**
+ * Set whether this download may proceed over a metered network
+ * connection. By default, metered networks are allowed.
+ *
+ * @see ConnectivityManager#isActiveNetworkMetered()
+ */
+ public Request setAllowedOverMetered(boolean allow) {
+ mAllowedOverMetered = allow;
+ return this;
+ }
+
+ /**
* Set whether this download may proceed over a roaming connection. By default, roaming is
* allowed.
* @param allowed whether to allow a roaming connection to be used
@@ -672,6 +687,7 @@ public class DownloadManager {
putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription);
putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType);
+ // TODO: add COLUMN_ALLOW_METERED and persist
values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility);
values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5cce25f..22d84f0 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1681,7 +1681,7 @@ public class Notification implements Parcelable
}
private RemoteViews makeBigContentView() {
- RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(R.layout.notification_template_base);
+ RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(R.layout.notification_template_big_text);
contentView.setTextViewText(R.id.big_text, mBigText);
contentView.setViewVisibility(R.id.big_text, View.VISIBLE);
@@ -1696,4 +1696,60 @@ public class Notification implements Parcelable
return wip;
}
}
+
+ /**
+ * Helper class for generating large-format notifications that include a list of (up to 5) strings.
+ *
+ * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so:
+ * <pre class="prettyprint">
+ * Notification noti = new Notification.DigestStyle(
+ * new Notification.Builder()
+ * .setContentTitle(&quot;New mail from &quot; + sender.toString())
+ * .setContentText(subject)
+ * .setSmallIcon(R.drawable.new_mail)
+ * .setLargeIcon(aBitmap))
+ * .addLine(str1)
+ * .addLine(str2)
+ * .build();
+ * </pre>
+ *
+ * @see Notification#bigContentView
+ */
+ public static class InboxStyle {
+ private Builder mBuilder;
+ private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5);
+
+ public InboxStyle(Builder builder) {
+ mBuilder = builder;
+ }
+
+ public InboxStyle addLine(CharSequence cs) {
+ mTexts.add(cs);
+ return this;
+ }
+
+ private RemoteViews makeBigContentView() {
+ RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(R.layout.notification_template_inbox);
+
+ int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, R.id.inbox_text4};
+
+ int i=0;
+ while (i < mTexts.size() && i < rowIds.length) {
+ CharSequence str = mTexts.get(i);
+ if (str != null && !str.equals("")) {
+ contentView.setViewVisibility(rowIds[i], View.VISIBLE);
+ contentView.setTextViewText(rowIds[i], str);
+ }
+ i++;
+ }
+
+ return contentView;
+ }
+
+ public Notification build() {
+ Notification wip = mBuilder.getNotification();
+ wip.bigContentView = makeBigContentView();
+ return wip;
+ }
+ }
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 19e4372..6653336 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6576,35 +6576,54 @@ public class Intent implements Parcelable, Cloneable {
final String action = getAction();
if (ACTION_SEND.equals(action)) {
- final Uri stream;
+ Uri stream = null;
try {
stream = getParcelableExtra(EXTRA_STREAM);
} catch (ClassCastException e) {
- return;
}
- if (stream != null) {
+ final CharSequence text = getCharSequenceExtra(EXTRA_TEXT);
+ final String htmlText = getStringExtra(EXTRA_HTML_TEXT);
+ if (stream != null || text != null || htmlText != null) {
final ClipData clipData = new ClipData(
- null, new String[] { getType() }, new ClipData.Item(stream));
-
+ null, new String[] { getType() },
+ new ClipData.Item(text, htmlText, null, stream));
setClipData(clipData);
addFlags(FLAG_GRANT_READ_URI_PERMISSION);
}
} else if (ACTION_SEND_MULTIPLE.equals(action)) {
- final ArrayList<Uri> streams;
+ ArrayList<Uri> streams = null;
try {
streams = getParcelableArrayListExtra(EXTRA_STREAM);
} catch (ClassCastException e) {
- return;
}
- if (streams != null && streams.size() > 0) {
- final Uri firstStream = streams.get(0);
+ final ArrayList<CharSequence> texts = getCharSequenceArrayListExtra(EXTRA_TEXT);
+ final ArrayList<String> htmlTexts = getStringArrayListExtra(EXTRA_HTML_TEXT);
+ int num = -1;
+ if (streams != null) {
+ num = streams.size();
+ }
+ if (texts != null) {
+ if (num >= 0 && num != texts.size()) {
+ // Wha...! F- you.
+ return;
+ }
+ num = texts.size();
+ }
+ if (htmlTexts != null) {
+ if (num >= 0 && num != htmlTexts.size()) {
+ // Wha...! F- you.
+ return;
+ }
+ num = htmlTexts.size();
+ }
+ if (num > 0) {
final ClipData clipData = new ClipData(
- null, new String[] { getType() }, new ClipData.Item(firstStream));
+ null, new String[] { getType() },
+ makeClipItem(streams, texts, htmlTexts, 0));
- final int size = streams.size();
- for (int i = 1; i < size; i++) {
- clipData.addItem(new ClipData.Item(streams.get(i)));
+ for (int i = 1; i < num; i++) {
+ clipData.addItem(makeClipItem(streams, texts, htmlTexts, i));
}
setClipData(clipData);
@@ -6612,4 +6631,12 @@ public class Intent implements Parcelable, Cloneable {
}
}
}
+
+ private static ClipData.Item makeClipItem(ArrayList<Uri> streams, ArrayList<CharSequence> texts,
+ ArrayList<String> htmlTexts, int which) {
+ Uri uri = streams != null ? streams.get(which) : null;
+ CharSequence text = texts != null ? texts.get(which) : null;
+ String htmlText = htmlTexts != null ? htmlTexts.get(which) : null;
+ return new ClipData.Item(text, htmlText, null, uri);
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index b6ebbdf..185fcb9 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3458,9 +3458,9 @@ public class PackageParser {
ai.disableCompatibilityMode();
}
if (stopped) {
- p.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED;
+ ai.flags |= ApplicationInfo.FLAG_STOPPED;
} else {
- p.applicationInfo.flags &= ~ApplicationInfo.FLAG_STOPPED;
+ ai.flags &= ~ApplicationInfo.FLAG_STOPPED;
}
if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
ai.enabled = true;
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 254f652..04f6377 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -711,6 +711,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
throw new IllegalArgumentException("sql must not be null.");
}
+ int changedRows = 0;
final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
sql, bindArgs);
try {
@@ -721,8 +722,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
applyBlockGuardPolicy(statement);
attachCancellationSignal(cancellationSignal);
try {
- return nativeExecuteForChangedRowCount(
+ changedRows = nativeExecuteForChangedRowCount(
mConnectionPtr, statement.mStatementPtr);
+ return changedRows;
} finally {
detachCancellationSignal(cancellationSignal);
}
@@ -733,7 +735,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
- mRecentOperations.endOperation(cookie);
+ if (mRecentOperations.endOperationDeferLog(cookie)) {
+ mRecentOperations.logOperation(cookie, "changedRows=" + changedRows);
+ }
}
}
diff --git a/core/java/android/hardware/LegacySensorManager.java b/core/java/android/hardware/LegacySensorManager.java
new file mode 100644
index 0000000..62c194f
--- /dev/null
+++ b/core/java/android/hardware/LegacySensorManager.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2012 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.hardware;
+
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.view.IRotationWatcher;
+import android.view.IWindowManager;
+import android.view.Surface;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Helper class for implementing the legacy sensor manager API.
+ * @hide
+ */
+@SuppressWarnings("deprecation")
+final class LegacySensorManager {
+ private static boolean sInitialized;
+ private static IWindowManager sWindowManager;
+ private static int sRotation = Surface.ROTATION_0;
+
+ private final SensorManager mSensorManager;
+
+ // List of legacy listeners. Guarded by mLegacyListenersMap.
+ private final HashMap<SensorListener, LegacyListener> mLegacyListenersMap =
+ new HashMap<SensorListener, LegacyListener>();
+
+ public LegacySensorManager(SensorManager sensorManager) {
+ mSensorManager = sensorManager;
+
+ synchronized (SensorManager.class) {
+ if (!sInitialized) {
+ sWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService("window"));
+ if (sWindowManager != null) {
+ // if it's null we're running in the system process
+ // which won't get the rotated values
+ try {
+ sRotation = sWindowManager.watchRotation(
+ new IRotationWatcher.Stub() {
+ public void onRotationChanged(int rotation) {
+ LegacySensorManager.onRotationChanged(rotation);
+ }
+ }
+ );
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+ }
+
+ public int getSensors() {
+ int result = 0;
+ final List<Sensor> fullList = mSensorManager.getFullSensorList();
+ for (Sensor i : fullList) {
+ switch (i.getType()) {
+ case Sensor.TYPE_ACCELEROMETER:
+ result |= SensorManager.SENSOR_ACCELEROMETER;
+ break;
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ result |= SensorManager.SENSOR_MAGNETIC_FIELD;
+ break;
+ case Sensor.TYPE_ORIENTATION:
+ result |= SensorManager.SENSOR_ORIENTATION
+ | SensorManager.SENSOR_ORIENTATION_RAW;
+ break;
+ }
+ }
+ return result;
+ }
+
+ public boolean registerListener(SensorListener listener, int sensors, int rate) {
+ if (listener == null) {
+ return false;
+ }
+ boolean result = false;
+ result = registerLegacyListener(SensorManager.SENSOR_ACCELEROMETER,
+ Sensor.TYPE_ACCELEROMETER, listener, sensors, rate) || result;
+ result = registerLegacyListener(SensorManager.SENSOR_MAGNETIC_FIELD,
+ Sensor.TYPE_MAGNETIC_FIELD, listener, sensors, rate) || result;
+ result = registerLegacyListener(SensorManager.SENSOR_ORIENTATION_RAW,
+ Sensor.TYPE_ORIENTATION, listener, sensors, rate) || result;
+ result = registerLegacyListener(SensorManager.SENSOR_ORIENTATION,
+ Sensor.TYPE_ORIENTATION, listener, sensors, rate) || result;
+ result = registerLegacyListener(SensorManager.SENSOR_TEMPERATURE,
+ Sensor.TYPE_TEMPERATURE, listener, sensors, rate) || result;
+ return result;
+ }
+
+ private boolean registerLegacyListener(int legacyType, int type,
+ SensorListener listener, int sensors, int rate) {
+ boolean result = false;
+ // Are we activating this legacy sensor?
+ if ((sensors & legacyType) != 0) {
+ // if so, find a suitable Sensor
+ Sensor sensor = mSensorManager.getDefaultSensor(type);
+ if (sensor != null) {
+ // We do all of this work holding the legacy listener lock to ensure
+ // that the invariants around listeners are maintained. This is safe
+ // because neither registerLegacyListener nor unregisterLegacyListener
+ // are called reentrantly while sensors are being registered or unregistered.
+ synchronized (mLegacyListenersMap) {
+ // If we don't already have one, create a LegacyListener
+ // to wrap this listener and process the events as
+ // they are expected by legacy apps.
+ LegacyListener legacyListener = mLegacyListenersMap.get(listener);
+ if (legacyListener == null) {
+ // we didn't find a LegacyListener for this client,
+ // create one, and put it in our list.
+ legacyListener = new LegacyListener(listener);
+ mLegacyListenersMap.put(listener, legacyListener);
+ }
+
+ // register this legacy sensor with this legacy listener
+ if (legacyListener.registerSensor(legacyType)) {
+ // and finally, register the legacy listener with the new apis
+ result = mSensorManager.registerListener(legacyListener, sensor, rate);
+ } else {
+ result = true; // sensor already enabled
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ public void unregisterListener(SensorListener listener, int sensors) {
+ if (listener == null) {
+ return;
+ }
+ unregisterLegacyListener(SensorManager.SENSOR_ACCELEROMETER, Sensor.TYPE_ACCELEROMETER,
+ listener, sensors);
+ unregisterLegacyListener(SensorManager.SENSOR_MAGNETIC_FIELD, Sensor.TYPE_MAGNETIC_FIELD,
+ listener, sensors);
+ unregisterLegacyListener(SensorManager.SENSOR_ORIENTATION_RAW, Sensor.TYPE_ORIENTATION,
+ listener, sensors);
+ unregisterLegacyListener(SensorManager.SENSOR_ORIENTATION, Sensor.TYPE_ORIENTATION,
+ listener, sensors);
+ unregisterLegacyListener(SensorManager.SENSOR_TEMPERATURE, Sensor.TYPE_TEMPERATURE,
+ listener, sensors);
+ }
+
+ private void unregisterLegacyListener(int legacyType, int type,
+ SensorListener listener, int sensors) {
+ // Are we deactivating this legacy sensor?
+ if ((sensors & legacyType) != 0) {
+ // if so, find the corresponding Sensor
+ Sensor sensor = mSensorManager.getDefaultSensor(type);
+ if (sensor != null) {
+ // We do all of this work holding the legacy listener lock to ensure
+ // that the invariants around listeners are maintained. This is safe
+ // because neither registerLegacyListener nor unregisterLegacyListener
+ // are called re-entrantly while sensors are being registered or unregistered.
+ synchronized (mLegacyListenersMap) {
+ // do we know about this listener?
+ LegacyListener legacyListener = mLegacyListenersMap.get(listener);
+ if (legacyListener != null) {
+ // unregister this legacy sensor and if we don't
+ // need the corresponding Sensor, unregister it too
+ if (legacyListener.unregisterSensor(legacyType)) {
+ // corresponding sensor not needed, unregister
+ mSensorManager.unregisterListener(legacyListener, sensor);
+
+ // finally check if we still need the legacyListener
+ // in our mapping, if not, get rid of it too.
+ if (!legacyListener.hasSensors()) {
+ mLegacyListenersMap.remove(listener);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ static void onRotationChanged(int rotation) {
+ synchronized (SensorManager.class) {
+ sRotation = rotation;
+ }
+ }
+
+ static int getRotation() {
+ synchronized (SensorManager.class) {
+ return sRotation;
+ }
+ }
+
+ private static final class LegacyListener implements SensorEventListener {
+ private float mValues[] = new float[6];
+ private SensorListener mTarget;
+ private int mSensors;
+ private final LmsFilter mYawfilter = new LmsFilter();
+
+ LegacyListener(SensorListener target) {
+ mTarget = target;
+ mSensors = 0;
+ }
+
+ boolean registerSensor(int legacyType) {
+ if ((mSensors & legacyType) != 0) {
+ return false;
+ }
+ boolean alreadyHasOrientationSensor = hasOrientationSensor(mSensors);
+ mSensors |= legacyType;
+ if (alreadyHasOrientationSensor && hasOrientationSensor(legacyType)) {
+ return false; // don't need to re-register the orientation sensor
+ }
+ return true;
+ }
+
+ boolean unregisterSensor(int legacyType) {
+ if ((mSensors & legacyType) == 0) {
+ return false;
+ }
+ mSensors &= ~legacyType;
+ if (hasOrientationSensor(legacyType) && hasOrientationSensor(mSensors)) {
+ return false; // can't unregister the orientation sensor just yet
+ }
+ return true;
+ }
+
+ boolean hasSensors() {
+ return mSensors != 0;
+ }
+
+ private static boolean hasOrientationSensor(int sensors) {
+ return (sensors & (SensorManager.SENSOR_ORIENTATION
+ | SensorManager.SENSOR_ORIENTATION_RAW)) != 0;
+ }
+
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ try {
+ mTarget.onAccuracyChanged(getLegacySensorType(sensor.getType()), accuracy);
+ } catch (AbstractMethodError e) {
+ // old app that doesn't implement this method
+ // just ignore it.
+ }
+ }
+
+ public void onSensorChanged(SensorEvent event) {
+ final float v[] = mValues;
+ v[0] = event.values[0];
+ v[1] = event.values[1];
+ v[2] = event.values[2];
+ int type = event.sensor.getType();
+ int legacyType = getLegacySensorType(type);
+ mapSensorDataToWindow(legacyType, v, LegacySensorManager.getRotation());
+ if (type == Sensor.TYPE_ORIENTATION) {
+ if ((mSensors & SensorManager.SENSOR_ORIENTATION_RAW)!=0) {
+ mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION_RAW, v);
+ }
+ if ((mSensors & SensorManager.SENSOR_ORIENTATION)!=0) {
+ v[0] = mYawfilter.filter(event.timestamp, v[0]);
+ mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION, v);
+ }
+ } else {
+ mTarget.onSensorChanged(legacyType, v);
+ }
+ }
+
+ /*
+ * Helper function to convert the specified sensor's data to the windows's
+ * coordinate space from the device's coordinate space.
+ *
+ * output: 3,4,5: values in the old API format
+ * 0,1,2: transformed values in the old API format
+ *
+ */
+ private void mapSensorDataToWindow(int sensor,
+ float[] values, int orientation) {
+ float x = values[0];
+ float y = values[1];
+ float z = values[2];
+
+ switch (sensor) {
+ case SensorManager.SENSOR_ORIENTATION:
+ case SensorManager.SENSOR_ORIENTATION_RAW:
+ z = -z;
+ break;
+ case SensorManager.SENSOR_ACCELEROMETER:
+ x = -x;
+ y = -y;
+ z = -z;
+ break;
+ case SensorManager.SENSOR_MAGNETIC_FIELD:
+ x = -x;
+ y = -y;
+ break;
+ }
+ values[0] = x;
+ values[1] = y;
+ values[2] = z;
+ values[3] = x;
+ values[4] = y;
+ values[5] = z;
+
+ if ((orientation & Surface.ROTATION_90) != 0) {
+ // handles 90 and 270 rotation
+ switch (sensor) {
+ case SensorManager.SENSOR_ACCELEROMETER:
+ case SensorManager.SENSOR_MAGNETIC_FIELD:
+ values[0] =-y;
+ values[1] = x;
+ values[2] = z;
+ break;
+ case SensorManager.SENSOR_ORIENTATION:
+ case SensorManager.SENSOR_ORIENTATION_RAW:
+ values[0] = x + ((x < 270) ? 90 : -270);
+ values[1] = z;
+ values[2] = y;
+ break;
+ }
+ }
+ if ((orientation & Surface.ROTATION_180) != 0) {
+ x = values[0];
+ y = values[1];
+ z = values[2];
+ // handles 180 (flip) and 270 (flip + 90) rotation
+ switch (sensor) {
+ case SensorManager.SENSOR_ACCELEROMETER:
+ case SensorManager.SENSOR_MAGNETIC_FIELD:
+ values[0] =-x;
+ values[1] =-y;
+ values[2] = z;
+ break;
+ case SensorManager.SENSOR_ORIENTATION:
+ case SensorManager.SENSOR_ORIENTATION_RAW:
+ values[0] = (x >= 180) ? (x - 180) : (x + 180);
+ values[1] =-y;
+ values[2] =-z;
+ break;
+ }
+ }
+ }
+
+ private static int getLegacySensorType(int type) {
+ switch (type) {
+ case Sensor.TYPE_ACCELEROMETER:
+ return SensorManager.SENSOR_ACCELEROMETER;
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ return SensorManager.SENSOR_MAGNETIC_FIELD;
+ case Sensor.TYPE_ORIENTATION:
+ return SensorManager.SENSOR_ORIENTATION_RAW;
+ case Sensor.TYPE_TEMPERATURE:
+ return SensorManager.SENSOR_TEMPERATURE;
+ }
+ return 0;
+ }
+ }
+
+ private static final class LmsFilter {
+ private static final int SENSORS_RATE_MS = 20;
+ private static final int COUNT = 12;
+ private static final float PREDICTION_RATIO = 1.0f/3.0f;
+ private static final float PREDICTION_TIME = (SENSORS_RATE_MS*COUNT/1000.0f)*PREDICTION_RATIO;
+ private float mV[] = new float[COUNT*2];
+ private float mT[] = new float[COUNT*2];
+ private int mIndex;
+
+ public LmsFilter() {
+ mIndex = COUNT;
+ }
+
+ public float filter(long time, float in) {
+ float v = in;
+ final float ns = 1.0f / 1000000000.0f;
+ final float t = time*ns;
+ float v1 = mV[mIndex];
+ if ((v-v1) > 180) {
+ v -= 360;
+ } else if ((v1-v) > 180) {
+ v += 360;
+ }
+ /* Manage the circular buffer, we write the data twice spaced
+ * by COUNT values, so that we don't have to copy the array
+ * when it's full
+ */
+ mIndex++;
+ if (mIndex >= COUNT*2)
+ mIndex = COUNT;
+ mV[mIndex] = v;
+ mT[mIndex] = t;
+ mV[mIndex-COUNT] = v;
+ mT[mIndex-COUNT] = t;
+
+ float A, B, C, D, E;
+ float a, b;
+ int i;
+
+ A = B = C = D = E = 0;
+ for (i=0 ; i<COUNT-1 ; i++) {
+ final int j = mIndex - 1 - i;
+ final float Z = mV[j];
+ final float T = 0.5f*(mT[j] + mT[j+1]) - t;
+ float dT = mT[j] - mT[j+1];
+ dT *= dT;
+ A += Z*dT;
+ B += T*(T*dT);
+ C += (T*dT);
+ D += Z*(T*dT);
+ E += dT;
+ }
+ b = (A*B + C*D) / (E*B + C*C);
+ a = (E*b - A) / C;
+ float f = b + PREDICTION_TIME*a;
+
+ // Normalize
+ f *= (1.0f / 360.0f);
+ if (((f>=0)?f:-f) >= 0.5f)
+ f = f - (float)Math.ceil(f + 0.5f) + 1.0f;
+ if (f < 0)
+ f += 1.0f;
+ f *= 360.0f;
+ return f;
+ }
+ }
+}
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 63fb32d..3c70dc6 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -131,7 +131,6 @@ public class Sensor {
private float mResolution;
private float mPower;
private int mMinDelay;
- private int mLegacyType;
Sensor() {
@@ -203,12 +202,4 @@ public class Sensor {
mMaxRange = max;
mResolution = res;
}
-
- void setLegacyType(int legacyType) {
- mLegacyType = legacyType;
- }
-
- int getLegacyType() {
- return mLegacyType;
- }
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 3fdf246..aeb46cf 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -16,23 +16,12 @@
package android.hardware;
-import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
import android.os.Handler;
-import android.os.Message;
-import android.os.ServiceManager;
import android.util.Log;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-import android.view.IRotationWatcher;
-import android.view.IWindowManager;
-import android.view.Surface;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
/**
@@ -83,11 +72,19 @@ import java.util.List;
* @see Sensor
*
*/
-public class SensorManager
-{
- private static final String TAG = "SensorManager";
+public abstract class SensorManager {
+ /** @hide */
+ protected static final String TAG = "SensorManager";
+
private static final float[] mTempMatrix = new float[16];
+ // Cached lists of sensors by type. Guarded by mSensorListByType.
+ private final SparseArray<List<Sensor>> mSensorListByType =
+ new SparseArray<List<Sensor>>();
+
+ // Legacy sensor manager implementation. Guarded by mSensorListByType during initialization.
+ private LegacySensorManager mLegacySensorManager;
+
/* NOTE: sensor IDs must be a power of 2 */
/**
@@ -353,340 +350,18 @@ public class SensorManager
/** see {@link #remapCoordinateSystem} */
public static final int AXIS_MINUS_Z = AXIS_Z | 0x80;
- /*-----------------------------------------------------------------------*/
-
- Looper mMainLooper;
- @SuppressWarnings("deprecation")
- private HashMap<SensorListener, LegacyListener> mLegacyListenersMap =
- new HashMap<SensorListener, LegacyListener>();
-
- /*-----------------------------------------------------------------------*/
-
- private static final int SENSOR_DISABLE = -1;
- private static boolean sSensorModuleInitialized = false;
- private static ArrayList<Sensor> sFullSensorsList = new ArrayList<Sensor>();
- private static SparseArray<List<Sensor>> sSensorListByType = new SparseArray<List<Sensor>>();
- private static IWindowManager sWindowManager;
- private static int sRotation = Surface.ROTATION_0;
- /* The thread and the sensor list are global to the process
- * but the actual thread is spawned on demand */
- private static SensorThread sSensorThread;
- private static int sQueue;
-
- // Used within this module from outside SensorManager, don't make private
- static SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
- static final ArrayList<ListenerDelegate> sListeners =
- new ArrayList<ListenerDelegate>();
-
- /*-----------------------------------------------------------------------*/
-
- private class SensorEventPool {
- private final int mPoolSize;
- private final SensorEvent mPool[];
- private int mNumItemsInPool;
-
- private SensorEvent createSensorEvent() {
- // maximal size for all legacy events is 3
- return new SensorEvent(3);
- }
-
- SensorEventPool(int poolSize) {
- mPoolSize = poolSize;
- mNumItemsInPool = poolSize;
- mPool = new SensorEvent[poolSize];
- }
-
- SensorEvent getFromPool() {
- SensorEvent t = null;
- synchronized (this) {
- if (mNumItemsInPool > 0) {
- // remove the "top" item from the pool
- final int index = mPoolSize - mNumItemsInPool;
- t = mPool[index];
- mPool[index] = null;
- mNumItemsInPool--;
- }
- }
- if (t == null) {
- // the pool was empty or this item was removed from the pool for
- // the first time. In any case, we need to create a new item.
- t = createSensorEvent();
- }
- return t;
- }
-
- void returnToPool(SensorEvent t) {
- synchronized (this) {
- // is there space left in the pool?
- if (mNumItemsInPool < mPoolSize) {
- // if so, return the item to the pool
- mNumItemsInPool++;
- final int index = mPoolSize - mNumItemsInPool;
- mPool[index] = t;
- }
- }
- }
- }
-
- private static SensorEventPool sPool;
-
- /*-----------------------------------------------------------------------*/
-
- static private class SensorThread {
-
- Thread mThread;
- boolean mSensorsReady;
-
- SensorThread() {
- }
-
- @Override
- protected void finalize() {
- }
-
- // must be called with sListeners lock
- boolean startLocked() {
- try {
- if (mThread == null) {
- mSensorsReady = false;
- SensorThreadRunnable runnable = new SensorThreadRunnable();
- Thread thread = new Thread(runnable, SensorThread.class.getName());
- thread.start();
- synchronized (runnable) {
- while (mSensorsReady == false) {
- runnable.wait();
- }
- }
- mThread = thread;
- }
- } catch (InterruptedException e) {
- }
- return mThread == null ? false : true;
- }
-
- private class SensorThreadRunnable implements Runnable {
- SensorThreadRunnable() {
- }
-
- private boolean open() {
- // NOTE: this cannot synchronize on sListeners, since
- // it's held in the main thread at least until we
- // return from here.
- sQueue = sensors_create_queue();
- return true;
- }
-
- public void run() {
- //Log.d(TAG, "entering main sensor thread");
- final float[] values = new float[3];
- final int[] status = new int[1];
- final long timestamp[] = new long[1];
- Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
-
- if (!open()) {
- return;
- }
-
- synchronized (this) {
- // we've open the driver, we're ready to open the sensors
- mSensorsReady = true;
- this.notify();
- }
-
- while (true) {
- // wait for an event
- final int sensor = sensors_data_poll(sQueue, values, status, timestamp);
-
- int accuracy = status[0];
- synchronized (sListeners) {
- if (sensor == -1 || sListeners.isEmpty()) {
- // we lost the connection to the event stream. this happens
- // when the last listener is removed or if there is an error
- if (sensor == -1 && !sListeners.isEmpty()) {
- // log a warning in case of abnormal termination
- Log.e(TAG, "_sensors_data_poll() failed, we bail out: sensors=" + sensor);
- }
- // we have no more listeners or polling failed, terminate the thread
- sensors_destroy_queue(sQueue);
- sQueue = 0;
- mThread = null;
- break;
- }
- final Sensor sensorObject = sHandleToSensor.get(sensor);
- if (sensorObject != null) {
- // report the sensor event to all listeners that
- // care about it.
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate listener = sListeners.get(i);
- if (listener.hasSensor(sensorObject)) {
- // this is asynchronous (okay to call
- // with sListeners lock held).
- listener.onSensorChangedLocked(sensorObject,
- values, timestamp, accuracy);
- }
- }
- }
- }
- }
- //Log.d(TAG, "exiting main sensor thread");
- }
- }
- }
-
- /*-----------------------------------------------------------------------*/
-
- private class ListenerDelegate {
- private final SensorEventListener mSensorEventListener;
- private final ArrayList<Sensor> mSensorList = new ArrayList<Sensor>();
- private final Handler mHandler;
- public SparseBooleanArray mSensors = new SparseBooleanArray();
- public SparseBooleanArray mFirstEvent = new SparseBooleanArray();
- public SparseIntArray mSensorAccuracies = new SparseIntArray();
-
- ListenerDelegate(SensorEventListener listener, Sensor sensor, Handler handler) {
- mSensorEventListener = listener;
- Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
- // currently we create one Handler instance per listener, but we could
- // have one per looper (we'd need to pass the ListenerDelegate
- // instance to handleMessage and keep track of them separately).
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- final SensorEvent t = (SensorEvent)msg.obj;
- final int handle = t.sensor.getHandle();
-
- switch (t.sensor.getType()) {
- // Only report accuracy for sensors that support it.
- case Sensor.TYPE_MAGNETIC_FIELD:
- case Sensor.TYPE_ORIENTATION:
- // call onAccuracyChanged() only if the value changes
- final int accuracy = mSensorAccuracies.get(handle);
- if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
- mSensorAccuracies.put(handle, t.accuracy);
- mSensorEventListener.onAccuracyChanged(t.sensor, t.accuracy);
- }
- break;
- default:
- // For other sensors, just report the accuracy once
- if (mFirstEvent.get(handle) == false) {
- mFirstEvent.put(handle, true);
- mSensorEventListener.onAccuracyChanged(
- t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
- }
- break;
- }
-
- mSensorEventListener.onSensorChanged(t);
- sPool.returnToPool(t);
- }
- };
- addSensor(sensor);
- }
-
- Object getListener() {
- return mSensorEventListener;
- }
-
- void addSensor(Sensor sensor) {
- mSensors.put(sensor.getHandle(), true);
- mSensorList.add(sensor);
- }
- int removeSensor(Sensor sensor) {
- mSensors.delete(sensor.getHandle());
- mSensorList.remove(sensor);
- return mSensors.size();
- }
- boolean hasSensor(Sensor sensor) {
- return mSensors.get(sensor.getHandle());
- }
- List<Sensor> getSensors() {
- return mSensorList;
- }
-
- void onSensorChangedLocked(Sensor sensor, float[] values, long[] timestamp, int accuracy) {
- SensorEvent t = sPool.getFromPool();
- final float[] v = t.values;
- v[0] = values[0];
- v[1] = values[1];
- v[2] = values[2];
- t.timestamp = timestamp[0];
- t.accuracy = accuracy;
- t.sensor = sensor;
- Message msg = Message.obtain();
- msg.what = 0;
- msg.obj = t;
- msg.setAsynchronous(true);
- mHandler.sendMessage(msg);
- }
- }
/**
* {@hide}
*/
- public SensorManager(Looper mainLooper) {
- mMainLooper = mainLooper;
-
-
- synchronized(sListeners) {
- if (!sSensorModuleInitialized) {
- sSensorModuleInitialized = true;
-
- nativeClassInit();
-
- sWindowManager = IWindowManager.Stub.asInterface(
- ServiceManager.getService("window"));
- if (sWindowManager != null) {
- // if it's null we're running in the system process
- // which won't get the rotated values
- try {
- sRotation = sWindowManager.watchRotation(
- new IRotationWatcher.Stub() {
- public void onRotationChanged(int rotation) {
- SensorManager.this.onRotationChanged(rotation);
- }
- }
- );
- } catch (RemoteException e) {
- }
- }
-
- // initialize the sensor list
- sensors_module_init();
- final ArrayList<Sensor> fullList = sFullSensorsList;
- int i = 0;
- do {
- Sensor sensor = new Sensor();
- i = sensors_module_get_next_sensor(sensor, i);
-
- if (i>=0) {
- //Log.d(TAG, "found sensor: " + sensor.getName() +
- // ", handle=" + sensor.getHandle());
- sensor.setLegacyType(getLegacySensorType(sensor.getType()));
- fullList.add(sensor);
- sHandleToSensor.append(sensor.getHandle(), sensor);
- }
- } while (i>0);
-
- sPool = new SensorEventPool( sFullSensorsList.size()*2 );
- sSensorThread = new SensorThread();
- }
- }
+ public SensorManager() {
}
- private int getLegacySensorType(int type) {
- switch (type) {
- case Sensor.TYPE_ACCELEROMETER:
- return SENSOR_ACCELEROMETER;
- case Sensor.TYPE_MAGNETIC_FIELD:
- return SENSOR_MAGNETIC_FIELD;
- case Sensor.TYPE_ORIENTATION:
- return SENSOR_ORIENTATION_RAW;
- case Sensor.TYPE_TEMPERATURE:
- return SENSOR_TEMPERATURE;
- }
- return 0;
- }
+ /**
+ * Gets the full list of sensors that are available.
+ * @hide
+ */
+ protected abstract List<Sensor> getFullSensorList();
/**
* @return available sensors.
@@ -695,23 +370,7 @@ public class SensorManager
*/
@Deprecated
public int getSensors() {
- int result = 0;
- final ArrayList<Sensor> fullList = sFullSensorsList;
- for (Sensor i : fullList) {
- switch (i.getType()) {
- case Sensor.TYPE_ACCELEROMETER:
- result |= SensorManager.SENSOR_ACCELEROMETER;
- break;
- case Sensor.TYPE_MAGNETIC_FIELD:
- result |= SensorManager.SENSOR_MAGNETIC_FIELD;
- break;
- case Sensor.TYPE_ORIENTATION:
- result |= SensorManager.SENSOR_ORIENTATION |
- SensorManager.SENSOR_ORIENTATION_RAW;
- break;
- }
- }
- return result;
+ return getLegacySensorManager().getSensors();
}
/**
@@ -731,9 +390,9 @@ public class SensorManager
public List<Sensor> getSensorList(int type) {
// cache the returned lists the first time
List<Sensor> list;
- final ArrayList<Sensor> fullList = sFullSensorsList;
- synchronized(fullList) {
- list = sSensorListByType.get(type);
+ final List<Sensor> fullList = getFullSensorList();
+ synchronized (mSensorListByType) {
+ list = mSensorListByType.get(type);
if (list == null) {
if (type == Sensor.TYPE_ALL) {
list = fullList;
@@ -745,7 +404,7 @@ public class SensorManager
}
}
list = Collections.unmodifiableList(list);
- sSensorListByType.append(type, list);
+ mSensorListByType.append(type, list);
}
}
return list;
@@ -817,143 +476,40 @@ public class SensorManager
*/
@Deprecated
public boolean registerListener(SensorListener listener, int sensors, int rate) {
- if (listener == null) {
- return false;
- }
- boolean result = false;
- result = registerLegacyListener(SENSOR_ACCELEROMETER, Sensor.TYPE_ACCELEROMETER,
- listener, sensors, rate) || result;
- result = registerLegacyListener(SENSOR_MAGNETIC_FIELD, Sensor.TYPE_MAGNETIC_FIELD,
- listener, sensors, rate) || result;
- result = registerLegacyListener(SENSOR_ORIENTATION_RAW, Sensor.TYPE_ORIENTATION,
- listener, sensors, rate) || result;
- result = registerLegacyListener(SENSOR_ORIENTATION, Sensor.TYPE_ORIENTATION,
- listener, sensors, rate) || result;
- result = registerLegacyListener(SENSOR_TEMPERATURE, Sensor.TYPE_TEMPERATURE,
- listener, sensors, rate) || result;
- return result;
- }
-
- @SuppressWarnings("deprecation")
- private boolean registerLegacyListener(int legacyType, int type,
- SensorListener listener, int sensors, int rate)
- {
- if (listener == null) {
- return false;
- }
- boolean result = false;
- // Are we activating this legacy sensor?
- if ((sensors & legacyType) != 0) {
- // if so, find a suitable Sensor
- Sensor sensor = getDefaultSensor(type);
- if (sensor != null) {
- // If we don't already have one, create a LegacyListener
- // to wrap this listener and process the events as
- // they are expected by legacy apps.
- LegacyListener legacyListener = null;
- synchronized (mLegacyListenersMap) {
- legacyListener = mLegacyListenersMap.get(listener);
- if (legacyListener == null) {
- // we didn't find a LegacyListener for this client,
- // create one, and put it in our list.
- legacyListener = new LegacyListener(listener);
- mLegacyListenersMap.put(listener, legacyListener);
- }
- }
- // register this legacy sensor with this legacy listener
- legacyListener.registerSensor(legacyType);
- // and finally, register the legacy listener with the new apis
- result = registerListener(legacyListener, sensor, rate);
- }
- }
- return result;
+ return getLegacySensorManager().registerListener(listener, sensors, rate);
}
/**
- * Unregisters a listener for the sensors with which it is registered.
+ * Unregisters a listener for all sensors.
*
* @deprecated This method is deprecated, use
- * {@link SensorManager#unregisterListener(SensorEventListener, Sensor)}
+ * {@link SensorManager#unregisterListener(SensorEventListener)}
* instead.
*
* @param listener
* a SensorListener object
- *
- * @param sensors
- * a bit masks of the sensors to unregister from
*/
@Deprecated
- public void unregisterListener(SensorListener listener, int sensors) {
- unregisterLegacyListener(SENSOR_ACCELEROMETER, Sensor.TYPE_ACCELEROMETER,
- listener, sensors);
- unregisterLegacyListener(SENSOR_MAGNETIC_FIELD, Sensor.TYPE_MAGNETIC_FIELD,
- listener, sensors);
- unregisterLegacyListener(SENSOR_ORIENTATION_RAW, Sensor.TYPE_ORIENTATION,
- listener, sensors);
- unregisterLegacyListener(SENSOR_ORIENTATION, Sensor.TYPE_ORIENTATION,
- listener, sensors);
- unregisterLegacyListener(SENSOR_TEMPERATURE, Sensor.TYPE_TEMPERATURE,
- listener, sensors);
- }
-
- @SuppressWarnings("deprecation")
- private void unregisterLegacyListener(int legacyType, int type,
- SensorListener listener, int sensors)
- {
- if (listener == null) {
- return;
- }
- // do we know about this listener?
- LegacyListener legacyListener = null;
- synchronized (mLegacyListenersMap) {
- legacyListener = mLegacyListenersMap.get(listener);
- }
- if (legacyListener != null) {
- // Are we deactivating this legacy sensor?
- if ((sensors & legacyType) != 0) {
- // if so, find the corresponding Sensor
- Sensor sensor = getDefaultSensor(type);
- if (sensor != null) {
- // unregister this legacy sensor and if we don't
- // need the corresponding Sensor, unregister it too
- if (legacyListener.unregisterSensor(legacyType)) {
- // corresponding sensor not needed, unregister
- unregisterListener(legacyListener, sensor);
- // finally check if we still need the legacyListener
- // in our mapping, if not, get rid of it too.
- synchronized(sListeners) {
- boolean found = false;
- for (ListenerDelegate i : sListeners) {
- if (i.getListener() == legacyListener) {
- found = true;
- break;
- }
- }
- if (!found) {
- synchronized (mLegacyListenersMap) {
- mLegacyListenersMap.remove(listener);
- }
- }
- }
- }
- }
- }
- }
+ public void unregisterListener(SensorListener listener) {
+ unregisterListener(listener, SENSOR_ALL | SENSOR_ORIENTATION_RAW);
}
/**
- * Unregisters a listener for all sensors.
+ * Unregisters a listener for the sensors with which it is registered.
*
* @deprecated This method is deprecated, use
- * {@link SensorManager#unregisterListener(SensorEventListener)}
+ * {@link SensorManager#unregisterListener(SensorEventListener, Sensor)}
* instead.
*
* @param listener
* a SensorListener object
+ *
+ * @param sensors
+ * a bit masks of the sensors to unregister from
*/
@Deprecated
- public void unregisterListener(SensorListener listener) {
- unregisterListener(listener, SENSOR_ALL | SENSOR_ORIENTATION_RAW);
+ public void unregisterListener(SensorListener listener, int sensors) {
+ getLegacySensorManager().unregisterListener(listener, sensors);
}
/**
@@ -970,7 +526,11 @@ public class SensorManager
*
*/
public void unregisterListener(SensorEventListener listener, Sensor sensor) {
- unregisterListener((Object)listener, sensor);
+ if (listener == null || sensor == null) {
+ return;
+ }
+
+ unregisterListenerImpl(listener, sensor);
}
/**
@@ -984,9 +544,16 @@ public class SensorManager
*
*/
public void unregisterListener(SensorEventListener listener) {
- unregisterListener((Object)listener);
+ if (listener == null) {
+ return;
+ }
+
+ unregisterListenerImpl(listener, null);
}
+ /** @hide */
+ protected abstract void unregisterListenerImpl(SensorEventListener listener, Sensor sensor);
+
/**
* Registers a {@link android.hardware.SensorEventListener
* SensorEventListener} for the given sensor.
@@ -1019,31 +586,6 @@ public class SensorManager
return registerListener(listener, sensor, rate, null);
}
- private boolean enableSensorLocked(Sensor sensor, int delay) {
- boolean result = false;
- for (ListenerDelegate i : sListeners) {
- if (i.hasSensor(sensor)) {
- String name = sensor.getName();
- int handle = sensor.getHandle();
- result = sensors_enable_sensor(sQueue, name, handle, delay);
- break;
- }
- }
- return result;
- }
-
- private boolean disableSensorLocked(Sensor sensor) {
- for (ListenerDelegate i : sListeners) {
- if (i.hasSensor(sensor)) {
- // not an error, it's just that this sensor is still in use
- return true;
- }
- }
- String name = sensor.getName();
- int handle = sensor.getHandle();
- return sensors_enable_sensor(sQueue, name, handle, SENSOR_DISABLE);
- }
-
/**
* Registers a {@link android.hardware.SensorEventListener
* SensorEventListener} for the given sensor.
@@ -1081,7 +623,7 @@ public class SensorManager
if (listener == null || sensor == null) {
return false;
}
- boolean result = true;
+
int delay = -1;
switch (rate) {
case SENSOR_DELAY_FASTEST:
@@ -1101,92 +643,12 @@ public class SensorManager
break;
}
- synchronized (sListeners) {
- // look for this listener in our list
- ListenerDelegate l = null;
- for (ListenerDelegate i : sListeners) {
- if (i.getListener() == listener) {
- l = i;
- break;
- }
- }
-
- // if we don't find it, add it to the list
- if (l == null) {
- l = new ListenerDelegate(listener, sensor, handler);
- sListeners.add(l);
- // if the list is not empty, start our main thread
- if (!sListeners.isEmpty()) {
- if (sSensorThread.startLocked()) {
- if (!enableSensorLocked(sensor, delay)) {
- // oops. there was an error
- sListeners.remove(l);
- result = false;
- }
- } else {
- // there was an error, remove the listener
- sListeners.remove(l);
- result = false;
- }
- } else {
- // weird, we couldn't add the listener
- result = false;
- }
- } else {
- l.addSensor(sensor);
- if (!enableSensorLocked(sensor, delay)) {
- // oops. there was an error
- l.removeSensor(sensor);
- result = false;
- }
- }
- }
-
- return result;
- }
-
- private void unregisterListener(Object listener, Sensor sensor) {
- if (listener == null || sensor == null) {
- return;
- }
-
- synchronized (sListeners) {
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate l = sListeners.get(i);
- if (l.getListener() == listener) {
- if (l.removeSensor(sensor) == 0) {
- // if we have no more sensors enabled on this listener,
- // take it off the list.
- sListeners.remove(i);
- }
- break;
- }
- }
- disableSensorLocked(sensor);
- }
+ return registerListenerImpl(listener, sensor, delay, handler);
}
- private void unregisterListener(Object listener) {
- if (listener == null) {
- return;
- }
-
- synchronized (sListeners) {
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate l = sListeners.get(i);
- if (l.getListener() == listener) {
- sListeners.remove(i);
- // disable all sensors for this listener
- for (Sensor sensor : l.getSensors()) {
- disableSensorLocked(sensor);
- }
- break;
- }
- }
- }
- }
+ /** @hide */
+ protected abstract boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
+ int delay, Handler handler);
/**
* <p>
@@ -1653,228 +1115,11 @@ public class SensorManager
* @param p atmospheric pressure
* @return Altitude in meters
*/
- public static float getAltitude(float p0, float p) {
+ public static float getAltitude(float p0, float p) {
final float coef = 1.0f / 5.255f;
return 44330.0f * (1.0f - (float)Math.pow(p/p0, coef));
}
-
- /**
- * {@hide}
- */
- public void onRotationChanged(int rotation) {
- synchronized(sListeners) {
- sRotation = rotation;
- }
- }
-
- static int getRotation() {
- synchronized(sListeners) {
- return sRotation;
- }
- }
-
- private class LegacyListener implements SensorEventListener {
- private float mValues[] = new float[6];
- @SuppressWarnings("deprecation")
- private SensorListener mTarget;
- private int mSensors;
- private final LmsFilter mYawfilter = new LmsFilter();
-
- @SuppressWarnings("deprecation")
- LegacyListener(SensorListener target) {
- mTarget = target;
- mSensors = 0;
- }
-
- void registerSensor(int legacyType) {
- mSensors |= legacyType;
- }
-
- boolean unregisterSensor(int legacyType) {
- mSensors &= ~legacyType;
- int mask = SENSOR_ORIENTATION|SENSOR_ORIENTATION_RAW;
- if (((legacyType&mask)!=0) && ((mSensors&mask)!=0)) {
- return false;
- }
- return true;
- }
-
- @SuppressWarnings("deprecation")
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- try {
- mTarget.onAccuracyChanged(sensor.getLegacyType(), accuracy);
- } catch (AbstractMethodError e) {
- // old app that doesn't implement this method
- // just ignore it.
- }
- }
-
- @SuppressWarnings("deprecation")
- public void onSensorChanged(SensorEvent event) {
- final float v[] = mValues;
- v[0] = event.values[0];
- v[1] = event.values[1];
- v[2] = event.values[2];
- int legacyType = event.sensor.getLegacyType();
- mapSensorDataToWindow(legacyType, v, SensorManager.getRotation());
- if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
- if ((mSensors & SENSOR_ORIENTATION_RAW)!=0) {
- mTarget.onSensorChanged(SENSOR_ORIENTATION_RAW, v);
- }
- if ((mSensors & SENSOR_ORIENTATION)!=0) {
- v[0] = mYawfilter.filter(event.timestamp, v[0]);
- mTarget.onSensorChanged(SENSOR_ORIENTATION, v);
- }
- } else {
- mTarget.onSensorChanged(legacyType, v);
- }
- }
-
- /*
- * Helper function to convert the specified sensor's data to the windows's
- * coordinate space from the device's coordinate space.
- *
- * output: 3,4,5: values in the old API format
- * 0,1,2: transformed values in the old API format
- *
- */
- private void mapSensorDataToWindow(int sensor,
- float[] values, int orientation) {
- float x = values[0];
- float y = values[1];
- float z = values[2];
-
- switch (sensor) {
- case SensorManager.SENSOR_ORIENTATION:
- case SensorManager.SENSOR_ORIENTATION_RAW:
- z = -z;
- break;
- case SensorManager.SENSOR_ACCELEROMETER:
- x = -x;
- y = -y;
- z = -z;
- break;
- case SensorManager.SENSOR_MAGNETIC_FIELD:
- x = -x;
- y = -y;
- break;
- }
- values[0] = x;
- values[1] = y;
- values[2] = z;
- values[3] = x;
- values[4] = y;
- values[5] = z;
-
- if ((orientation & Surface.ROTATION_90) != 0) {
- // handles 90 and 270 rotation
- switch (sensor) {
- case SENSOR_ACCELEROMETER:
- case SENSOR_MAGNETIC_FIELD:
- values[0] =-y;
- values[1] = x;
- values[2] = z;
- break;
- case SENSOR_ORIENTATION:
- case SENSOR_ORIENTATION_RAW:
- values[0] = x + ((x < 270) ? 90 : -270);
- values[1] = z;
- values[2] = y;
- break;
- }
- }
- if ((orientation & Surface.ROTATION_180) != 0) {
- x = values[0];
- y = values[1];
- z = values[2];
- // handles 180 (flip) and 270 (flip + 90) rotation
- switch (sensor) {
- case SENSOR_ACCELEROMETER:
- case SENSOR_MAGNETIC_FIELD:
- values[0] =-x;
- values[1] =-y;
- values[2] = z;
- break;
- case SENSOR_ORIENTATION:
- case SENSOR_ORIENTATION_RAW:
- values[0] = (x >= 180) ? (x - 180) : (x + 180);
- values[1] =-y;
- values[2] =-z;
- break;
- }
- }
- }
- }
-
- class LmsFilter {
- private static final int SENSORS_RATE_MS = 20;
- private static final int COUNT = 12;
- private static final float PREDICTION_RATIO = 1.0f/3.0f;
- private static final float PREDICTION_TIME = (SENSORS_RATE_MS*COUNT/1000.0f)*PREDICTION_RATIO;
- private float mV[] = new float[COUNT*2];
- private float mT[] = new float[COUNT*2];
- private int mIndex;
-
- public LmsFilter() {
- mIndex = COUNT;
- }
-
- public float filter(long time, float in) {
- float v = in;
- final float ns = 1.0f / 1000000000.0f;
- final float t = time*ns;
- float v1 = mV[mIndex];
- if ((v-v1) > 180) {
- v -= 360;
- } else if ((v1-v) > 180) {
- v += 360;
- }
- /* Manage the circular buffer, we write the data twice spaced
- * by COUNT values, so that we don't have to copy the array
- * when it's full
- */
- mIndex++;
- if (mIndex >= COUNT*2)
- mIndex = COUNT;
- mV[mIndex] = v;
- mT[mIndex] = t;
- mV[mIndex-COUNT] = v;
- mT[mIndex-COUNT] = t;
-
- float A, B, C, D, E;
- float a, b;
- int i;
-
- A = B = C = D = E = 0;
- for (i=0 ; i<COUNT-1 ; i++) {
- final int j = mIndex - 1 - i;
- final float Z = mV[j];
- final float T = 0.5f*(mT[j] + mT[j+1]) - t;
- float dT = mT[j] - mT[j+1];
- dT *= dT;
- A += Z*dT;
- B += T*(T*dT);
- C += (T*dT);
- D += Z*(T*dT);
- E += dT;
- }
- b = (A*B + C*D) / (E*B + C*C);
- a = (E*b - A) / C;
- float f = b + PREDICTION_TIME*a;
-
- // Normalize
- f *= (1.0f / 360.0f);
- if (((f>=0)?f:-f) >= 0.5f)
- f = f - (float)Math.ceil(f + 0.5f) + 1.0f;
- if (f < 0)
- f += 1.0f;
- f *= 360.0f;
- return f;
- }
- }
-
-
/** Helper function to compute the angle change between two rotation matrices.
* Given a current rotation matrix (R) and a previous rotation matrix
* (prevR) computes the rotation around the x,y, and z axes which
@@ -2060,14 +1305,66 @@ public class SensorManager
Q[3] = rv[2];
}
- private static native void nativeClassInit();
+ private LegacySensorManager getLegacySensorManager() {
+ synchronized (mSensorListByType) {
+ if (mLegacySensorManager == null) {
+ Log.i(TAG, "This application is using deprecated SensorManager API which will "
+ + "be removed someday. Please consider switching to the new API.");
+ mLegacySensorManager = new LegacySensorManager(this);
+ }
+ return mLegacySensorManager;
+ }
+ }
+
+ /**
+ * Sensor event pool implementation.
+ * @hide
+ */
+ protected static final class SensorEventPool {
+ private final int mPoolSize;
+ private final SensorEvent mPool[];
+ private int mNumItemsInPool;
+
+ private SensorEvent createSensorEvent() {
+ // maximal size for all legacy events is 3
+ return new SensorEvent(3);
+ }
+
+ SensorEventPool(int poolSize) {
+ mPoolSize = poolSize;
+ mNumItemsInPool = poolSize;
+ mPool = new SensorEvent[poolSize];
+ }
- private static native int sensors_module_init();
- private static native int sensors_module_get_next_sensor(Sensor sensor, int next);
+ SensorEvent getFromPool() {
+ SensorEvent t = null;
+ synchronized (this) {
+ if (mNumItemsInPool > 0) {
+ // remove the "top" item from the pool
+ final int index = mPoolSize - mNumItemsInPool;
+ t = mPool[index];
+ mPool[index] = null;
+ mNumItemsInPool--;
+ }
+ }
+ if (t == null) {
+ // the pool was empty or this item was removed from the pool for
+ // the first time. In any case, we need to create a new item.
+ t = createSensorEvent();
+ }
+ return t;
+ }
- // Used within this module from outside SensorManager, don't make private
- static native int sensors_create_queue();
- static native void sensors_destroy_queue(int queue);
- static native boolean sensors_enable_sensor(int queue, String name, int sensor, int enable);
- static native int sensors_data_poll(int queue, float[] values, int[] status, long[] timestamp);
+ void returnToPool(SensorEvent t) {
+ synchronized (this) {
+ // is there space left in the pool?
+ if (mNumItemsInPool < mPoolSize) {
+ // if so, return the item to the pool
+ mNumItemsInPool++;
+ final int index = mPoolSize - mNumItemsInPool;
+ mPool[index] = t;
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
new file mode 100644
index 0000000..0204e94
--- /dev/null
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2012 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.hardware;
+
+import android.os.Looper;
+import android.os.Process;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Sensor manager implementation that communicates with the built-in
+ * system sensors.
+ *
+ * @hide
+ */
+public class SystemSensorManager extends SensorManager {
+ private static final int SENSOR_DISABLE = -1;
+ private static boolean sSensorModuleInitialized = false;
+ private static ArrayList<Sensor> sFullSensorsList = new ArrayList<Sensor>();
+ /* The thread and the sensor list are global to the process
+ * but the actual thread is spawned on demand */
+ private static SensorThread sSensorThread;
+ private static int sQueue;
+
+ // Used within this module from outside SensorManager, don't make private
+ static SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
+ static final ArrayList<ListenerDelegate> sListeners =
+ new ArrayList<ListenerDelegate>();
+
+ // Common pool of sensor events.
+ static SensorEventPool sPool;
+
+ // Looper associated with the context in which this instance was created.
+ final Looper mMainLooper;
+
+ /*-----------------------------------------------------------------------*/
+
+ static private class SensorThread {
+
+ Thread mThread;
+ boolean mSensorsReady;
+
+ SensorThread() {
+ }
+
+ @Override
+ protected void finalize() {
+ }
+
+ // must be called with sListeners lock
+ boolean startLocked() {
+ try {
+ if (mThread == null) {
+ mSensorsReady = false;
+ SensorThreadRunnable runnable = new SensorThreadRunnable();
+ Thread thread = new Thread(runnable, SensorThread.class.getName());
+ thread.start();
+ synchronized (runnable) {
+ while (mSensorsReady == false) {
+ runnable.wait();
+ }
+ }
+ mThread = thread;
+ }
+ } catch (InterruptedException e) {
+ }
+ return mThread == null ? false : true;
+ }
+
+ private class SensorThreadRunnable implements Runnable {
+ SensorThreadRunnable() {
+ }
+
+ private boolean open() {
+ // NOTE: this cannot synchronize on sListeners, since
+ // it's held in the main thread at least until we
+ // return from here.
+ sQueue = sensors_create_queue();
+ return true;
+ }
+
+ public void run() {
+ //Log.d(TAG, "entering main sensor thread");
+ final float[] values = new float[3];
+ final int[] status = new int[1];
+ final long timestamp[] = new long[1];
+ Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
+
+ if (!open()) {
+ return;
+ }
+
+ synchronized (this) {
+ // we've open the driver, we're ready to open the sensors
+ mSensorsReady = true;
+ this.notify();
+ }
+
+ while (true) {
+ // wait for an event
+ final int sensor = sensors_data_poll(sQueue, values, status, timestamp);
+
+ int accuracy = status[0];
+ synchronized (sListeners) {
+ if (sensor == -1 || sListeners.isEmpty()) {
+ // we lost the connection to the event stream. this happens
+ // when the last listener is removed or if there is an error
+ if (sensor == -1 && !sListeners.isEmpty()) {
+ // log a warning in case of abnormal termination
+ Log.e(TAG, "_sensors_data_poll() failed, we bail out: sensors=" + sensor);
+ }
+ // we have no more listeners or polling failed, terminate the thread
+ sensors_destroy_queue(sQueue);
+ sQueue = 0;
+ mThread = null;
+ break;
+ }
+ final Sensor sensorObject = sHandleToSensor.get(sensor);
+ if (sensorObject != null) {
+ // report the sensor event to all listeners that
+ // care about it.
+ final int size = sListeners.size();
+ for (int i=0 ; i<size ; i++) {
+ ListenerDelegate listener = sListeners.get(i);
+ if (listener.hasSensor(sensorObject)) {
+ // this is asynchronous (okay to call
+ // with sListeners lock held).
+ listener.onSensorChangedLocked(sensorObject,
+ values, timestamp, accuracy);
+ }
+ }
+ }
+ }
+ }
+ //Log.d(TAG, "exiting main sensor thread");
+ }
+ }
+ }
+
+ /*-----------------------------------------------------------------------*/
+
+ private class ListenerDelegate {
+ private final SensorEventListener mSensorEventListener;
+ private final ArrayList<Sensor> mSensorList = new ArrayList<Sensor>();
+ private final Handler mHandler;
+ public SparseBooleanArray mSensors = new SparseBooleanArray();
+ public SparseBooleanArray mFirstEvent = new SparseBooleanArray();
+ public SparseIntArray mSensorAccuracies = new SparseIntArray();
+
+ ListenerDelegate(SensorEventListener listener, Sensor sensor, Handler handler) {
+ mSensorEventListener = listener;
+ Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
+ // currently we create one Handler instance per listener, but we could
+ // have one per looper (we'd need to pass the ListenerDelegate
+ // instance to handleMessage and keep track of them separately).
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ final SensorEvent t = (SensorEvent)msg.obj;
+ final int handle = t.sensor.getHandle();
+
+ switch (t.sensor.getType()) {
+ // Only report accuracy for sensors that support it.
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ case Sensor.TYPE_ORIENTATION:
+ // call onAccuracyChanged() only if the value changes
+ final int accuracy = mSensorAccuracies.get(handle);
+ if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
+ mSensorAccuracies.put(handle, t.accuracy);
+ mSensorEventListener.onAccuracyChanged(t.sensor, t.accuracy);
+ }
+ break;
+ default:
+ // For other sensors, just report the accuracy once
+ if (mFirstEvent.get(handle) == false) {
+ mFirstEvent.put(handle, true);
+ mSensorEventListener.onAccuracyChanged(
+ t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
+ }
+ break;
+ }
+
+ mSensorEventListener.onSensorChanged(t);
+ sPool.returnToPool(t);
+ }
+ };
+ addSensor(sensor);
+ }
+
+ Object getListener() {
+ return mSensorEventListener;
+ }
+
+ void addSensor(Sensor sensor) {
+ mSensors.put(sensor.getHandle(), true);
+ mSensorList.add(sensor);
+ }
+ int removeSensor(Sensor sensor) {
+ mSensors.delete(sensor.getHandle());
+ mSensorList.remove(sensor);
+ return mSensors.size();
+ }
+ boolean hasSensor(Sensor sensor) {
+ return mSensors.get(sensor.getHandle());
+ }
+ List<Sensor> getSensors() {
+ return mSensorList;
+ }
+
+ void onSensorChangedLocked(Sensor sensor, float[] values, long[] timestamp, int accuracy) {
+ SensorEvent t = sPool.getFromPool();
+ final float[] v = t.values;
+ v[0] = values[0];
+ v[1] = values[1];
+ v[2] = values[2];
+ t.timestamp = timestamp[0];
+ t.accuracy = accuracy;
+ t.sensor = sensor;
+ Message msg = Message.obtain();
+ msg.what = 0;
+ msg.obj = t;
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public SystemSensorManager(Looper mainLooper) {
+ mMainLooper = mainLooper;
+
+ synchronized(sListeners) {
+ if (!sSensorModuleInitialized) {
+ sSensorModuleInitialized = true;
+
+ nativeClassInit();
+
+ // initialize the sensor list
+ sensors_module_init();
+ final ArrayList<Sensor> fullList = sFullSensorsList;
+ int i = 0;
+ do {
+ Sensor sensor = new Sensor();
+ i = sensors_module_get_next_sensor(sensor, i);
+
+ if (i>=0) {
+ //Log.d(TAG, "found sensor: " + sensor.getName() +
+ // ", handle=" + sensor.getHandle());
+ fullList.add(sensor);
+ sHandleToSensor.append(sensor.getHandle(), sensor);
+ }
+ } while (i>0);
+
+ sPool = new SensorEventPool( sFullSensorsList.size()*2 );
+ sSensorThread = new SensorThread();
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ protected List<Sensor> getFullSensorList() {
+ return sFullSensorsList;
+ }
+
+ private boolean enableSensorLocked(Sensor sensor, int delay) {
+ boolean result = false;
+ for (ListenerDelegate i : sListeners) {
+ if (i.hasSensor(sensor)) {
+ String name = sensor.getName();
+ int handle = sensor.getHandle();
+ result = sensors_enable_sensor(sQueue, name, handle, delay);
+ break;
+ }
+ }
+ return result;
+ }
+
+ private boolean disableSensorLocked(Sensor sensor) {
+ for (ListenerDelegate i : sListeners) {
+ if (i.hasSensor(sensor)) {
+ // not an error, it's just that this sensor is still in use
+ return true;
+ }
+ }
+ String name = sensor.getName();
+ int handle = sensor.getHandle();
+ return sensors_enable_sensor(sQueue, name, handle, SENSOR_DISABLE);
+ }
+
+ /** @hide */
+ @Override
+ protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
+ int delay, Handler handler) {
+ boolean result = true;
+ synchronized (sListeners) {
+ // look for this listener in our list
+ ListenerDelegate l = null;
+ for (ListenerDelegate i : sListeners) {
+ if (i.getListener() == listener) {
+ l = i;
+ break;
+ }
+ }
+
+ // if we don't find it, add it to the list
+ if (l == null) {
+ l = new ListenerDelegate(listener, sensor, handler);
+ sListeners.add(l);
+ // if the list is not empty, start our main thread
+ if (!sListeners.isEmpty()) {
+ if (sSensorThread.startLocked()) {
+ if (!enableSensorLocked(sensor, delay)) {
+ // oops. there was an error
+ sListeners.remove(l);
+ result = false;
+ }
+ } else {
+ // there was an error, remove the listener
+ sListeners.remove(l);
+ result = false;
+ }
+ } else {
+ // weird, we couldn't add the listener
+ result = false;
+ }
+ } else if (!l.hasSensor(sensor)) {
+ l.addSensor(sensor);
+ if (!enableSensorLocked(sensor, delay)) {
+ // oops. there was an error
+ l.removeSensor(sensor);
+ result = false;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /** @hide */
+ @Override
+ protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
+ synchronized (sListeners) {
+ final int size = sListeners.size();
+ for (int i=0 ; i<size ; i++) {
+ ListenerDelegate l = sListeners.get(i);
+ if (l.getListener() == listener) {
+ if (sensor == null) {
+ sListeners.remove(i);
+ // disable all sensors for this listener
+ for (Sensor s : l.getSensors()) {
+ disableSensorLocked(s);
+ }
+ } else if (l.removeSensor(sensor) == 0) {
+ // if we have no more sensors enabled on this listener,
+ // take it off the list.
+ sListeners.remove(i);
+ disableSensorLocked(sensor);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ private static native void nativeClassInit();
+
+ private static native int sensors_module_init();
+ private static native int sensors_module_get_next_sensor(Sensor sensor, int next);
+
+ // Used within this module from outside SensorManager, don't make private
+ static native int sensors_create_queue();
+ static native void sensors_destroy_queue(int queue);
+ static native boolean sensors_enable_sensor(int queue, String name, int sensor, int enable);
+ static native int sensors_data_poll(int queue, float[] values, int[] status, long[] timestamp);
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index de16985..ef4209f 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -373,10 +373,11 @@ public class ConnectivityManager {
}
/**
- * Gets you info about the current data network.
- * Call {@link NetworkInfo#isConnected()} on the returned {@link NetworkInfo}
- * to check if the device has a data connection.
- */
+ * Returns details about the currently active data network. When connected,
+ * this network is the default route for outgoing connections. You should
+ * always check {@link NetworkInfo#isConnected()} before initiating network
+ * traffic. This may return {@code null} when no networks are available.
+ */
public NetworkInfo getActiveNetworkInfo() {
try {
return mService.getActiveNetworkInfo();
@@ -856,4 +857,19 @@ public class ConnectivityManager {
} catch (RemoteException e) {}
return false;
}
+
+ /**
+ * Returns if the currently active data network is metered. A network is
+ * classified as metered when the user is sensitive to heavy data usage on
+ * that connection. You should check this before doing large data transfers,
+ * and warn the user or delay the operation until another network is
+ * available.
+ */
+ public boolean isActiveNetworkMetered() {
+ try {
+ return mService.isActiveNetworkMetered();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 7046008..92aeff2 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -51,6 +51,7 @@ interface IConnectivityManager
NetworkState[] getAllNetworkState();
NetworkQuotaInfo getActiveNetworkQuotaInfo();
+ boolean isActiveNetworkMetered();
boolean setRadios(boolean onOff);
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 35e8e47..3250ae7 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -51,5 +51,6 @@ interface INetworkPolicyManager {
boolean getRestrictBackground();
NetworkQuotaInfo getNetworkQuotaInfo(in NetworkState state);
+ boolean isNetworkMetered(in NetworkState state);
}
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index b4f6367..08d4c6c 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -32,6 +32,9 @@ interface INetworkStatsService {
/** Return data layer snapshot of UID network usage. */
NetworkStats getDataLayerSnapshotForUid(int uid);
+ /** Return set of any ifaces associated with mobile networks since boot. */
+ String[] getMobileIfaces();
+
/** Increment data layer count of operations performed for UID and tag. */
void incrementOperationCount(int uid, int tag, int operationCount);
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 50432a1..39a4d7b 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -48,6 +48,8 @@ public class NetworkTemplate implements Parcelable {
public static final int MATCH_MOBILE_4G = 3;
public static final int MATCH_WIFI = 4;
public static final int MATCH_ETHERNET = 5;
+ public static final int MATCH_MOBILE_WILDCARD = 6;
+ public static final int MATCH_WIFI_WILDCARD = 7;
/**
* Set of {@link NetworkInfo#getType()} that reflect data usage.
@@ -86,11 +88,19 @@ public class NetworkTemplate implements Parcelable {
}
/**
+ * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks,
+ * regardless of IMSI.
+ */
+ public static NetworkTemplate buildTemplateMobileWildcard() {
+ return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null);
+ }
+
+ /**
* Template to match all {@link ConnectivityManager#TYPE_WIFI} networks,
* regardless of SSID.
*/
public static NetworkTemplate buildTemplateWifiWildcard() {
- return new NetworkTemplate(MATCH_WIFI, null, null);
+ return new NetworkTemplate(MATCH_WIFI_WILDCARD, null, null);
}
@Deprecated
@@ -198,6 +208,10 @@ public class NetworkTemplate implements Parcelable {
return matchesWifi(ident);
case MATCH_ETHERNET:
return matchesEthernet(ident);
+ case MATCH_MOBILE_WILDCARD:
+ return matchesMobileWildcard(ident);
+ case MATCH_WIFI_WILDCARD:
+ return matchesWifiWildcard(ident);
default:
throw new IllegalArgumentException("unknown network template");
}
@@ -257,13 +271,7 @@ public class NetworkTemplate implements Parcelable {
private boolean matchesWifi(NetworkIdentity ident) {
switch (ident.mType) {
case TYPE_WIFI:
- if (mNetworkId == null) {
- return true;
- } else {
- return Objects.equal(mNetworkId, ident.mNetworkId);
- }
- case TYPE_WIFI_P2P:
- return mNetworkId == null;
+ return Objects.equal(mNetworkId, ident.mNetworkId);
default:
return false;
}
@@ -279,6 +287,24 @@ public class NetworkTemplate implements Parcelable {
return false;
}
+ private boolean matchesMobileWildcard(NetworkIdentity ident) {
+ if (ident.mType == TYPE_WIMAX) {
+ return true;
+ } else {
+ return contains(DATA_USAGE_NETWORK_TYPES, ident.mType);
+ }
+ }
+
+ private boolean matchesWifiWildcard(NetworkIdentity ident) {
+ switch (ident.mType) {
+ case TYPE_WIFI:
+ case TYPE_WIFI_P2P:
+ return true;
+ default:
+ return false;
+ }
+ }
+
private static String getMatchRuleName(int matchRule) {
switch (matchRule) {
case MATCH_MOBILE_3G_LOWER:
@@ -291,6 +317,10 @@ public class NetworkTemplate implements Parcelable {
return "WIFI";
case MATCH_ETHERNET:
return "ETHERNET";
+ case MATCH_MOBILE_WILDCARD:
+ return "MOBILE_WILDCARD";
+ case MATCH_WIFI_WILDCARD:
+ return "WIFI_WILDCARD";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index ee3e165..e437d2e 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -88,6 +88,16 @@ public class TrafficStats {
*/
public static final int TAG_SYSTEM_BACKUP = 0xFFFFFF03;
+ private static INetworkStatsService sStatsService;
+
+ private synchronized static INetworkStatsService getStatsService() {
+ if (sStatsService == null) {
+ sStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ }
+ return sStatsService;
+ }
+
/**
* Snapshot of {@link NetworkStats} when the currently active profiling
* session started, or {@code null} if no session active.
@@ -228,11 +238,9 @@ public class TrafficStats {
* @param operationCount Number of operations to increment count by.
*/
public static void incrementOperationCount(int tag, int operationCount) {
- final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
final int uid = android.os.Process.myUid();
try {
- statsService.incrementOperationCount(uid, tag, operationCount);
+ getStatsService().incrementOperationCount(uid, tag, operationCount);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -257,7 +265,13 @@ public class TrafficStats {
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getMobileTxPackets();
+ public static long getMobileTxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += getTxPackets(iface);
+ }
+ return total;
+ }
/**
* Get the total number of packets received through the mobile interface.
@@ -265,7 +279,13 @@ public class TrafficStats {
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getMobileRxPackets();
+ public static long getMobileRxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += getRxPackets(iface);
+ }
+ return total;
+ }
/**
* Get the total number of bytes transmitted through the mobile interface.
@@ -273,7 +293,13 @@ public class TrafficStats {
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getMobileTxBytes();
+ public static long getMobileTxBytes() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += getTxBytes(iface);
+ }
+ return total;
+ }
/**
* Get the total number of bytes received through the mobile interface.
@@ -281,7 +307,13 @@ public class TrafficStats {
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getMobileRxBytes();
+ public static long getMobileRxBytes() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += getRxBytes(iface);
+ }
+ return total;
+ }
/**
* Get the total number of packets transmitted through the specified interface.
@@ -290,7 +322,9 @@ public class TrafficStats {
* {@link #UNSUPPORTED} will be returned.
* @hide
*/
- public static native long getTxPackets(String iface);
+ public static long getTxPackets(String iface) {
+ return nativeGetIfaceStat(iface, TYPE_TX_PACKETS);
+ }
/**
* Get the total number of packets received through the specified interface.
@@ -299,7 +333,9 @@ public class TrafficStats {
* {@link #UNSUPPORTED} will be returned.
* @hide
*/
- public static native long getRxPackets(String iface);
+ public static long getRxPackets(String iface) {
+ return nativeGetIfaceStat(iface, TYPE_RX_PACKETS);
+ }
/**
* Get the total number of bytes transmitted through the specified interface.
@@ -308,7 +344,9 @@ public class TrafficStats {
* {@link #UNSUPPORTED} will be returned.
* @hide
*/
- public static native long getTxBytes(String iface);
+ public static long getTxBytes(String iface) {
+ return nativeGetIfaceStat(iface, TYPE_TX_BYTES);
+ }
/**
* Get the total number of bytes received through the specified interface.
@@ -317,8 +355,9 @@ public class TrafficStats {
* {@link #UNSUPPORTED} will be returned.
* @hide
*/
- public static native long getRxBytes(String iface);
-
+ public static long getRxBytes(String iface) {
+ return nativeGetIfaceStat(iface, TYPE_RX_BYTES);
+ }
/**
* Get the total number of packets sent through all network interfaces.
@@ -326,7 +365,9 @@ public class TrafficStats {
* @return the number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getTotalTxPackets();
+ public static long getTotalTxPackets() {
+ return nativeGetTotalStat(TYPE_TX_PACKETS);
+ }
/**
* Get the total number of packets received through all network interfaces.
@@ -334,7 +375,9 @@ public class TrafficStats {
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getTotalRxPackets();
+ public static long getTotalRxPackets() {
+ return nativeGetTotalStat(TYPE_RX_PACKETS);
+ }
/**
* Get the total number of bytes sent through all network interfaces.
@@ -342,7 +385,9 @@ public class TrafficStats {
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getTotalTxBytes();
+ public static long getTotalTxBytes() {
+ return nativeGetTotalStat(TYPE_TX_BYTES);
+ }
/**
* Get the total number of bytes received through all network interfaces.
@@ -350,7 +395,9 @@ public class TrafficStats {
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getTotalRxBytes();
+ public static long getTotalRxBytes() {
+ return nativeGetTotalStat(TYPE_RX_BYTES);
+ }
/**
* Get the number of bytes sent through the network for this UID.
@@ -483,7 +530,6 @@ public class TrafficStats {
*/
public static native long getUidTcpRxSegments(int uid);
-
/**
* Get the number of UDP packets sent for this UID.
* Includes DNS requests.
@@ -515,13 +561,33 @@ public class TrafficStats {
* special permission.
*/
private static NetworkStats getDataLayerSnapshotForUid(Context context) {
- final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
final int uid = android.os.Process.myUid();
try {
- return statsService.getDataLayerSnapshotForUid(uid);
+ return getStatsService().getDataLayerSnapshotForUid(uid);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Return set of any ifaces associated with mobile networks since boot.
+ * Interfaces are never removed from this list, so counters should always be
+ * monotonic.
+ */
+ private static String[] getMobileIfaces() {
+ try {
+ return getStatsService().getMobileIfaces();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
+
+ // NOTE: keep these in sync with android_net_TrafficStats.cpp
+ private static final int TYPE_RX_BYTES = 0;
+ private static final int TYPE_RX_PACKETS = 1;
+ private static final int TYPE_TX_BYTES = 2;
+ private static final int TYPE_TX_PACKETS = 3;
+
+ private static native long nativeGetTotalStat(int type);
+ private static native long nativeGetIfaceStat(String iface, int type);
}
diff --git a/core/java/android/net/nsd/DnsSdServiceInfo.java b/core/java/android/net/nsd/DnsSdServiceInfo.java
index 33c3eb9..66abd3a 100644
--- a/core/java/android/net/nsd/DnsSdServiceInfo.java
+++ b/core/java/android/net/nsd/DnsSdServiceInfo.java
@@ -22,8 +22,8 @@ import android.os.Parcel;
import java.net.InetAddress;
/**
- * Defines a service based on DNS service discovery
- * {@hide}
+ * A class representing service information for network service discovery
+ * {@see NsdManager}
*/
public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
@@ -40,56 +40,63 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
public DnsSdServiceInfo() {
}
+ /** @hide */
public DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) {
mServiceName = sn;
mServiceType = rt;
mTxtRecord = tr;
}
+ /** Get the service name */
@Override
- /** @hide */
public String getServiceName() {
return mServiceName;
}
+ /** Set the service name */
@Override
- /** @hide */
public void setServiceName(String s) {
mServiceName = s;
}
+ /** Get the service type */
@Override
- /** @hide */
public String getServiceType() {
return mServiceType;
}
+ /** Set the service type */
@Override
- /** @hide */
public void setServiceType(String s) {
mServiceType = s;
}
+ /** @hide */
public DnsSdTxtRecord getTxtRecord() {
return mTxtRecord;
}
+ /** @hide */
public void setTxtRecord(DnsSdTxtRecord t) {
mTxtRecord = new DnsSdTxtRecord(t);
}
+ /** Get the host address. The host address is valid for a resolved service. */
public InetAddress getHost() {
return mHost;
}
+ /** Set the host address */
public void setHost(InetAddress s) {
mHost = s;
}
+ /** Get port number. The port number is valid for a resolved service. */
public int getPort() {
return mPort;
}
+ /** Set port number */
public void setPort(int p) {
mPort = p;
}
@@ -147,5 +154,4 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
return new DnsSdServiceInfo[size];
}
};
-
}
diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java
index 952e02f..ccb9a91 100644
--- a/core/java/android/net/nsd/DnsSdTxtRecord.java
+++ b/core/java/android/net/nsd/DnsSdTxtRecord.java
@@ -37,7 +37,6 @@ import java.util.Arrays;
* The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it
* as need be to implement its various methods.
*
- * @hide
*/
public class DnsSdTxtRecord implements Parcelable {
private static final byte mSeperator = '=';
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 505f11b..dac8d20 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -24,29 +24,110 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.Messenger;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
/**
- * The Network Service Discovery Manager class provides the API for service
- * discovery. Service discovery enables applications to discover and connect with services
- * on a network. Example applications include a game application discovering another instance
- * of the game application or a printer application discovering other printers on a network.
+ * The Network Service Discovery Manager class provides the API to discover services
+ * on a network. As an example, if device A and device B are connected over a Wi-Fi
+ * network, a game registered on device A can be discovered by a game on device
+ * B. Another example use case is an application discovering printers on the network.
+ *
+ * <p> The API currently supports DNS based service discovery and discovery is currently
+ * limited to a local network over Multicast DNS. In future, it will be extended to
+ * support wide area discovery and other service discovery mechanisms.
+ * DNS service discovery is described at http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
*
* <p> The API is asynchronous and responses to requests from an application are on listener
- * callbacks provided by the application. The application needs to do an initialization with
- * {@link #initialize} before doing any operation.
+ * callbacks provided by the application. The application must invoke {@link #initialize} before
+ * doing any other operation.
+ *
+ * <p> There are three main operations the API supports - registration, discovery and resolution.
+ * <pre>
+ * Application start
+ * |
+ * | <----------------------------------------------
+ * initialize() |
+ * | |
+ * | Wait until channel connects |
+ * | before doing any operation |
+ * | |
+ * onChannelConnected() __________ |
+ * | | |
+ * | | |
+ * | onServiceRegistered() | |
+ * Register any local services / | |
+ * to be advertised with \ | | If application needs to
+ * registerService() onFailure() | | do any further operations
+ * | | | again, it needs to
+ * | | | initialize() connection
+ * discoverServices() | | to framework again
+ * | | |
+ * Maintain a list to track | |
+ * discovered services | |
+ * | | |
+ * |---------> |-> onChannelDisconnected()
+ * | | |
+ * | onServiceFound() |
+ * | | |
+ * | add service to list |
+ * | | |
+ * |<---------- |
+ * | |
+ * |---------> |
+ * | | |
+ * | onServiceLost() |
+ * | | |
+ * | remove service from list |
+ * | | |
+ * |<---------- |
+ * | |
+ * | |
+ * | Connect to a service |
+ * | from list ? |
+ * | |
+ * resolveService() |
+ * | |
+ * onServiceResolved() |
+ * | |
+ * Establish connection to service |
+ * with the host and port information |
+ * | |
+ * | ___________|
+ * deinitialize()
+ * when done with all operations
+ * or before quit
+ *
+ * </pre>
+ * An application that needs to advertise itself over a network for other applications to
+ * discover it can do so with a call to {@link #registerService}. If Example is a http based
+ * application that can provide HTML data to peer services, it can register a name "Example"
+ * with service type "_http._tcp". A successful registration is notified with a callback to
+ * {@link DnsSdRegisterListener#onServiceRegistered} and a failure to register is notified
+ * over {@link DnsSdRegisterListener#onFailure}
+ *
+ * <p> A peer application looking for http services can initiate a discovery for "_http._tcp"
+ * with a call to {@link #discoverServices}. A service found is notified with a callback
+ * to {@link DnsSdDiscoveryListener#onServiceFound} and a service lost is notified on
+ * {@link DnsSdDiscoveryListener#onServiceLost}.
+ *
+ * <p> Once the peer application discovers the "Example" http srevice, and needs to receive data
+ * from the "Example" application, it can initiate a resolve with {@link #resolveService} to
+ * resolve the host and port details for the purpose of establishing a connection. A successful
+ * resolve is notified on {@link DnsSdResolveListener#onServiceResolved} and a failure is notified
+ * on {@link DnsSdResolveListener#onFailure}.
*
- * <p> Android currently supports DNS based service discovery and it is limited to a local
- * network with the use of multicast DNS. In future, this class will be
- * extended to support other service discovery mechanisms.
+ * Applications can reserve for a service type at
+ * http://www.iana.org/form/ports-service. Existing services can be found at
+ * http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml
*
* Get an instance of this class by calling {@link android.content.Context#getSystemService(String)
* Context.getSystemService(Context.NSD_SERVICE)}.
- * @hide
*
+ * {@see DnsSdServiceInfo}
*/
public class NsdManager {
private static final String TAG = "NsdManager";
@@ -80,27 +161,32 @@ public class NsdManager {
public static final int REGISTER_SERVICE_SUCCEEDED = BASE + 11;
/** @hide */
- public static final int UPDATE_SERVICE = BASE + 12;
+ public static final int UNREGISTER_SERVICE = BASE + 12;
/** @hide */
- public static final int UPDATE_SERVICE_FAILED = BASE + 13;
+ public static final int UNREGISTER_SERVICE_FAILED = BASE + 13;
/** @hide */
- public static final int UPDATE_SERVICE_SUCCEEDED = BASE + 14;
+ public static final int UNREGISTER_SERVICE_SUCCEEDED = BASE + 14;
/** @hide */
- public static final int RESOLVE_SERVICE = BASE + 15;
+ public static final int UPDATE_SERVICE = BASE + 15;
/** @hide */
- public static final int RESOLVE_SERVICE_FAILED = BASE + 16;
+ public static final int UPDATE_SERVICE_FAILED = BASE + 16;
/** @hide */
- public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 17;
+ public static final int UPDATE_SERVICE_SUCCEEDED = BASE + 17;
/** @hide */
- public static final int STOP_RESOLVE = BASE + 18;
+ public static final int RESOLVE_SERVICE = BASE + 18;
/** @hide */
- public static final int STOP_RESOLVE_FAILED = BASE + 19;
+ public static final int RESOLVE_SERVICE_FAILED = BASE + 19;
/** @hide */
- public static final int STOP_RESOLVE_SUCCEEDED = BASE + 20;
-
+ public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 20;
+ /** @hide */
+ public static final int STOP_RESOLVE = BASE + 21;
+ /** @hide */
+ public static final int STOP_RESOLVE_FAILED = BASE + 22;
+ /** @hide */
+ public static final int STOP_RESOLVE_SUCCEEDED = BASE + 23;
/**
* Create a new Nsd instance. Applications use
@@ -115,36 +201,44 @@ public class NsdManager {
}
/**
+ * Passed with onFailure() calls.
* Indicates that the operation failed due to an internal error.
*/
public static final int ERROR = 0;
/**
- * Indicates that the operation failed because service discovery is unsupported on the device.
+ * Passed with onFailure() calls.
+ * Indicates that the operation failed because service discovery
+ * is unsupported on the device.
*/
public static final int UNSUPPORTED = 1;
/**
- * Indicates that the operation failed because the framework is busy and
- * unable to service the request.
+ * Passed with onFailure() calls.
+ * Indicates that the operation failed because the framework is
+ * busy and unable to service the request.
*/
public static final int BUSY = 2;
/**
+ * Passed with onFailure() calls.
* Indicates that the operation failed because it is already active.
*/
public static final int ALREADY_ACTIVE = 3;
/**
+ * Passed with onFailure() calls.
* Indicates that the operation failed because maximum limit on
* service registrations has reached.
*/
public static final int MAX_REGS_REACHED = 4;
-
-
/** Interface for callback invocation when framework channel is connected or lost */
public interface ChannelListener {
+ /**
+ * The channel to the framework is connected.
+ * Application can initiate calls into the framework using the channel instance passed.
+ */
public void onChannelConnected(Channel c);
/**
* The channel to the framework has been disconnected.
@@ -153,6 +247,7 @@ public class NsdManager {
public void onChannelDisconnected();
}
+ /** Generic interface for callback invocation for a success or failure */
public interface ActionListener {
public void onFailure(int errorCode);
@@ -160,11 +255,12 @@ public class NsdManager {
public void onSuccess();
}
+ /** Interface for callback invocation for service discovery */
public interface DnsSdDiscoveryListener {
public void onFailure(int errorCode);
- public void onStarted(String registrationType);
+ public void onStarted(String serviceType);
public void onServiceFound(DnsSdServiceInfo serviceInfo);
@@ -172,6 +268,7 @@ public class NsdManager {
}
+ /** Interface for callback invocation for service registration */
public interface DnsSdRegisterListener {
public void onFailure(int errorCode);
@@ -179,6 +276,7 @@ public class NsdManager {
public void onServiceRegistered(int registeredId, DnsSdServiceInfo serviceInfo);
}
+ /** @hide */
public interface DnsSdUpdateRegistrationListener {
public void onFailure(int errorCode);
@@ -186,6 +284,7 @@ public class NsdManager {
public void onServiceUpdated(int registeredId, DnsSdTxtRecord txtRecord);
}
+ /** Interface for callback invocation for service resolution */
public interface DnsSdResolveListener {
public void onFailure(int errorCode);
@@ -208,6 +307,7 @@ public class NsdManager {
private DnsSdDiscoveryListener mDnsSdDiscoveryListener;
private ActionListener mDnsSdStopDiscoveryListener;
private DnsSdRegisterListener mDnsSdRegisterListener;
+ private ActionListener mDnsSdUnregisterListener;
private DnsSdUpdateRegistrationListener mDnsSdUpdateListener;
private DnsSdResolveListener mDnsSdResolveListener;
private ActionListener mDnsSdStopResolveListener;
@@ -279,7 +379,17 @@ public class NsdManager {
(DnsSdServiceInfo) message.obj);
}
break;
- case UPDATE_SERVICE_FAILED:
+ case UNREGISTER_SERVICE_FAILED:
+ if (mDnsSdUnregisterListener != null) {
+ mDnsSdUnregisterListener.onFailure(message.arg1);
+ }
+ break;
+ case UNREGISTER_SERVICE_SUCCEEDED:
+ if (mDnsSdUnregisterListener != null) {
+ mDnsSdUnregisterListener.onSuccess();
+ }
+ break;
+ case UPDATE_SERVICE_FAILED:
if (mDnsSdUpdateListener != null) {
mDnsSdUpdateListener.onFailure(message.arg1);
}
@@ -319,6 +429,10 @@ public class NsdManager {
}
}
+ private static void checkChannel(Channel c) {
+ if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ }
+
/**
* Registers the application with the service discovery framework. This function
* must be the first to be called before any other operations are performed. No service
@@ -327,7 +441,7 @@ public class NsdManager {
*
* @param srcContext is the context of the source
* @param srcLooper is the Looper on which the callbacks are receivied
- * @param listener for callback at loss of framework communication.
+ * @param listener for callback at loss of framework communication. Cannot be null.
*/
public void initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
Messenger messenger = getMessenger();
@@ -339,88 +453,142 @@ public class NsdManager {
}
/**
- * Set the listener for service discovery. Can be null.
- */
- public void setDiscoveryListener(Channel c, DnsSdDiscoveryListener b) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
- c.mDnsSdDiscoveryListener = b;
- }
-
- /**
- * Set the listener for stop service discovery. Can be null.
- */
- public void setStopDiscoveryListener(Channel c, ActionListener a) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
- c.mDnsSdStopDiscoveryListener = a;
- }
-
- /**
- * Set the listener for service registration. Can be null.
- */
- public void setRegisterListener(Channel c, DnsSdRegisterListener b) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
- c.mDnsSdRegisterListener = b;
- }
-
- /**
- * Set the listener for service registration. Can be null.
+ * Disconnects application from service discovery framework. No further operations
+ * will succeed until a {@link #initialize} is called again.
+ *
+ * @param c channel initialized with {@link #initialize}
*/
- public void setUpdateRegistrationListener(Channel c, DnsSdUpdateRegistrationListener b) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
- c.mDnsSdUpdateListener = b;
+ public void deinitialize(Channel c) {
+ checkChannel(c);
+ c.mAsyncChannel.disconnect();
}
/**
- * Set the listener for service resolution. Can be null.
+ * Register a service to be discovered by other services.
+ *
+ * <p> The function call immediately returns after sending a request to register service
+ * to the framework. The application is notified of a success to initiate
+ * discovery through the callback {@link DnsSdRegisterListener#onServiceRegistered} or a failure
+ * through {@link DnsSdRegisterListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param serviceType The service type being advertised.
+ * @param port on which the service is listenering for incoming connections
+ * @param listener for success or failure callback. Can be null.
*/
- public void setResolveListener(Channel c, DnsSdResolveListener b) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
- c.mDnsSdResolveListener = b;
+ public void registerService(Channel c, String serviceName, String serviceType, int port,
+ DnsSdRegisterListener listener) {
+ checkChannel(c);
+ if (TextUtils.isEmpty(serviceName) || TextUtils.isEmpty(serviceType)) {
+ throw new IllegalArgumentException("Service name or type cannot be empty");
+ }
+ if (port <= 0) {
+ throw new IllegalArgumentException("Invalid port number");
+ }
+ DnsSdServiceInfo serviceInfo = new DnsSdServiceInfo(serviceName, serviceType, null);
+ serviceInfo.setPort(port);
+ c.mDnsSdRegisterListener = listener;
+ c.mAsyncChannel.sendMessage(REGISTER_SERVICE, serviceInfo);
}
/**
- * Set the listener for stopping service resolution. Can be null.
+ * Unregister a service registered through {@link #registerService}
+ * @param c is the channel created at {@link #initialize}
+ * @param registeredId is obtained at {@link DnsSdRegisterListener#onServiceRegistered}
+ * @param listener provides callbacks for success or failure. Can be null.
*/
- public void setStopResolveListener(Channel c, ActionListener b) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
- c.mDnsSdStopResolveListener = b;
- }
-
- public void registerService(Channel c, DnsSdServiceInfo serviceInfo) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
- if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo");
- c.mAsyncChannel.sendMessage(REGISTER_SERVICE, serviceInfo);
+ public void unregisterService(Channel c, int registeredId, ActionListener listener) {
+ checkChannel(c);
+ c.mDnsSdUnregisterListener = listener;
+ c.mAsyncChannel.sendMessage(UNREGISTER_SERVICE, registeredId);
}
+ /** @hide */
public void updateService(Channel c, int registeredId, DnsSdTxtRecord txtRecord) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ checkChannel(c);
c.mAsyncChannel.sendMessage(UPDATE_SERVICE, registeredId, 0, txtRecord);
}
- public void discoverServices(Channel c, String serviceType) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
- if (c.mDnsSdDiscoveryListener == null) throw new
- IllegalStateException("Discovery listener needs to be set first");
+ /**
+ * Initiate service discovery to browse for instances of a service type. Service discovery
+ * consumes network bandwidth and will continue until the application calls
+ * {@link #stopServiceDiscovery}.
+ *
+ * <p> The function call immediately returns after sending a request to start service
+ * discovery to the framework. The application is notified of a success to initiate
+ * discovery through the callback {@link DnsSdDiscoveryListener#onStarted} or a failure
+ * through {@link DnsSdDiscoveryListener#onFailure}.
+ *
+ * <p> Upon successful start, application is notified when a service is found with
+ * {@link DnsSdDiscoveryListener#onServiceFound} or when a service is lost with
+ * {@link DnsSdDiscoveryListener#onServiceLost}.
+ *
+ * <p> Upon failure to start, service discovery is not active and application does
+ * not need to invoke {@link #stopServiceDiscovery}
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param serviceType The service type being discovered. Examples include "_http._tcp" for
+ * http services or "_ipp._tcp" for printers
+ * @param listener provides callbacks when service is found or lost. Cannot be null.
+ */
+ public void discoverServices(Channel c, String serviceType, DnsSdDiscoveryListener listener) {
+ checkChannel(c);
+ if (listener == null) {
+ throw new IllegalStateException("Discovery listener needs to be set first");
+ }
+ if (TextUtils.isEmpty(serviceType)) {
+ throw new IllegalStateException("Service type cannot be empty");
+ }
DnsSdServiceInfo s = new DnsSdServiceInfo();
s.setServiceType(serviceType);
+ c.mDnsSdDiscoveryListener = listener;
c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, s);
}
- public void stopServiceDiscovery(Channel c) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ /**
+ * Stop service discovery initiated with {@link #discoverServices}. An active service
+ * discovery is notified to the application with {@link DnsSdDiscoveryListener#onStarted}
+ * and it stays active until the application invokes a stop service discovery.
+ *
+ * <p> Upon failure to start service discovery notified through
+ * {@link DnsSdDiscoveryListener#onFailure} service discovery is not active and
+ * application does not need to stop it.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener notifies success or failure. Can be null.
+ */
+ public void stopServiceDiscovery(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mDnsSdStopDiscoveryListener = listener;
c.mAsyncChannel.sendMessage(STOP_DISCOVERY);
}
- public void resolveService(Channel c, DnsSdServiceInfo serviceInfo) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
- if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo");
- if (c.mDnsSdResolveListener == null) throw new
- IllegalStateException("Resolve listener needs to be set first");
+ /**
+ * Resolve a discovered service. An application can resolve a service right before
+ * establishing a connection to fetch the IP and port details on which to setup
+ * the connection.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param serviceName of the the service
+ * @param serviceType of the service
+ * @param listener to receive callback upon success or failure. Cannot be null.
+ */
+ public void resolveService(Channel c, String serviceName, String serviceType,
+ DnsSdResolveListener listener) {
+ checkChannel(c);
+ if (TextUtils.isEmpty(serviceName) || TextUtils.isEmpty(serviceType)) {
+ throw new IllegalArgumentException("Service name or type cannot be empty");
+ }
+ if (listener == null) throw new
+ IllegalStateException("Resolve listener cannot be null");
+ c.mDnsSdResolveListener = listener;
+ DnsSdServiceInfo serviceInfo = new DnsSdServiceInfo(serviceName, serviceType, null);
c.mAsyncChannel.sendMessage(RESOLVE_SERVICE, serviceInfo);
}
+ /** @hide */
public void stopServiceResolve(Channel c) {
- if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ checkChannel(c);
if (c.mDnsSdResolveListener == null) throw new
IllegalStateException("Resolve listener needs to be set first");
c.mAsyncChannel.sendMessage(STOP_RESOLVE);
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 20a731e..98fe06a 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1324,4 +1324,42 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
return false;
}
}
+
+ /**
+ * Return a String describing the calling method and location at a particular stack depth.
+ * @param callStack the Thread stack
+ * @param depth the depth of stack to return information for.
+ * @return the String describing the caller at that depth.
+ */
+ private static String getCaller(StackTraceElement callStack[], int depth) {
+ // callStack[4] is the caller of the method that called getCallers()
+ if (4 + depth >= callStack.length) {
+ return "<bottom of call stack>";
+ }
+ StackTraceElement caller = callStack[4 + depth];
+ return caller.getClassName() + "." + caller.getMethodName() + ":" + caller.getLineNumber();
+ }
+
+ /**
+ * Return a string consisting of methods and locations at multiple call stack levels.
+ * @param depth the number of levels to return, starting with the immediate caller.
+ * @return a string describing the call stack.
+ * {@hide}
+ */
+ public static String getCallers(final int depth) {
+ final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < depth; i++) {
+ sb.append(getCaller(callStack, i)).append(" ");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @return a String describing the immediate caller of the calling function.
+ * {@hide}
+ */
+ public static String getCaller() {
+ return getCaller(Thread.currentThread().getStackTrace(), 0);
+ }
}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index af94a37..4645fab 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -33,6 +33,7 @@ public final class Trace {
public static final long TRACE_TAG_GRAPHICS = 1L << 1;
public static final long TRACE_TAG_INPUT = 1L << 2;
public static final long TRACE_TAG_VIEW = 1L << 3;
+ public static final long TRACE_TAG_WEBVIEW = 1L << 4;
private static final long sEnabledTags = nativeGetEnabledTags();
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 83799c4..c4aa691 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -756,6 +756,22 @@ public final class CalendarContract {
public static final int ATTENDEE_STATUS_DECLINED = 2;
public static final int ATTENDEE_STATUS_INVITED = 3;
public static final int ATTENDEE_STATUS_TENTATIVE = 4;
+
+ /**
+ * The identity of the attendee as referenced in
+ * {@link ContactsContract.CommonDataKinds.Identity#IDENTITY}.
+ * This is required only if {@link #ATTENDEE_ID_NAMESPACE} is present. Column name.
+ * <P>Type: STRING</P>
+ */
+ public static final String ATTENDEE_IDENTITY = "attendeeIdentity";
+
+ /**
+ * The identity name space of the attendee as referenced in
+ * {@link ContactsContract.CommonDataKinds.Identity#NAMESPACE}.
+ * This is required only if {@link #ATTENDEE_IDENTITY} is present. Column name.
+ * <P>Type: STRING</P>
+ */
+ public static final String ATTENDEE_ID_NAMESPACE = "attendeeIdNamespace";
}
/**
@@ -773,6 +789,8 @@ public final class CalendarContract {
* <li>{@link #ATTENDEE_RELATIONSHIP}</li>
* <li>{@link #ATTENDEE_TYPE}</li>
* <li>{@link #ATTENDEE_STATUS}</li>
+ * <li>{@link #ATTENDEE_IDENTITY}</li>
+ * <li>{@link #ATTENDEE_ID_NAMESPACE}</li>
* </ul>
*/
public static final class Attendees implements BaseColumns, AttendeesColumns, EventsColumns {
@@ -1221,12 +1239,17 @@ public final class CalendarContract {
Attendees.ATTENDEE_RELATIONSHIP,
Attendees.ATTENDEE_TYPE,
Attendees.ATTENDEE_STATUS,
+ Attendees.ATTENDEE_IDENTITY,
+ Attendees.ATTENDEE_ID_NAMESPACE
};
private static final int COLUMN_ATTENDEE_NAME = 0;
private static final int COLUMN_ATTENDEE_EMAIL = 1;
private static final int COLUMN_ATTENDEE_RELATIONSHIP = 2;
private static final int COLUMN_ATTENDEE_TYPE = 3;
private static final int COLUMN_ATTENDEE_STATUS = 4;
+ private static final int COLUMN_ATTENDEE_IDENTITY = 5;
+ private static final int COLUMN_ATTENDEE_ID_NAMESPACE = 6;
+
private static final String[] EXTENDED_PROJECTION = new String[] {
ExtendedProperties._ID,
ExtendedProperties.NAME,
@@ -1362,6 +1385,10 @@ public final class CalendarContract {
subCursor.getInt(COLUMN_ATTENDEE_TYPE));
attendeeValues.put(Attendees.ATTENDEE_STATUS,
subCursor.getInt(COLUMN_ATTENDEE_STATUS));
+ attendeeValues.put(Attendees.ATTENDEE_IDENTITY,
+ subCursor.getInt(COLUMN_ATTENDEE_IDENTITY));
+ attendeeValues.put(Attendees.ATTENDEE_ID_NAMESPACE,
+ subCursor.getInt(COLUMN_ATTENDEE_ID_NAMESPACE));
entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
}
} finally {
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 850349b..36c0189 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -611,6 +611,11 @@ public class BluetoothService extends IBluetooth.Stub {
/*package*/ void initBluetoothAfterTurningOn() {
String discoverable = getProperty("Discoverable", false);
String timeout = getProperty("DiscoverableTimeout", false);
+ if (timeout == null) {
+ Log.w(TAG, "Null DiscoverableTimeout property");
+ // assign a number, anything not 0
+ timeout = "1";
+ }
if (discoverable.equals("true") && Integer.valueOf(timeout) != 0) {
setAdapterPropertyBooleanNative("Discoverable", 0);
}
diff --git a/core/java/android/service/textservice/SpellCheckerService.java b/core/java/android/service/textservice/SpellCheckerService.java
index 53ce32d..c579e6e 100644
--- a/core/java/android/service/textservice/SpellCheckerService.java
+++ b/core/java/android/service/textservice/SpellCheckerService.java
@@ -26,12 +26,18 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
+import android.text.TextUtils;
+import android.text.method.WordIterator;
import android.util.Log;
import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
+import android.widget.SpellChecker;
import java.lang.ref.WeakReference;
+import java.text.BreakIterator;
+import java.util.ArrayList;
+import java.util.Locale;
/**
* SpellCheckerService provides an abstract base class for a spell checker.
@@ -92,6 +98,7 @@ public abstract class SpellCheckerService extends Service {
*/
public static abstract class Session {
private InternalISpellCheckerSession mInternalSession;
+ private volatile SentenceLevelAdapter mSentenceLevelAdapter;
/**
* @hide
@@ -142,8 +149,8 @@ public abstract class SpellCheckerService extends Service {
/**
* Get sentence suggestions for specified texts in an array of TextInfo.
- * The default implementation returns an array of SentenceSuggestionsInfo by simply
- * calling onGetSuggestions.
+ * The default implementation splits the input text to words and returns
+ * {@link SentenceSuggestionsInfo} which contains suggestions for each word.
* This function will run on the incoming IPC thread.
* So, this is not called on the main thread,
* but will be called in series on another thread.
@@ -156,14 +163,41 @@ public abstract class SpellCheckerService extends Service {
*/
public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos,
int suggestionsLimit) {
- final int length = textInfos.length;
- final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[length];
- for (int i = 0; i < length; ++i) {
- final SuggestionsInfo si = onGetSuggestions(textInfos[i], suggestionsLimit);
- si.setCookieAndSequence(textInfos[i].getCookie(), textInfos[i].getSequence());
- final int N = textInfos[i].getText().length();
- retval[i] = new SentenceSuggestionsInfo(
- new SuggestionsInfo[] {si}, new int[]{0}, new int[]{N});
+ if (textInfos == null || textInfos.length == 0) {
+ return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
+ }
+ if (DBG) {
+ Log.d(TAG, "onGetSentenceSuggestionsMultiple: + " + textInfos.length + ", "
+ + suggestionsLimit);
+ }
+ if (mSentenceLevelAdapter == null) {
+ synchronized(this) {
+ if (mSentenceLevelAdapter == null) {
+ final String localeStr = getLocale();
+ if (!TextUtils.isEmpty(localeStr)) {
+ mSentenceLevelAdapter = new SentenceLevelAdapter(new Locale(localeStr));
+ }
+ }
+ }
+ }
+ if (mSentenceLevelAdapter == null) {
+ return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
+ }
+ final int infosSize = textInfos.length;
+ final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[infosSize];
+ for (int i = 0; i < infosSize; ++i) {
+ final SentenceLevelAdapter.SentenceTextInfoParams textInfoParams =
+ mSentenceLevelAdapter.getSplitWords(textInfos[i]);
+ final ArrayList<SentenceLevelAdapter.SentenceWordItem> mItems =
+ textInfoParams.mItems;
+ final int itemsSize = mItems.size();
+ final TextInfo[] splitTextInfos = new TextInfo[itemsSize];
+ for (int j = 0; j < itemsSize; ++j) {
+ splitTextInfos[j] = mItems.get(j).mTextInfo;
+ }
+ retval[i] = SentenceLevelAdapter.reconstructSuggestions(
+ textInfoParams, onGetSuggestionsMultiple(
+ splitTextInfos, suggestionsLimit, true));
}
return retval;
}
@@ -290,4 +324,135 @@ public abstract class SpellCheckerService extends Service {
return internalSession;
}
}
+
+ /**
+ * Adapter class to accommodate word level spell checking APIs to sentence level spell checking
+ * APIs used in
+ * {@link SpellCheckerService.Session#onGetSuggestionsMultiple(TextInfo[], int, boolean)}
+ */
+ private static class SentenceLevelAdapter {
+ public static final SentenceSuggestionsInfo[] EMPTY_SENTENCE_SUGGESTIONS_INFOS =
+ new SentenceSuggestionsInfo[] {};
+ private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, null);
+ /**
+ * Container for split TextInfo parameters
+ */
+ public static class SentenceWordItem {
+ public final TextInfo mTextInfo;
+ public final int mStart;
+ public final int mLength;
+ public SentenceWordItem(TextInfo ti, int start, int end) {
+ mTextInfo = ti;
+ mStart = start;
+ mLength = end - start;
+ }
+ }
+
+ /**
+ * Container for originally queried TextInfo and parameters
+ */
+ public static class SentenceTextInfoParams {
+ final TextInfo mOriginalTextInfo;
+ final ArrayList<SentenceWordItem> mItems;
+ final int mSize;
+ public SentenceTextInfoParams(TextInfo ti, ArrayList<SentenceWordItem> items) {
+ mOriginalTextInfo = ti;
+ mItems = items;
+ mSize = items.size();
+ }
+ }
+
+ private final WordIterator mWordIterator;
+ public SentenceLevelAdapter(Locale locale) {
+ mWordIterator = new WordIterator(locale);
+ }
+
+ private SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) {
+ final WordIterator wordIterator = mWordIterator;
+ final CharSequence originalText = originalTextInfo.getText();
+ final int cookie = originalTextInfo.getCookie();
+ final int start = 0;
+ final int end = originalText.length();
+ final ArrayList<SentenceWordItem> wordItems = new ArrayList<SentenceWordItem>();
+ wordIterator.setCharSequence(originalText, 0, originalText.length());
+ int wordEnd = wordIterator.following(start);
+ int wordStart = wordIterator.getBeginning(wordEnd);
+ if (DBG) {
+ Log.d(TAG, "iterator: break: ---- 1st word start = " + wordStart + ", end = "
+ + wordEnd + "\n" + originalText);
+ }
+ while (wordStart <= end && wordEnd != BreakIterator.DONE
+ && wordStart != BreakIterator.DONE) {
+ if (wordEnd >= start && wordEnd > wordStart) {
+ final String query = originalText.subSequence(wordStart, wordEnd).toString();
+ final TextInfo ti = new TextInfo(query, cookie, query.hashCode());
+ wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd));
+ if (DBG) {
+ Log.d(TAG, "Adapter: word (" + (wordItems.size() - 1) + ") " + query);
+ }
+ }
+ wordEnd = wordIterator.following(wordEnd);
+ if (wordEnd == BreakIterator.DONE) {
+ break;
+ }
+ wordStart = wordIterator.getBeginning(wordEnd);
+ }
+ if (originalText.length() >= SpellChecker.WORD_ITERATOR_INTERVAL
+ && wordItems.size() >= 2) {
+ if (DBG) {
+ Log.w(TAG, "Remove possibly divided word: "
+ + wordItems.get(0).mTextInfo.getText());
+ }
+ wordItems.remove(0);
+ }
+ return new SentenceTextInfoParams(originalTextInfo, wordItems);
+ }
+
+ public static SentenceSuggestionsInfo reconstructSuggestions(
+ SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) {
+ if (results == null || results.length == 0) {
+ return null;
+ }
+ if (DBG) {
+ Log.w(TAG, "Adapter: onGetSuggestions: got " + results.length);
+ }
+ if (originalTextInfoParams == null) {
+ if (DBG) {
+ Log.w(TAG, "Adapter: originalTextInfoParams is null.");
+ }
+ return null;
+ }
+ final int originalCookie = originalTextInfoParams.mOriginalTextInfo.getCookie();
+ final int originalSequence =
+ originalTextInfoParams.mOriginalTextInfo.getSequence();
+
+ final int querySize = originalTextInfoParams.mSize;
+ final int[] offsets = new int[querySize];
+ final int[] lengths = new int[querySize];
+ final SuggestionsInfo[] reconstructedSuggestions = new SuggestionsInfo[querySize];
+ for (int i = 0; i < querySize; ++i) {
+ final SentenceWordItem item = originalTextInfoParams.mItems.get(i);
+ SuggestionsInfo result = null;
+ for (int j = 0; j < results.length; ++j) {
+ final SuggestionsInfo cur = results[j];
+ if (cur != null && cur.getSequence() == item.mTextInfo.getSequence()) {
+ result = cur;
+ result.setCookieAndSequence(originalCookie, originalSequence);
+ break;
+ }
+ }
+ offsets[i] = item.mStart;
+ lengths[i] = item.mLength;
+ reconstructedSuggestions[i] = result != null ? result : EMPTY_SUGGESTIONS_INFO;
+ if (DBG) {
+ final int size = reconstructedSuggestions[i].getSuggestionsCount();
+ Log.w(TAG, "reconstructedSuggestions(" + i + ")" + size + ", first = "
+ + (size > 0 ? reconstructedSuggestions[i].getSuggestionAt(0)
+ : "<none>") + ", offset = " + offsets[i] + ", length = "
+ + lengths[i]);
+ }
+ }
+ return new SentenceSuggestionsInfo(reconstructedSuggestions, offsets, lengths);
+ }
+ }
}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index ab21b32..6c1a6bf 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -602,6 +602,7 @@ final class AccessibilityInteractionController {
// tree traversal.
return (view.mAttachInfo != null
&& view.mAttachInfo.mWindowVisibility == View.VISIBLE
+ && view.getAlpha() > 0
&& view.isShown()
&& view.getGlobalVisibleRect(mViewRootImpl.mTempRect));
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8fe8e40..b70d7b5 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -80,6 +80,8 @@ interface IWindowManager
void prepareAppTransition(int transit, boolean alwaysKeepCurrent);
int getPendingAppTransition();
void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim);
+ void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
+ int startHeight);
void overridePendingAppTransitionThumb(in Bitmap srcThumb, int startX, int startY,
IRemoteCallback startedCallback);
void executeAppTransition();
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 32029ba..214dc5c 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -676,7 +676,6 @@ public class TextureView extends View {
*
* @param surfaceTexture The {@link SurfaceTexture} that the view should use.
* @see SurfaceTexture#detachFromGLContext()
- * @hide
*/
public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
if (surfaceTexture == null) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 962e13a..0ded5f9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24,6 +24,7 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Camera;
import android.graphics.Canvas;
+import android.graphics.Insets;
import android.graphics.Interpolator;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
@@ -2698,6 +2699,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
protected int mPaddingBottom;
/**
+ * The layout insets in pixels, that is the distance in pixels between the
+ * visible edges of this view its bounds.
+ */
+ private Insets mLayoutInsets;
+
+ /**
* Briefly describes the view and is primarily used for accessibility support.
*/
private CharSequence mContentDescription;
@@ -5807,18 +5814,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @see #FOCUSABLES_ALL
* @see #FOCUSABLES_TOUCH_MODE
- * @see #FOCUSABLES_ACCESSIBILITY
*/
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
if (views == null) {
return;
}
- if ((focusableMode & FOCUSABLE_IN_TOUCH_MODE) == FOCUSABLE_IN_TOUCH_MODE) {
- if (isFocusable() && (!isInTouchMode() || isFocusableInTouchMode())) {
- views.add(this);
- return;
- }
- }
if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
if (AccessibilityManager.getInstance(mContext).isEnabled()
&& includeForAccessibility()) {
@@ -5826,14 +5826,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return;
}
}
- if ((focusableMode & FOCUSABLES_ALL) == FOCUSABLES_ALL) {
- if (isFocusable()) {
- views.add(this);
- return;
- }
- } else {
- throw new IllegalArgumentException("Unknow focusable mode: " + focusableMode);
+ if (!isFocusable()) {
+ return;
+ }
+ if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
+ && isInTouchMode() && !isFocusableInTouchMode()) {
+ return;
}
+ views.add(this);
}
/**
@@ -13842,6 +13842,29 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * @hide
+ */
+ public Insets getLayoutInsets() {
+ if (mLayoutInsets == null) {
+ if (mBackground == null) {
+ mLayoutInsets = Insets.NONE;
+ } else {
+ Rect insetRect = new Rect();
+ boolean hasInsets = mBackground.getLayoutInsets(insetRect);
+ mLayoutInsets = hasInsets ? Insets.of(insetRect) : Insets.NONE;
+ }
+ }
+ return mLayoutInsets;
+ }
+
+ /**
+ * @hide
+ */
+ public void setLayoutInsets(Insets layoutInsets) {
+ mLayoutInsets = layoutInsets;
+ }
+
+ /**
* Changes the selection state of this view. A view can be selected or not.
* Note that selection is not the same as focus. Views are typically
* selected in the context of an AdapterView like ListView or GridView;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7e90e2b..6371963 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -170,6 +170,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
protected int mGroupFlags;
+ /*
+ * THe layout mode: either {@link #UNDEFINED_LAYOUT_MODE}, {@link #COMPONENT_BOUNDS} or
+ * {@link #LAYOUT_BOUNDS}
+ */
+ private int mLayoutMode = UNDEFINED_LAYOUT_MODE;
+
/**
* NOTE: If you change the flags below make sure to reflect the changes
* the DisplayList class
@@ -335,6 +341,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
public static final int PERSISTENT_ALL_CACHES = 0x3;
+ // Layout Modes
+
+ private static final int UNDEFINED_LAYOUT_MODE = -1;
+
+ /**
+ * This constant is a {@link #setLayoutMode(int) layoutMode}.
+ * Component bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top},
+ * {@link #getRight() right} and {@link #getBottom() bottom}.
+ */
+ public static final int COMPONENT_BOUNDS = 0;
+
+ /**
+ * This constant is a {@link #setLayoutMode(int) layoutMode}.
+ */
+ public static final int LAYOUT_BOUNDS = 1;
+
/**
* We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL
* are set at the same time.
@@ -4185,6 +4207,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
public final void layout(int l, int t, int r, int b) {
if (mTransition == null || !mTransition.isChangingLayout()) {
+ if (mTransition != null) {
+ mTransition.layoutChange(this);
+ }
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
@@ -4424,6 +4449,50 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Returns the basis of alignment during the layout of this view group:
+ * either {@link #COMPONENT_BOUNDS} or {@link #LAYOUT_BOUNDS}.
+ *
+ * @return the layout mode to use during layout operations
+ *
+ * @see #setLayoutMode(int)
+ */
+ public int getLayoutMode() {
+ if (mLayoutMode == UNDEFINED_LAYOUT_MODE) {
+ ViewParent parent = getParent();
+ if (parent instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) parent;
+ return viewGroup.getLayoutMode();
+ } else {
+ int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+ boolean preJellyBean = targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN;
+ return preJellyBean ? COMPONENT_BOUNDS : LAYOUT_BOUNDS;
+ }
+
+ }
+ return mLayoutMode;
+ }
+
+ /**
+ * Sets the basis of alignment during alignment of this view group.
+ * Valid values are either {@link #COMPONENT_BOUNDS} or {@link #LAYOUT_BOUNDS}.
+ * <p>
+ * The default is to query the property of the parent if this view group has a parent.
+ * If this ViewGroup is the root of the view hierarchy the default
+ * value is {@link #LAYOUT_BOUNDS} for target SDK's greater than JellyBean,
+ * {@link #LAYOUT_BOUNDS} otherwise.
+ *
+ * @param layoutMode the layout mode to use during layout operations
+ *
+ * @see #getLayoutMode()
+ */
+ public void setLayoutMode(int layoutMode) {
+ if (mLayoutMode != layoutMode) {
+ mLayoutMode = layoutMode;
+ requestLayout();
+ }
+ }
+
+ /**
* Returns a new set of layout parameters based on the supplied attributes set.
*
* @param attrs the attributes to build the layout parameters from
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b4554d5..1472993 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2977,7 +2977,10 @@ public final class ViewRootImpl implements ViewParent,
// be when the window is first being added, and mFocused isn't
// set yet.
final View focused = mView.findFocus();
- if (focused != null && !focused.isFocusableInTouchMode()) {
+ if (focused != null) {
+ if (focused.isFocusableInTouchMode()) {
+ return true;
+ }
final ViewGroup ancestorToTakeFocus =
findAncestorToTakeFocusInTouchMode(focused);
if (ancestorToTakeFocus != null) {
diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java
index d05c1af..2167862 100644
--- a/core/java/android/view/textservice/SpellCheckerInfo.java
+++ b/core/java/android/view/textservice/SpellCheckerInfo.java
@@ -45,7 +45,6 @@ public final class SpellCheckerInfo implements Parcelable {
private final ResolveInfo mService;
private final String mId;
private final int mLabel;
- private final boolean mSupportsSentenceSpellCheck;
/**
* The spell checker setting activity's name, used by the system settings to
@@ -98,9 +97,6 @@ public final class SpellCheckerInfo implements Parcelable {
label = sa.getResourceId(com.android.internal.R.styleable.SpellChecker_label, 0);
settingsActivityComponent = sa.getString(
com.android.internal.R.styleable.SpellChecker_settingsActivity);
- mSupportsSentenceSpellCheck = sa.getBoolean(
- com.android.internal.R.styleable.SpellChecker_supportsSentenceSpellCheck,
- false);
sa.recycle();
final int depth = parser.getDepth();
@@ -142,7 +138,6 @@ public final class SpellCheckerInfo implements Parcelable {
*/
public SpellCheckerInfo(Parcel source) {
mLabel = source.readInt();
- mSupportsSentenceSpellCheck = source.readInt() != 0;
mId = source.readString();
mSettingsActivityName = source.readString();
mService = ResolveInfo.CREATOR.createFromParcel(source);
@@ -158,13 +153,6 @@ public final class SpellCheckerInfo implements Parcelable {
}
/**
- * @hide
- */
- public boolean isSentenceSpellCheckSupported() {
- return mSupportsSentenceSpellCheck;
- }
-
- /**
* Return the component of the service that implements.
*/
public ComponentName getComponent() {
@@ -188,7 +176,6 @@ public final class SpellCheckerInfo implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mLabel);
- dest.writeInt(mSupportsSentenceSpellCheck ? 1 : 0);
dest.writeString(mId);
dest.writeString(mSettingsActivityName);
mService.writeToParcel(dest, flags);
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 9dc05e4..628da3c 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -91,8 +91,6 @@ public class SpellCheckerSession {
* This meta-data must reference an XML resource.
**/
public static final String SERVICE_META_DATA = "android.view.textservice.scs";
- private static final String SUPPORT_SENTENCE_SPELL_CHECK = "SupportSentenceSpellCheck";
-
private static final int MSG_ON_GET_SUGGESTION_MULTIPLE = 1;
private static final int MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE = 2;
@@ -191,7 +189,9 @@ public class SpellCheckerSession {
* Get candidate strings for a substring of the specified text.
* @param textInfo text metadata for a spell checker
* @param suggestionsLimit the maximum number of suggestions that will be returned
+ * @deprecated use {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)} instead
*/
+ @Deprecated
public void getSuggestions(TextInfo textInfo, int suggestionsLimit) {
getSuggestions(new TextInfo[] {textInfo}, suggestionsLimit, false);
}
@@ -201,13 +201,14 @@ public class SpellCheckerSession {
* @param textInfos an array of text metadata for a spell checker
* @param suggestionsLimit the maximum number of suggestions that will be returned
* @param sequentialWords true if textInfos can be treated as sequential words.
+ * @deprecated use {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)} instead
*/
+ @Deprecated
public void getSuggestions(
TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
if (DBG) {
Log.w(TAG, "getSuggestions from " + mSpellCheckerInfo.getId());
}
- // TODO: Handle multiple words suggestions by using WordBreakIterator
mSpellCheckerSessionListenerImpl.getSuggestionsMultiple(
textInfos, suggestionsLimit, sequentialWords);
}
@@ -281,7 +282,7 @@ public class SpellCheckerSession {
break;
case TASK_GET_SUGGESTIONS_MULTIPLE_FOR_SENTENCE:
if (DBG) {
- Log.w(TAG, "Get suggestions from the spell checker.");
+ Log.w(TAG, "Get sentence suggestions from the spell checker.");
}
try {
session.onGetSentenceSuggestionsMultiple(
@@ -492,11 +493,4 @@ public class SpellCheckerSession {
public ISpellCheckerSessionListener getSpellCheckerSessionListener() {
return mSpellCheckerSessionListenerImpl;
}
-
- /**
- * @return true if the spell checker supports sentence level spell checking APIs
- */
- public boolean isSentenceSpellCheckSupported() {
- return mSubtype.containsExtraValueKey(SUPPORT_SENTENCE_SPELL_CHECK);
- }
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 64fbdd5..ab798e8 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -1711,7 +1711,7 @@ class CallbackProxy extends Handler {
void onIsSupportedCallback(boolean isSupported) {
Message msg = obtainMessage(SEARCHBOX_IS_SUPPORTED_CALLBACK);
- msg.obj = new Boolean(isSupported);
+ msg.obj = Boolean.valueOf(isSupported);
sendMessage(msg);
}
diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java
index 1441541..a916884 100755
--- a/core/java/android/webkit/GeolocationPermissions.java
+++ b/core/java/android/webkit/GeolocationPermissions.java
@@ -252,7 +252,7 @@ public class GeolocationPermissions {
}
if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
boolean allowed = nativeGetAllowed(origin);
- callback.onReceiveValue(new Boolean(allowed));
+ callback.onReceiveValue(Boolean.valueOf(allowed));
} else {
Map values = new HashMap<String, Object>();
values.put(ORIGIN, origin);
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 105285c..ba48da1 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -190,15 +190,19 @@ public class WebSettings {
}
/**
- * Sets whether the WebView should use its built-in zoom mechanisms, as
- * opposed to separate zoom controls. The built-in zoom mechanisms comprise
- * on-screen zoom controls, which are displayed over the WebView's content,
- * and the use of a pinch gesture to control zooming. Whether or not these
- * on-screen controls are displayed can be set with {@link #setDisplayZoomControls}.
- * The separate zoom controls are no longer supported, so it is recommended
- * that this setting is always enabled.
- * @param enabled Whether the WebView should use the built-in zoom mechanism.
- */
+ * Sets whether the WebView should use its built-in zoom mechanisms. The
+ * built-in zoom mechanisms comprise on-screen zoom controls, which are
+ * displayed over the WebView's content, and the use of a pinch gesture to
+ * control zooming. Whether or not these on-screen controls are displayed
+ * can be set with {@link #setDisplayZoomControls}.
+ * <p>
+ * The built-in mechanisms are the only currently supported zoom
+ * mechanisms, so it is recommended that this setting is always enabled.
+ * @param enabled Whether the WebView should use its built-in zoom mechanisms.
+ */
+ // This method was intended to select between the built-in zoom mechanisms
+ // and the separate zoom controls. The latter were obtained using
+ // {@link WebView#getZoomControls}, which is now hidden.
public void setBuiltInZoomControls(boolean enabled) {
throw new MustOverrideException();
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 5498622..bd10cca 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1530,17 +1530,15 @@ public class WebView extends AbsoluteLayout
}
/**
- * Returns a view containing zoom controls i.e. +/- buttons. The caller is
- * in charge of installing this view to the view hierarchy. This view will
- * become visible when the user starts scrolling via touch and fade away if
- * the user does not interact with it.
+ * Gets the zoom controls for the WebView, as a separate View. The caller is
+ * responsible for inserting this View into the layout hierarchy.
* <p/>
- * API version 3 introduces a built-in zoom mechanism that is shown
- * automatically by the MapView. This is the preferred approach for
- * showing the zoom UI.
+ * API Level 3 introduced built-in zoom mechanisms for the WebView, as
+ * opposed to these separate zoom controls. The built-in mechanisms are
+ * preferred and can be enabled using
+ * {@link WebSettings#setBuiltInZoomControls}.
*
- * @deprecated The built-in zoom mechanism is preferred, see
- * {@link WebSettings#setBuiltInZoomControls(boolean)}.
+ * @deprecated The built-in zoom mechanisms are preferred.
* @hide since API version 16.
*/
@Deprecated
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index e2880d6..aea23c0 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1772,6 +1772,9 @@ public final class WebViewCore {
@Override
public boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
+ if (mNativeClass == 0) {
+ return false;
+ }
switch (eventType) {
case WebViewInputDispatcher.EVENT_TYPE_CLICK:
return nativeMouseClick(mNativeClass);
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 1a2231e..abfc577 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -650,7 +650,8 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
mEmptyView = emptyView;
// If not explicitly specified this view is important for accessibility.
- if (emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ if (emptyView != null
+ && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 60dd55c..1cb676f 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Insets;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
@@ -559,9 +560,9 @@ public class GridLayout extends ViewGroup {
int flags = (gravity & mask) >> shift;
switch (flags) {
case (AXIS_SPECIFIED | AXIS_PULL_BEFORE):
- return LEADING;
+ return horizontal ? LEFT : TOP;
case (AXIS_SPECIFIED | AXIS_PULL_AFTER):
- return TRAILING;
+ return horizontal ? RIGHT : BOTTOM;
case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER):
return FILL;
case AXIS_SPECIFIED:
@@ -1042,12 +1043,15 @@ public class GridLayout extends ViewGroup {
int rightMargin = getMargin(c, true, false);
int bottomMargin = getMargin(c, false, false);
+ int sumMarginsX = leftMargin + rightMargin;
+ int sumMarginsY = topMargin + bottomMargin;
+
// Alignment offsets: the location of the view relative to its alignment group.
- int alignmentOffsetX = boundsX.getOffset(c, hAlign, leftMargin + pWidth + rightMargin);
- int alignmentOffsetY = boundsY.getOffset(c, vAlign, topMargin + pHeight + bottomMargin);
+ int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true);
+ int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false);
- int width = hAlign.getSizeInCell(c, pWidth, cellWidth - leftMargin - rightMargin);
- int height = vAlign.getSizeInCell(c, pHeight, cellHeight - topMargin - bottomMargin);
+ int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX);
+ int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY);
int dx = x1 + gravityOffsetX + alignmentOffsetX;
@@ -1181,7 +1185,7 @@ public class GridLayout extends ViewGroup {
View c = getChildAt(i);
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
- groupBounds.getValue(i).include(c, spec, GridLayout.this, this);
+ groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
}
}
@@ -2138,16 +2142,30 @@ public class GridLayout extends ViewGroup {
return before + after;
}
- protected int getOffset(View c, Alignment alignment, int size) {
- return before - alignment.getAlignmentValue(c, size);
+ private int getAlignmentValue(GridLayout gl, View c, int size, Alignment a, boolean horiz) {
+ boolean useLayoutBounds = gl.getLayoutMode() == LAYOUT_BOUNDS;
+ if (!useLayoutBounds) {
+ return a.getAlignmentValue(c, size);
+ } else {
+ Insets insets = c.getLayoutInsets();
+ int leadingInset = horiz ? insets.left : insets.top; // RTL?
+ int trailingInset = horiz ? insets.right : insets.bottom; // RTL?
+ int totalInset = leadingInset + trailingInset;
+ return leadingInset + a.getAlignmentValue(c, size - totalInset);
+ }
+ }
+
+ protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) {
+ return before - getAlignmentValue(gl, c, size, a, horizontal);
}
- protected final void include(View c, Spec spec, GridLayout gridLayout, Axis axis) {
+ protected final void include(GridLayout gl, View c, Spec spec, Axis axis) {
this.flexibility &= spec.getFlexibility();
- int size = gridLayout.getMeasurementIncludingMargin(c, axis.horizontal);
- Alignment alignment = gridLayout.getAlignment(spec.alignment, axis.horizontal);
+ boolean horizontal = axis.horizontal;
+ int size = gl.getMeasurementIncludingMargin(c, horizontal);
+ Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
// todo test this works correctly when the returned value is UNDEFINED
- int before = alignment.getAlignmentValue(c, size);
+ int before = getAlignmentValue(gl, c, size, alignment, horizontal);
include(before, size - before);
}
@@ -2614,8 +2632,8 @@ public class GridLayout extends ViewGroup {
}
@Override
- protected int getOffset(View c, Alignment alignment, int size) {
- return max(0, super.getOffset(c, alignment, size));
+ protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) {
+ return max(0, super.getOffset(gl, c, a, size, hrz));
}
};
}
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index c725b64..a13ee5a 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -24,6 +24,7 @@ import android.text.Spanned;
import android.text.method.WordIterator;
import android.text.style.SpellCheckSpan;
import android.text.style.SuggestionSpan;
+import android.util.Log;
import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SpellCheckerSession;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
@@ -43,6 +44,8 @@ import java.util.Locale;
* @hide
*/
public class SpellChecker implements SpellCheckerSessionListener {
+ private static final String TAG = SpellChecker.class.getSimpleName();
+ private static final boolean DBG = false;
// No more than this number of words will be parsed on each iteration to ensure a minimum
// lock of the UI thread
@@ -116,7 +119,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
null /* Bundle not currently used by the textServicesManager */,
mCurrentLocale, this,
false /* means any available languages from current spell checker */);
- mIsSentenceSpellCheckSupported = mSpellCheckerSession.isSentenceSpellCheckSupported();
+ mIsSentenceSpellCheckSupported = true;
}
// Restore SpellCheckSpans in pool
@@ -266,6 +269,12 @@ public class SpellChecker implements SpellCheckerSessionListener {
editable.subSequence(start, end).toString();
spellCheckSpan.setSpellCheckInProgress(true);
textInfos[textInfosCount++] = new TextInfo(word, mCookie, mIds[i]);
+ if (DBG) {
+ Log.d(TAG, "create TextInfo: (" + i + "/" + mLength + ")" + word
+ + ", cookie = " + mCookie + ", seq = "
+ + mIds[i] + ", sel start = " + selectionStart + ", sel end = "
+ + selectionEnd + ", start = " + start + ", end = " + end);
+ }
}
}
@@ -507,7 +516,20 @@ public class SpellChecker implements SpellCheckerSessionListener {
if (regionEnd <= spellCheckStart) {
return;
}
- addSpellCheckSpan(editable, spellCheckStart, regionEnd);
+ final int selectionStart = Selection.getSelectionStart(editable);
+ final int selectionEnd = Selection.getSelectionEnd(editable);
+ if (DBG) {
+ Log.d(TAG, "addSpellCheckSpan: "
+ + editable.subSequence(spellCheckStart, regionEnd)
+ + ", regionEnd = " + regionEnd + ", spellCheckStart = "
+ + spellCheckStart + ", sel start = " + selectionStart + ", sel end ="
+ + selectionEnd);
+ }
+ // Do not check this word if the user is currently editing it
+ if (spellCheckStart >= 0 && regionEnd > spellCheckStart
+ && (selectionEnd < spellCheckStart || selectionStart > regionEnd)) {
+ addSpellCheckSpan(editable, spellCheckStart, regionEnd);
+ }
} else {
while (wordStart <= end) {
if (wordEnd >= start && wordEnd > wordStart) {
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index f1dffa1..1ba6d43 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -51,6 +51,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AnimationUtils;
import android.widget.SpinnerAdapter;
import java.lang.ref.WeakReference;
@@ -596,19 +597,23 @@ public class ActionBarImpl extends ActionBar {
if (mCurWindowVisibility == View.VISIBLE && (mShowHideAnimationEnabled
|| alwaysAnimate)) {
mTopVisibilityView.setAlpha(0);
+ mTopVisibilityView.setTranslationY(-mTopVisibilityView.getHeight());
AnimatorSet anim = new AnimatorSet();
AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView, "alpha", 1));
+ b.with(ObjectAnimator.ofFloat(mTopVisibilityView, "translationY", 0));
if (mContentView != null) {
b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
-mTopVisibilityView.getHeight(), 0));
- mTopVisibilityView.setTranslationY(-mTopVisibilityView.getHeight());
- b.with(ObjectAnimator.ofFloat(mTopVisibilityView, "translationY", 0));
}
if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
mSplitView.setAlpha(0);
+ mSplitView.setTranslationY(mSplitView.getHeight());
mSplitView.setVisibility(View.VISIBLE);
b.with(ObjectAnimator.ofFloat(mSplitView, "alpha", 1));
+ b.with(ObjectAnimator.ofFloat(mSplitView, "translationY", 0));
}
+ anim.setInterpolator(AnimationUtils.loadInterpolator(mContext,
+ com.android.internal.R.interpolator.decelerate_quad));
anim.addListener(mShowListener);
mCurrentShowAnim = anim;
anim.start();
@@ -638,16 +643,20 @@ public class ActionBarImpl extends ActionBar {
mContainerView.setTransitioning(true);
AnimatorSet anim = new AnimatorSet();
AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView, "alpha", 0));
+ b.with(ObjectAnimator.ofFloat(mTopVisibilityView, "translationY",
+ -mTopVisibilityView.getHeight()));
if (mContentView != null) {
b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
0, -mTopVisibilityView.getHeight()));
- b.with(ObjectAnimator.ofFloat(mTopVisibilityView, "translationY",
- -mTopVisibilityView.getHeight()));
}
if (mSplitView != null && mSplitView.getVisibility() == View.VISIBLE) {
mSplitView.setAlpha(1);
b.with(ObjectAnimator.ofFloat(mSplitView, "alpha", 0));
+ b.with(ObjectAnimator.ofFloat(mSplitView, "translationY",
+ mSplitView.getHeight()));
}
+ anim.setInterpolator(AnimationUtils.loadInterpolator(mContext,
+ com.android.internal.R.interpolator.accelerate_quad));
anim.addListener(mHideListener);
mCurrentShowAnim = anim;
anim.start();
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index e00a853..6a09fe0 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -888,4 +888,19 @@ public class XmlUtils
;
}
}
+
+ public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
+ throws IOException, XmlPullParserException {
+ for (;;) {
+ int type = parser.next();
+ if (type == XmlPullParser.END_DOCUMENT
+ || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
+ return false;
+ }
+ if (type == XmlPullParser.START_TAG
+ && parser.getDepth() == outerDepth + 1) {
+ return true;
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
new file mode 100644
index 0000000..adfd3dc
--- /dev/null
+++ b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import com.android.internal.R;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.widget.RemoteViews.RemoteView;
+
+/**
+ * A layout that switches between its children based on the requested layout height.
+ * Each child specifies its minimum and maximum valid height. Results are undefined
+ * if children specify overlapping ranges. A child may specify the maximum height
+ * as 'unbounded' to indicate that it is willing to be displayed arbitrarily tall.
+ *
+ * <p>
+ * See {@link SizeAdaptiveLayout.LayoutParams} for a full description of the
+ * layout parameters used by SizeAdaptiveLayout.
+ */
+@RemoteView
+public class SizeAdaptiveLayout extends ViewGroup {
+
+ private static final String TAG = "SizeAdaptiveLayout";
+ private static final boolean DEBUG = false;
+ private static final long CROSSFADE_TIME = 250;
+
+ // TypedArray indices
+ private static final int MIN_VALID_HEIGHT =
+ R.styleable.SizeAdaptiveLayout_Layout_layout_minHeight;
+ private static final int MAX_VALID_HEIGHT =
+ R.styleable.SizeAdaptiveLayout_Layout_layout_maxHeight;
+
+ // view state
+ private View mActiveChild;
+ private View mLastActive;
+
+ // animation state
+ private AnimatorSet mTransitionAnimation;
+ private AnimatorListener mAnimatorListener;
+ private ObjectAnimator mFadePanel;
+ private ObjectAnimator mFadeView;
+ private int mCanceledAnimationCount;
+ private View mEnteringView;
+ private View mLeavingView;
+ // View used to hide larger views under smaller ones to create a uniform crossfade
+ private View mModestyPanel;
+ private int mModestyPanelTop;
+
+ public SizeAdaptiveLayout(Context context) {
+ super(context);
+ initialize();
+ }
+
+ public SizeAdaptiveLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize();
+ }
+
+ public SizeAdaptiveLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initialize();
+ }
+
+ private void initialize() {
+ mModestyPanel = new View(getContext());
+ // If the SizeAdaptiveLayout has a solid background, use it as a transition hint.
+ if (getBackground() instanceof ColorDrawable) {
+ mModestyPanel.setBackgroundDrawable(getBackground());
+ } else {
+ mModestyPanel.setBackgroundColor(Color.BLACK);
+ }
+ SizeAdaptiveLayout.LayoutParams layout =
+ new SizeAdaptiveLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ mModestyPanel.setLayoutParams(layout);
+ addView(mModestyPanel);
+ mFadePanel = ObjectAnimator.ofFloat(mModestyPanel, "alpha", 0f);
+ mFadeView = ObjectAnimator.ofFloat(null, "alpha", 0f);
+ mAnimatorListener = new BringToFrontOnEnd();
+ mTransitionAnimation = new AnimatorSet();
+ mTransitionAnimation.play(mFadeView).with(mFadePanel);
+ mTransitionAnimation.setDuration(CROSSFADE_TIME);
+ mTransitionAnimation.addListener(mAnimatorListener);
+ }
+
+ /**
+ * Visible for testing
+ * @hide
+ */
+ public Animator getTransitionAnimation() {
+ return mTransitionAnimation;
+ }
+
+ /**
+ * Visible for testing
+ * @hide
+ */
+ public View getModestyPanel() {
+ return mModestyPanel;
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ mLastActive = null;
+ // make sure all views start off invisible.
+ for (int i = 0; i < getChildCount(); i++) {
+ getChildAt(i).setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (DEBUG) Log.d(TAG, this + " measure spec: " +
+ MeasureSpec.toString(heightMeasureSpec));
+ View model = selectActiveChild(heightMeasureSpec);
+ SizeAdaptiveLayout.LayoutParams lp =
+ (SizeAdaptiveLayout.LayoutParams) model.getLayoutParams();
+ if (DEBUG) Log.d(TAG, "active min: " + lp.minHeight + " max: " + lp.maxHeight);
+ measureChild(model, widthMeasureSpec, heightMeasureSpec);
+ int childHeight = model.getMeasuredHeight();
+ int childWidth = model.getMeasuredHeight();
+ int childState = combineMeasuredStates(0, model.getMeasuredState());
+ if (DEBUG) Log.d(TAG, "measured child at: " + childHeight);
+ int resolvedWidth = resolveSizeAndState(childWidth, widthMeasureSpec, childState);
+ int resolvedheight = resolveSizeAndState(childHeight, heightMeasureSpec, childState);
+ setMeasuredDimension(resolvedWidth, resolvedheight);
+ if (DEBUG) Log.d(TAG, "resolved to: " + resolvedheight);
+ }
+
+ //TODO extend to width and height
+ private View selectActiveChild(int heightMeasureSpec) {
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ View unboundedView = null;
+ View tallestView = null;
+ int tallestViewSize = 0;
+ View smallestView = null;
+ int smallestViewSize = Integer.MAX_VALUE;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child != mModestyPanel) {
+ SizeAdaptiveLayout.LayoutParams lp =
+ (SizeAdaptiveLayout.LayoutParams) child.getLayoutParams();
+ if (DEBUG) Log.d(TAG, "looking at " + i +
+ " with min: " + lp.minHeight +
+ " max: " + lp.maxHeight);
+ if (lp.maxHeight == SizeAdaptiveLayout.LayoutParams.UNBOUNDED &&
+ unboundedView == null) {
+ unboundedView = child;
+ }
+ if (lp.maxHeight > tallestViewSize) {
+ tallestViewSize = lp.maxHeight;
+ tallestView = child;
+ }
+ if (lp.minHeight < smallestViewSize) {
+ smallestViewSize = lp.minHeight;
+ smallestView = child;
+ }
+ if (heightMode != MeasureSpec.UNSPECIFIED &&
+ heightSize >= lp.minHeight && heightSize <= lp.maxHeight) {
+ if (DEBUG) Log.d(TAG, " found exact match, finishing early");
+ return child;
+ }
+ }
+ }
+ if (unboundedView != null) {
+ tallestView = unboundedView;
+ }
+ if (heightMode == MeasureSpec.UNSPECIFIED) {
+ return tallestView;
+ }
+ if (heightSize > tallestViewSize) {
+ return tallestView;
+ }
+ return smallestView;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (DEBUG) Log.d(TAG, this + " onlayout height: " + (bottom - top));
+ mLastActive = mActiveChild;
+ int measureSpec = View.MeasureSpec.makeMeasureSpec(bottom - top,
+ View.MeasureSpec.EXACTLY);
+ mActiveChild = selectActiveChild(measureSpec);
+ mActiveChild.setVisibility(View.VISIBLE);
+
+ if (mLastActive != mActiveChild && mLastActive != null) {
+ if (DEBUG) Log.d(TAG, this + " changed children from: " + mLastActive +
+ " to: " + mActiveChild);
+
+ mEnteringView = mActiveChild;
+ mLeavingView = mLastActive;
+
+ mEnteringView.setAlpha(1f);
+
+ mModestyPanel.setAlpha(1f);
+ mModestyPanel.bringToFront();
+ mModestyPanelTop = mLeavingView.getHeight();
+ mModestyPanel.setVisibility(View.VISIBLE);
+ // TODO: mModestyPanel background should be compatible with mLeavingView
+
+ mLeavingView.bringToFront();
+
+ if (mTransitionAnimation.isRunning()) {
+ mTransitionAnimation.cancel();
+ }
+ mFadeView.setTarget(mLeavingView);
+ mFadeView.setFloatValues(0f);
+ mFadePanel.setFloatValues(0f);
+ mTransitionAnimation.setupStartValues();
+ mTransitionAnimation.start();
+ }
+ final int childWidth = mActiveChild.getMeasuredWidth();
+ final int childHeight = mActiveChild.getMeasuredHeight();
+ // TODO investigate setting LAYER_TYPE_HARDWARE on mLastActive
+ mActiveChild.layout(0, 0, 0 + childWidth, 0 + childHeight);
+
+ if (DEBUG) Log.d(TAG, "got modesty offset of " + mModestyPanelTop);
+ mModestyPanel.layout(0, mModestyPanelTop, 0 + childWidth, mModestyPanelTop + childHeight);
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ if (DEBUG) Log.d(TAG, "generate layout from attrs");
+ return new SizeAdaptiveLayout.LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ if (DEBUG) Log.d(TAG, "generate default layout from viewgroup");
+ return new SizeAdaptiveLayout.LayoutParams(p);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ if (DEBUG) Log.d(TAG, "generate default layout from null");
+ return new SizeAdaptiveLayout.LayoutParams();
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof SizeAdaptiveLayout.LayoutParams;
+ }
+
+ /**
+ * Per-child layout information associated with ViewSizeAdaptiveLayout.
+ *
+ * TODO extend to width and height
+ *
+ * @attr ref android.R.styleable#SizeAdaptiveLayout_Layout_layout_minHeight
+ * @attr ref android.R.styleable#SizeAdaptiveLayout_Layout_layout_maxHeight
+ */
+ public static class LayoutParams extends ViewGroup.LayoutParams {
+
+ /**
+ * Indicates the minimum valid height for the child.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public int minHeight;
+
+ /**
+ * Indicates the maximum valid height for the child.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public int maxHeight;
+
+ /**
+ * Constant value for maxHeight that indicates there is not maximum height.
+ */
+ public static final int UNBOUNDED = -1;
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ if (DEBUG) {
+ Log.d(TAG, "construct layout from attrs");
+ for (int i = 0; i < attrs.getAttributeCount(); i++) {
+ Log.d(TAG, " " + attrs.getAttributeName(i) + " = " +
+ attrs.getAttributeValue(i));
+ }
+ }
+ TypedArray a =
+ c.obtainStyledAttributes(attrs,
+ R.styleable.SizeAdaptiveLayout_Layout);
+
+ minHeight = a.getDimensionPixelSize(MIN_VALID_HEIGHT, 0);
+ if (DEBUG) Log.d(TAG, "got minHeight of: " + minHeight);
+
+ try {
+ maxHeight = a.getLayoutDimension(MAX_VALID_HEIGHT, UNBOUNDED);
+ if (DEBUG) Log.d(TAG, "got maxHeight of: " + maxHeight);
+ } catch (Exception e) {
+ if (DEBUG) Log.d(TAG, "caught exception looking for maxValidHeight " + e);
+ }
+
+ a.recycle();
+ }
+
+ /**
+ * Creates a new set of layout parameters with the specified width, height
+ * and valid height bounds.
+ *
+ * @param width the width, either {@link #MATCH_PARENT},
+ * {@link #WRAP_CONTENT} or a fixed size in pixels
+ * @param height the height, either {@link #MATCH_PARENT},
+ * {@link #WRAP_CONTENT} or a fixed size in pixels
+ * @param minHeight the minimum height of this child
+ * @param maxHeight the maximum height of this child
+ * or {@link #UNBOUNDED} if the child can grow forever
+ */
+ public LayoutParams(int width, int height, int minHeight, int maxHeight) {
+ super(width, height);
+ this.minHeight = minHeight;
+ this.maxHeight = maxHeight;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(int width, int height) {
+ this(width, height, UNBOUNDED, UNBOUNDED);
+ }
+
+ /**
+ * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
+ */
+ public LayoutParams() {
+ this(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(ViewGroup.LayoutParams p) {
+ super(p);
+ minHeight = UNBOUNDED;
+ maxHeight = UNBOUNDED;
+ }
+
+ public String debug(String output) {
+ return output + "SizeAdaptiveLayout.LayoutParams={" +
+ ", max=" + maxHeight +
+ ", max=" + minHeight + "}";
+ }
+ }
+
+ class BringToFrontOnEnd implements AnimatorListener {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCanceledAnimationCount == 0) {
+ mLeavingView.setVisibility(View.GONE);
+ mModestyPanel.setVisibility(View.GONE);
+ mEnteringView.bringToFront();
+ mEnteringView = null;
+ mLeavingView = null;
+ } else {
+ mCanceledAnimationCount--;
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceledAnimationCount++;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ if (DEBUG) Log.d(TAG, "fade animation repeated: should never happen.");
+ assert(false);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+ }
+}